- Role
- Agent runtime + context assembly
- Problem
- 'Hi' used to pull old codebase memories and a pile of tools. Retrieval looked correct and the conversation felt broken.
- Constraint
- Continuity across restarts without stuffing every fact and capability into every prompt.
- Outcome
- Separate admission for identity, episodes, and tools; empty tool set on trivial turns; optional post-check on personal claims.
Ashley is a local personal agent. It can remember you across weeks without the prompt becoming a dump of everything it has ever known. Most of the work is deciding what not to load.
The failure that stuck
Early builds ran the full pipeline on every message — including “hi”, “thanks”, “okay”.
A greeting would retrieve an old conversation about a codebase, surface search and planning tools, and the model would cheerfully continue work nobody asked about. Similarity said the memory was relevant. The product was wrong.
That forced a boring rule I still like: trivial turns get almost nothing. No episodic recall, no agenda, no personality block, empty tool set. Reflect/critic skipped. The model has to answer like a person because it cannot invent a task.
Precision is not “did we find something similar?” It’s “does loading this make the next answer better?”
Two processes on purpose
ashley-core (Rust) owns durable state: facts, episodes, agenda, workers, presence. It never calls a model.
ashley-agent (TypeScript / Bun) owns the loop: retrieve, assemble prompt, tools, verify, session. Crash the agent and core still has the record. Change providers without rewriting storage.
The model only sees what crossed the socket for this turn. That boundary is the product.
Tools are rented for a step
There are something like 80 tools in ~10 groups. Shipping all of them every call is expensive and the model gets worse at picking.
Ashley starts with a small core set. Filesystem, browser, research, etc. stay out until load_tool_group. After a step uses a group, that group is dropped again. Need it next step? Load it again.
Trivial turns start with an empty set. No “just in case” capabilities.
Memory is not one bucket
Different stuff has different rules:
- Identity facts (name, timezone, role) go on every substantive turn. You should not have to embed near “timezone” for the agent to know it.
- Episodes compete inside a ~1,200 token budget.
- Recent turns bridge the gap before summarisation lands.
- Preferences change delivery, not “what is true.”
Under pressure, episodes get cut before identity. That is a product choice, written into the assembler.
After generation, check the personal claims
Retrieval does not stop the model inventing continuity. On heavier turns a second pass marks personal claims as supported, contradicted, or invented against stored evidence. Tool-grounded answers (files, screen, web) skip the memory judge — the live tool output is the source of truth.
When not to act
Ashley can nudge and initiate, so there is an expected-utility gate on proactive work, under hard safety ceilings for destructive stuff. An audit once found the safety ceiling had unit tests but was not wired into the live IPC path. Modules green, product open. Wiring the full request path is the only test that counts there.
Still a prototype
Token budgets are still character approximations. Fact provenance is thin. Summarisation can promote bad extractions facts. Reflect fails open if the judge is down. Episodic storage still depends on Memvid in places I do not fully trust after Greppa’s measurements.
What I care about showing is inspectable decisions: which sources ran, which tools were admitted, what was left out. Without that, debugging an agent is staring at a final prompt and guessing.