memory-working-agent
v0.5.0
Published
MWA — Memory Working Agent. A turnkey TS agent runtime built on AWM as an always-on cognitive substrate, so a cheap model conducts, resumes, builds on prior reasoning, and runs long without re-onboarding or context rot.
Downloads
61
Maintainers
Readme
MWA — Memory Working Agent
A lightweight, turnkey autonomous coding orchestrator that runs a cheap model on the AWM decision substrate. The cheap model conducts — it recalls prior decisions, dispatches coding subtasks to a worker, reads files for ground truth, supersedes facts that go stale, and drives the task to done without a human — while AWM keeps it on-goal and lets it get smarter with reuse.
Thesis: capability for orchestration lives in the substrate, not the model weights. A cheap conductor + memory can match a frontier model on cost — and at scale, memory does something no context window can.
New in 0.4.0 — the memory substrate. MWA was rebuilt from the substrate up: every memory and action now carries provenance, trust, namespace, and an action record, and six capabilities — transparency, injection safety, governance, honest confidence, topic scoping, self-improving routines — sit on that spine. One smart memory doing what other harnesses need many bolt-on subsystems for. → Read the story: docs/the-memory-substrate.md
Two results
1. Orchestration — cheap conductor matches frontier (A/B/C benchmark)
3 objective tasks × 2 runs, fixed graders, real models:
| arm | model | AWM | pass | total cost | avg dispatches | |---|---|---|---|---|---| | A | gpt-5-4-mini | on | 6/6 | $0.019 | 1 | | B | gpt-5-4-mini | off | 6/6 | $0.013 | 1 | | C | Claude Sonnet | off | 4/6 | $0.069 | 1 |
- Cheap matches (here, beats) frontier at ~3.5× lower cost — both gpt-5-4-mini arms hit 100%; Sonnet's 2 misses were both the hard no-
.reverse()constraint on the palindrome task (a strong-prior trap; small N=2, not a claim that cheap is generally better). - The orchestration win is modest on easy tasks — the hybrid loop one-shots each task (1 dispatch) with or without memory, so AWM's per-task overhead is tiny but so is its savings. That's the honest V1→V2 lesson: AWM's decisive value isn't here — it's at scale (Result 2).
2. Memory at scale — what a context window structurally can't do
The orchestration win is modest on easy tasks (recall overhead vs savings). The decisive win is at scale, and it's the reason AWM exists: past ~500K tokens you can no longer keep a large project's context alive by carrying it. Measured on a real large software project:
| to answer one question, carry… | tokens | AWM scoped recall | |---|---|---| | the whole system (code + docs) | ~29,000,000 — fits in no window, any tier | ~630, flat | | a real notes/transcript repo | ~2,000,000 | ~630, flat | | the accumulated memory (~20K memories) | ~1,300,000 | ~630, flat |
Empirically, on real questions, scoped recall used ~5× fewer tokens than opening
the single best-matching doc, switched between projects with 0% cross-project bleed,
stayed correct after code changed (via supersede, which a notes file can't),
and compressed structured results another ~48% (TOON). Full methodology, honest
caveats, and the where-it-doesn't-win scorecard: results/RESULTS-V2.md.
Why AWM — and why not the usual alternatives
Most "memory for AI" is a vector database with a retrieval wrapper. AWM is a cognitive substrate: it decides what's worth keeping, strengthens what gets used, forgets noise, and knows when a fact has changed. That difference is what lets a cheap conductor hold up over a long, messy, real project instead of drowning in its own history.
The alternatives, honestly
| Approach | What breaks as the project grows |
|---|---|
| Stuff the context window | Hard wall. Past the window you summarize — and summarizing silently drops the one fact you needed next. 29M tokens fits in no tier, any model. |
| Notes file (CLAUDE.md, markdown) | The whole file rides in every prompt, and it goes stale silently — nothing tells you a line is now wrong. One file = no project isolation. |
| Plain RAG / vector DB | Returns top-k chunks with no salience, decay, or correction — so it surfaces the old value next to the new one, and noise grows with the corpus. |
| Rolling summary | Lossy by construction; overwrites the specifics you'll want verbatim later (a column name, a working query, a prior correction). |
| Flat fact store (Mem0-style) | Better — but no consolidation, no Hebbian reinforcement, no staleness signal. Facts pile up flat and you pay for the dead weight on every query. |
| AWM | Scoped recall stays ~630 tokens flat as the store grows; supersede retires stale facts so recall stops returning them; salience-gating makes the ~90% never-recalled memories cost ≈0; agent/workspace scoping gives 0% cross-project bleed. |
The real wins (measured, not asserted)
- Flat cost at scale. ~630 tokens to answer a question whether the store holds 600 memories or 20,000 — ~2,000× less than carrying the store, ~5× less than opening the single best-matching doc file. (
src/repo-qa.ts) - Stays correct when reality changes. A fact changes between visits →
supersede→ recall returns the new value, not the old. A notes file or plain vector store can't signal this. (src/staleness.ts) - No cross-project bleed. Interleave several projects in one store → 0% wrong-project recall, via agent/workspace scoping. (
src/contextswitch.ts) - Capability moves into the substrate. On a real domain workload, gpt-5.4-mini primed by AWM scored 14/15 vs a frontier model's 7/15 at ~1/40th the cost — the hard-won knowledge lived in memory, not the model's weights. (AWM-Native Harness pattern)
What's actually under the hood (specs)
Grounded in cognitive science — ACT-R activation decay, Hebbian learning, complementary learning systems, synaptic homeostasis + tagging — not ad-hoc heuristics:
- Recall pipeline: BM25 + dense embeddings (
bge-small-en-v1.5, 384-d) + a cross-encoder reranker (ms-marco-MiniLM-L-6-v2), with query expansion (flan-t5-small), prefix-tag / entity-bridge boosts, and optional confidence-based abstention. ~300 ms typical, all in-process. - Write pipeline: novelty + salience gating (low-value writes stage or drop), reinforce-on-duplicate (restating a fact with new detail merges + re-embeds rather than discarding), supersede-on-correction, and a
canonicalclass with a salience floor for source-of-truth facts. <10 ms per write. - Engines: Activation (recall), Connection (Hebbian graph), Consolidation ("sleep" — cluster, strengthen, bridge, decay), Retraction (forget). Consolidation is what makes recall mature over time instead of just accumulating.
- Content fade: un-recalled memories degrade their body but keep their cue pathways (concept + tags + embedding), so old memories stay findable while recall keeps the hot ones full-fidelity. Write-and-forget is safe.
- Backends: SQLite (embedded, multi-process-safe WAL — the default) or PGlite (embedded Postgres + pgvector).
- Output compression: TOON encodes structured tool results ~50–65% smaller, losslessly (every encode is verified
encode→decode→deep-equal). - Embedded, no server: MWA links AWM in-process —
EngramStore+ engines +performWritedirectly — so there's no daemon and no network hop. AWM is vendored as a git submodule atvendor/agent-working-memory, so a bug fix or feature found while building the agent flows straight back upstream.
Full methodology + the honest where-it-doesn't-win scorecard: results/RESULTS-V2.md; AWM internals: its own README.
Install (Docker) — one container, onboards itself
The container is the whole app: open it in a browser, get guided setup on first run,
then just chat. Everything persistent (secrets .env, AWM memory, workspace, model
cache) lives on the /data volume — on your disk. Your provider key is entered in the
setup screen and only ever goes to the AI provider you chose.
Pull the published image (no local build needed):
docker run -d -p 127.0.0.1:7788:7788 -v mwa-data:/data --name mwa \
ghcr.io/completeideas/mwa:latest
# open http://localhost:7788 → pick a model → chat. (Build locally instead with
# `docker build -t mwa .` if you've cloned the repo with --recurse-submodules.)Or use Compose (cp .env.example .env, then docker compose up -d).
Running it on a network / NAS? Set an access password and lock it down:
docker run -d -p 7788:7788 -v mwa-data:/data --name mwa \
-e MWA_ACCESS_PASSWORD='a-strong-password' \
ghcr.io/completeideas/mwa:latestMWA_ACCESS_PASSWORD puts the whole UI + API behind a login (empty = no gate, the
localhost default). For an always-on box (so scheduled tasks fire) — including a
Synology NAS — see docs/deploy-nas.md (Container Manager,
Tailscale-only access, volume mapping, RAM notes).
Quickstart (local, turnkey)
Prereqs: Node 22+ (and a C toolchain for better-sqlite3). Either run the wizard
(npm run wizard → http://localhost:7788) or put API keys in .env directly
(see Config).
git clone --recurse-submodules https://github.com/CompleteIdeas/memory-working-agent.git
cd memory-working-agent
npm install # links the vendored AWM submodule + web app as npm workspaces
npm run build # builds AWM (submodule) + MWA + the web app (→ dist-ui)
npm run serve # → opens http://localhost:7788 — guided setup, then just chat with itmwa serve (also the default, mwa with no args) is the one command for humans: it
opens a local web app in your browser — connect a model, then talk to your assistant and
watch it remember, look things up, and do the work (the live "activity spine"). Keys
stay on your machine. For developers there's also mwa run "<instruction>" (headless),
mwa watch (a drop-folder), mwa connect telegram, and npm run bench (the A/B/C
benchmark UI).
Connecting email (Gmail or Outlook) uses a guided bring-your-own sign-in — you
make a free Google/Microsoft app once and the token stays on your machine; there's no
managed broker (no per-user cost, no third-party token custody). See
docs/connect-email.md for the steps and the plan to make it
near-turnkey via a homegrown mwa connect helper.
Already cloned without submodules? Run
git submodule update --initthennpm install && npm run build. AWM lives atvendor/agent-working-memoryand is built from source, so MWA always runs the exact AWM it vendors — fix it there and the change flows upstream.
npm run setup (published as the mwa bin) is turnkey and non-interactive — it reads
your .env, checks the AWM substrate, and writes mwa.config.json (model tiers,
enabled tools, escalation). Re-run it any time; edit the file to change tooling.
Tools
The brain calls native function-calling tools. Beyond the orchestration tools
(dispatch/recall/read/supersede/done), MWA has a pluggable tool registry
(src/tools/): each tool is a ToolDef + handler, enabled by name in mwa.config.json.
Built-ins: run_command (sandbox shell), read_file, list_files, http_request.
MCP bridge (live). Point tools.mcpServers at any MCP server and its tools register
into the same registry — namespaced <server>__<tool>, no code:
// mwa.config.json
"tools": {
"builtins": ["run_command", "read_file", "list_files"],
"mcpServers": {
"filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] }
}
}So MWA drives the whole MCP ecosystem (filesystem, github, postgres — even AWM itself)
on the cheap-conductor + memory substrate. The brain sees built-in and MCP tools as one
unified tool list. (Uses @modelcontextprotocol/sdk; servers that fail to connect are
logged and skipped, never aborting a run.)
Run it live
Give the agent a free-form instruction and a working directory — it works toward it over many steps on the AWM substrate, then stops on done / stuck / budget:
mwa run "add a /health route to server.js and verify it returns 200" --dir .
# dev: npm run agent -- "…instruction…" --dir . --max-steps 40 --max-min 10It recalls prior context, acts via tools (dispatch codegen / run_command / read /
recall / supersede / any MCP tool), verifies its work, and self-determines done — no
fixed grader. Long runs stay flat in tokens: only recent steps sit in the prompt, older
context returns via recall, and it sleeps (consolidates) on phase boundaries. The cheap
brain conducts; the worker escalates cheap→strong only when it stalls. Memory persists across
runs (--db, default ./data/agent.db) — so it gets sharper the more you use it.
Talk to it from your phone (chat connector)
A connector bridges a messaging channel to the agent (message in → run → reply out):
# get a bot token from @BotFather, then:
TELEGRAM_BOT_TOKEN=… TELEGRAM_ALLOWED_CHATS=<your chat id> mwa connect telegramText the bot an instruction; it runs the agent and replies with the result + output
files. Security: the agent can run commands, so only chat ids in
TELEGRAM_ALLOWED_CHATS are served — message the bot once to learn your id, then add
it. Email (Gmail) and WhatsApp (Twilio) follow the same connector pattern.
Mailbox (file I/O)
mwa watch polls <workspace>/inbox/ for instruction files, works each in
outputs/<id>/, and writes a report to outbox/<id>.md. Drop a .md/.txt in,
get results + generated files out — no server.
V2 evidence experiments (deterministic where possible):
npx tsx src/retention.ts # decision survives N distractors at flat token cost
npx tsx src/repo-qa.ts # scan → wipe → return: recall vs notes-file vs long-context
npx tsx src/staleness.ts # code changes between visits: supersede stays correct
npx tsx src/contextswitch.ts # interleaved projects: 0% cross-project bleed at scale
npx tsx src/headtohead.ts # AWM recall vs grep+read a real docs repo (needs a work snapshot)One command in Docker (reproducible, no local toolchain):
docker build -t mwa-bench .
docker run --rm --env-file .env -v mwa-hf:/data/hf -e BENCH_RUNS=2 mwa-benchConfig
.env (gitignored; never baked into the image — passed at runtime):
ANTHROPIC_API_KEY=... # high-model arm (Claude Sonnet)
AZURE_GPT_BASE_URL=https://<res>.api.cognitive.microsoft.com/openai/v1
AZURE_GPT_API_KEY=... # brain (gpt-5-4-mini, Azure Responses API)
AZURE_GPT_DEPLOYMENT=gpt-5-4-miniIf AZURE_GPT_* is absent, the brain falls back to Claude Haiku automatically. Every runtime env
knob — memory backend (AWM_STORE_BACKEND = sqlite/pglite/postgres), access/security, per-person,
scheduler, connectors — is enumerated in docs/configuration.md.
Shared memory across agents/CLIs (opt-in)
Set awm.workspace in mwa.config.json to a name and every MWA surface (web, CLI, Telegram,
mailbox) joins it — so a decision made on one surface/agent is recalled by another. Unset =
isolated per-agent (the default). This is the one thing a per-process notes file or vector index
can't do; in the cross-agent benchmark, AWM recalls another agent's decisions 100% where
RAG/notes/no-memory score 0% (per-process stores can't share). See docs/gauntlet.md.
{ "awm": { "workspace": "home" } }Who it's talking to (per-person Profile)
MWA adapts to the person on the other end and learns about them over time. It resolves who's
talking from the channel (Telegram chat id, email sender, a web ?person= name), keeps a
per-person, hard-isolated profile (one person's notes never surface for another), and primes a
compact "who I'm talking to" block each turn so it matches their style, depth, and how to draw the
most useful exchange out of them — tone/format only, never facts and never the safety gates. It
learns durable facts about them (so it stops re-asking) into a bounded, self-recompressing dossier,
and tracks a small intake set (what to call you / how to reach you / role / working style).
Identity is watched, not assumed: if a message contradicts what's on record (a different name or
contact), MWA flags the turn, redacts the stored profile from its own prompt, and — for a
sensitive/irreversible action — mechanically blocks it until a second factor is verified
out-of-band through a validated connection (step-up verification). The suspicion is sticky across
turns, so splitting the claim from the request can't slip past it. Off-switches: MWA_PERSON_PROFILE=0,
MWA_PERSON_LEARN=0. Design + threat model: docs/true-agent-and-person-profile.md.
Maintenance — mwa consolidate
mwa consolidate # cluster overlapping memories, strengthen/decay edges, sweep staging,
# create syntheses — matures the store. Safe anytime; good as a cron.The agent also consolidates automatically during long runs; this command covers stores built
from many short interactions. (Recall defaults to a rerank-only path — measured ~2× faster than
adding query-expansion, with no accuracy cost even on paraphrase queries; set
AWM_RECALL_EXPAND=1 to restore expansion-by-default.)
Benchmark
npm run gauntlet / npm run gauntlet:crossagent run a controlled, single-factor ablation
(memory substrate as the only variable; AWM vs vector-RAG vs notes vs long-context vs none) with
deterministic scoring, token + recall-latency metrics, and pass^k confidence intervals. The
honest write-up — including where AWM ties a well-built RAG and where it wins by construction —
is in docs/gauntlet.md.
Operations, security & embedding
Running MWA as a real service or backend:
- Security: set
MWA_ACCESS_PASSWORD(or keep it on a private network / Tailscale); secrets live only in a local.env(mode0600) — never in API responses or the npm tarball. File/command reach is gated by the access preset (locked-down/assistant/developer) + a granted-roots allowlist. State-changing endpoints require a same-origin request (CSRF). Irreversible/production writes go through a dry-run→confirm gate; if the person's identity is in doubt, that confirm additionally requires step-up (a 2nd factor). Adding an arbitrary MCP server requires explicit approval; removing tools is always allowed. - Limits & lifecycle: per-run token + wall-clock + step caps; a concurrency cap and idle-session eviction protect the box; SIGTERM drains in-flight work before exit;
/api/healthreports DB + scheduler liveness (the Docker healthcheck uses it). - Observability: structured logs at
data/mwa.log(JSONL), surfaced viaGET /api/logs. - As a backend/library: compose
runAgent+MwaMemory+ the tool registry directly — see docs/embedding-mwa.md. - HTTP API: every
mwa serveendpoint is documented in docs/api-reference.md.
How it works
The brain runs a hybrid memory↔grep loop (the discipline validated in V2), not pure recall. Each turn it calls one native function-calling tool (the proven USEA design — the API returns validated tool calls, not hand-parsed JSON):
recall— scoped, full-content recall of prior decisions/learnings (first, and re-queried mid-task framed to the current sub-problem).read— read a sandbox file for ground truth when memory is silent or to verify.dispatch— hand a concrete coding step to the worker; result + decision written to AWM.supersede— replace a recalled fact the moment a result proves it wrong (stay fresh).done— only after a dispatch produced passing tests this run (guarded against a recalled "success" tricking it into skipping the work).
Components:
- brain (
src/brain.ts) — the loop above; native tools viasrc/provider.ts(Azure gpt-5-4-mini onchat/completions, Anthropic via SDK), text-JSON fallback for non-tool models. - router (
src/model-router.ts) —classifyIntentpicks the starting tier; consecutive worker failures escalate cheap→strong, AWM shared across the switch. - worker (
src/worker.ts) — codegen+exec into a sandbox, runs the fixed test, reports pass/fail. The grader is protected from overwrite. - AWM (
src/awm.ts) — in-process substrate:MwaMemory(recall + write + supersede + feedback) for arm A;NullMemoryfor AWM-off arms. Same loop, memory swaps. - tasks (
src/tasks.ts) — objective, ungameable; each ships a fixed test, plus a hard constraint checked against the produced code. - benchmark (
src/benchmark.ts) — arms A/B/C × tasks × N →results/.
Layout
src/
cli.ts # `mwa` entry: run · watch · connect · wizard · setup
brain.ts agent.ts # hybrid loops: bench conductor + live agent
provider.ts model-router.ts env.ts util.ts # native tool-calling providers + cheap→strong router
awm.ts # in-process AWM substrate (recall/write/supersede/schedule)
tools/ (registry · builtins · build · mcp) # pluggable tool registry + MCP client bridge
connectors/ (telegram · google) # chat + Gmail/Calendar (read + draft, never send)
mailbox.ts scheduler.ts wizard.ts config.ts # file I/O · scheduled tasks · onboarding · config
worker.ts tasks.ts benchmark.ts server.ts # worker + A/B/C bench + live UI
retention.ts staleness.ts repo-qa.ts contextswitch.ts headtohead.ts # V2 evidence experiments
vendor/agent-working-memory/ # AWM — git submodule, built from source
mcp-servers/search.mjs # bundled keyless web-search MCP server
results/RESULTS-V2.md # the honest V2 writeup
Dockerfile docker-entrypoint.sh README.mdStatus
0.5.0 (Apache-2.0, sub-1.0 on purpose; unreleased work tracked in CHANGELOG.md).
Core loop + A/B/C benchmark + Docker + serve UI verified; the brain embodies the USEA-tuned hybrid
loop (recall-first, grep-on-miss, supersede-on-divergence). Shipping: the live agent (mwa run /
mwa serve), mailbox (mwa watch), Telegram + Gmail/Outlook/Calendar connectors (read + draft only),
a scheduler on AWM's task ledger (with a cheap change-detection gate), an MCP tool bridge, multi-provider
models (Anthropic/Azure/OpenAI/OpenRouter/Ollama), a pluggable memory backend (SQLite · PGlite ·
Postgres), the per-person Profile + step-up identity layer, and AWM vendored as a built-from-source
submodule. Dry-run→confirm write approval + CSRF + trust-tiered provenance are in place. V2 measured
where AWM decisively wins — long-horizon retention, context switching, staleness, and scale — with the
honest scorecard in results/RESULTS-V2.md. Latest audit + fix ledger:
docs/gap-analysis-2026-07.md.
