Personalized agents

The problem
The feature was a personalized agent inside the product: a chat the user opens in the app, where the agent already holds their data, all of it, whatever the product has on them, and answers or acts from it.
That is a data-access and memory problem before it is a model problem: assemble one user's data for the turn, keep prior context, never leak one user's context into another's, all while the agent runs in a sandbox destroyed at session end.
What I built
OpenClaw is the overall agent architecture. Users talk to their agent through chat inside the app, and a Railway orchestrator routes each conversation. Every agent runs in an isolated, stateless E2B sandbox.
The memory layer is built from tools I picked and wired, in three tiers: Supermemory for long-term user memory, a QMD service for retrieval and memory search on a Prime Intellect GPU with persistent storage, and an archive in the product database kept apart from live memory. The model is served on Baseten. The tiering and wiring are mine; the components are existing tools.
I also built the operational side: Prometheus and Grafana dashboards for tracing, monitoring and evaluation.
The failure worth describing
The first build used the memory that shipped with OpenClaw. Inside stateless sandboxes it did not hold: context from one session was not reliably there for the next, and what returned was hard to attribute.
I traced it to the memory layer, not the prompt. I kept OpenClaw as the agent runtime and replaced its memory layer with a three-tier architecture that replicates the Nous Hermes memory design, which I studied and then implemented here. The turn tier is the live context for the message, assembled by QMD's retrieval. The user tier is held in Supermemory. The archive in the product database is the durable tier. Recall across sessions held.
How it works
- A message arrives in the in-app chat.
- The orchestrator routes it to that user's agent.
- It starts the agent in a fresh sandbox, so each run begins clean.
- The runtime reads that user's data from the product, and their memory, then writes to the archive.
- It calls the retrieval service, whose persistent storage keeps memory across sessions.
- It calls the model endpoint, served outside the sandbox so the provider can be swapped.
- The reply follows the same path back, and the sandbox is discarded.
How I kept it reliable and safe
The sandbox is stateless and per session, so failures stay contained. Memory is tiered: live context serves the turn, the user tier carries continuity, and the archive holds the record, so one tier failing does not erase the others.
Every boundary is typed. JSON schemas and Pydantic catch a malformed output before it is acted on, retries and fallbacks cover a service that does not answer, and guardrails bound what the agent may do.
The dashboards show which tier produced a weak answer.
My role
I designed and built this end to end: the orchestrator, the sandbox runtime, the memory tiers, the retrieval service, the model serving path, and the tracing layer.
What I'd measure next
Task success per conversation, how often a turn needed a retry or a fallback, and which tier a failure came from, so the fix lands at the layer that caused it.