Devlog #6 š
I just wanted to add a way for robot to effectively store and fetch its memory.
I expected this phase to be fairly straightforward.
The runtime already had State Estimation, Planning and Scheduling. Memory sounded like another subsystem to plug into the pipeline, expose a few interfaces and move on.
It didnāt quite go that way.
I started with what seemed like the obvious design:
A common memory interface with multiple interchangeable strategies. Instead of committing to a single implementation, I built:
- FIFO
- Time Decay
- Priority
- Episodic
- SPSCA
behind the same abstraction. I knew Iād want to compare different approaches later, so making them interchangeable from the beginning felt like the right decision.
Most of the implementation went smoothly š„.
Then I reached Sparse Principal Component Analysis (SPSCA).
Unlike the other strategies, SPSCA wasnāt just storing records. It was trying to represent them. My first implementation followed the algorithm closely. Every piece of content was recursively encoded using semantic pointer operations before being stored. At the time, I was pretty happy with it.
Then I ran the benchmarks.
The larger scenarios became painfully slow. Some didnāt even finish. I was upset that this whole work and time for nothing and just ruining the project? :-(
At this point, I feel like giving up on this and do the next thing or just implement this in the simplest way possible. I actually started making the next phase idea and then while doing that, suddenly out of nowhere, I got an idea about how to find the exact cause of failure š„¹.
So, I just went to see that was my hypothesis was correct about the error.
I spent a whole evening looking for small optimizations. Once I did, I found the issue with it!. The encoding stage itself had become the bottleneck.
That forced me to see the bigger picture of whatās happening during that stage.
Instead of trying to optimize the existing implementation, I redesigned the encoding pipeline completely. Memory content is now serialized into a deterministic representation and converted into fixed-length vectors using SHA-256 before participating in similarity operations. The semantic pointer operations are still implemented, but theyāre no longer part of every storage operation.
That one decision simplified the whole implementation very much.
Watching the benchmarks finally finish without timing out was a huge relief. š
With the implementation stable again, I turned my attention to testing. I added benchmarks for insertion, retrieval latency, sustained memory growth and strategy comparisons, along with a much larger test suite covering storage, retrieval, forgetting behaviour, vector operations and runtime integration.
By the end of the phase, the runtime could finally retain experiences across execution cycles.
But, stopped myself from immediately moving on to the next feature as there were still a few loose ends I wanted to clean up before building anything on top of Memory.
Nothing in the runtime actually depends on memory yet. Planning doesnāt query it, scheduling doesnāt use it, and no module learns from previous experience. I considered pushing further, but decided against it. Changing the foundation becomes much harder once other components start depending on it.
As a small side quest during this phase, I also redesigned the project homepage. Hopefully it finally looks the way I originally imagined it.
For now, memory exists for one reason: to give the runtime a place to store or fetch it in a very efficient way.