AI Coworkers: Not Just Chatbots or Agents

Shipping Headlong's Design Doc: The Memory Ladder in ai-coworkers

In the previous post I compared Laude Institute’s Headlong runtime (Bash, the agent never sleeps) with ai-coworkers, my TypeScript version built around a tick loop. The layout is the easy part of that comparison. Memory is where the two runtimes diverge.

Headlong’s repo contains a design doc, unified_progressive_resolution_memory.md, whose first line is Status: NOT YET IMPLEMENTED. Its diagnosis is that agent memory tends to be two systems ignoring each other: curated notes the agent writes, and automatic summaries nobody can navigate, with no links between them.

Its fix is a ladder with rungs a factor of ten apart. Raw steps at the bottom, episodes above them, chapters above that. Every entry carries links to the coarser summary above and the finer detail below, and recall starts at the top and follows children down, reading roughly ten summaries per level. The cost is logarithmic: about four lookups for ten thousand memories.

I implemented it. Here is what that took: one retention bug, three schema columns, one pure tool, and a short list of things I refused to build.

Deleting broke the ladder

Their design assumes raw events are kept forever. Mine deleted them. The weekly reflect ritual in src/runtime/reflect.ts ran DELETE FROM events WHERE ts < ? AND kind NOT IN ('ritual.run','note') on anything older than 30 days, and a trigger cascaded each delete into the FTS index. A ladder that recalls by drilling down to raw events cannot survive that.

Pruned rows now move into an events_archive table in a single transaction. The insert is idempotent, so a retried ritual cannot double-copy a row, and a WAL checkpoint runs after the transaction commits. Nothing is deleted.

Provenance, or NULL

Rollups gained three columns: level, parent_id, and source_range, the event-id span the summary covers. Decisions gained source_events. Old databases migrate in place, and rows written before the change keep NULL provenance, because there is nothing honest to backfill them with.

ADR-0008, an architecture decision record in the repo, has a rule: provenance that cannot be validated is written as NULL, never faked. If a rollup’s span does not resolve against the events table, the rollup is stored with NULL provenance plus a log line.

memory.walk

memory.walk is a pure tool: zero LLM calls inside. Their design has the model navigate each level itself, choosing which summary to expand. I diverged there. Entry is FTS5 plus token overlap against rollup bodies, gated by a confidence threshold of 0.5. Above the threshold the tool runs a capped walk from the coarsest rollup down to the raw events in the span and returns an ordered trace of {level, id, why} rows, one per node it visited. Pure means the walk can be tested with fixtures instead of mocked model calls.

Below the threshold the walk does not throw. Refusal is a first-class return value: the tick loop sees {refused: true, reason} in the tool result and carries on. The outputs below are real runs against a scratch database seeded with two weeks of triage events; the trace shows the first path, the refusal shows the second.

"trace": [
  { "level": 2, "id": 1,
    "why": "entry: best lexical match at level 2 (3/3 tokens: parser, bugs, triage)" },
  { "level": 2, "id": 2,
    "why": "entry: best lexical match at level 2 (3/3 tokens: parser, bugs, triage)" },
  { "level": 0, "id": 1, "why": "hot FTS entry hit (3/3 tokens)" },
  ...
]
"refused": true,
"reason": "no confident entry point: best lexical match 0/3 (0.00) is below
the 0.5 threshold — refusing rather than walking a weak keyword match (ADR-0008)"

Left on the page

Their design docs offer more than I shipped. Monologue ticks, an always-thinking mode, were cut first. The sharpest thing this runtime does is decide not to act, and a coworker that narrates between ticks works against that.

Frequent rollup sealing is deferred behind a telemetry gate I wrote into ADR-0008: no new memory machinery until memory.walk usage data shows anyone walking the ladder. Vectors are my own call, not theirs: their design rejects vector distances outright, and I only defer the question until the lexical entry point proves too weak on real queries.

What the owner sees

Every week the reflect ritual writes state/memory-map.md: the whole ladder as one read-only page, regenerated from scratch each run. No code reads it back. It exists for the person who employs the coworker.

Promotions into MEMORY.md now split by confidence. Confident promotions apply immediately. Doubtful ones queue as reviewable candidates: the ritual already refused promotions that lost too much of the source text, and the queue makes those refusals reviewable.

The config flag memory_promotions: gated holds all promotions for a new coworker, mirroring how writes start as dry-run and earn --live. The strike command removes saved entries, and it writes a versioned snapshot before it touches anything.

What is parked

The graph layer, community detection, and the memory-provider interface are decided in an ADR and parked behind the usage gate. contrib/hermes-recall ships the drill-down pattern back out as a plain SKILL.md playbook for Hermes, which is where the skills format came from in the first place. The Headlong doc still reads Status: NOT YET IMPLEMENTED.