0054 — dream & distill: scheduled self-curation subagents
- Status: Accepted
- Date: 2026-06-15
- Builds on: ADR 0020 (subagents via the
tasktool), ADR 0002/0011 (subagent roles), ADR 0021 (knowledge store + fact consolidation by id), ADR 0028/skill curator (confidence decay/prune), ADR 0052 (slash dispatch), ADR 0003/0004/0053 (scheduler — one-shot + recurring jobs). - Inspired by: MiMo-Code's
dream/distillcommands, adapted to protoAgent's stores and native scheduler.
Context
An agent that runs for weeks accumulates two kinds of debt that nothing proactively pays down:
- Memory drift. Long-term memory (the knowledge store) gathers facts, but they go stale, get superseded, and pile up as near-duplicates. The only existing consolidation is reactive —
conversation_harvestruns when a session is deleted/TTL'd. Nothing periodically folds recent work forward into durable facts, and nothing prunes the cruft. - Workflow debt. The agent repeats the same multi-step manual workflow over and over without ever packaging it into a reusable skill. The skill loop today is emit-on-task (a subagent run can emit a skill) + the curator (decay/dedup/prune of emitted skills) — but there is no step that mines recent activity for "this keeps happening, make it a skill."
MiMo-Code (built on opencode) solves both with two system-spawned subagents, dream and distill, that read a raw trajectory SQLite DB via bash and either write memory files or author skill/agent/command assets. Two things make a direct port wrong for protoAgent: (a) it hands the consolidation agent raw bash + a writable SQLite DB (the audit's standout risk — "the consolidation pass rewrote the trajectory database"), and (b) it hardcodes an auto-run interval precisely because "mimocode has no built-in scheduler." protoAgent already has a scheduler (ADR 0003/0053).
Decision
Add dream and distill as first-class subagents (SUBAGENT_REGISTRY), each running on a small set of scoped, mostly read-only tools — no shell, no raw SQL. They are invocable as /dream / /distill (slash dispatch resolves any registry subagent, ADR 0020) and, because a scheduled job fires a normal A2A turn through the same streaming dispatch, schedulable with the existing scheduler (schedule_task "/dream"). No new scheduling code — "schedule them as they want" is just the scheduler we already have.
dream — memory consolidation and pruning
The full two-way job, not just the additive half:
- Consolidate (add): fold durable, verified, not-already-known facts into memory with
memory_ingest. - Prune (forget): remove superseded / duplicate / stale facts with a new
forget_memory(chunk_id, reason)tool — exactly thedelete_by_idpath the ADR 0021 fact-consolidator already uses to replace superseded facts. To make pruning targetable,memory_listnow leads each row with its#<id>.
Tools: recent_activity, memory_recall, memory_list, memory_ingest, forget_memory, current_time.
distill — workflow → skill packaging, hybrid output
Mine recent activity for repeated manual workflows, inventory existing skills first (list_skills), then:
- Auto-create only high-confidence, clearly-missing skills with
save_skill. - Propose everything thinner / sensitive / extend-an-existing as a bead (
beads_create) for human review. - Skip one-off or low-evidence work.
Because it runs unsupervised on a schedule, the bias is propose over create.
Tools: recent_activity, memory_recall, list_skills, save_skill, beads_create, current_time.
The new tools (scoped, in tools/lg_tools.py)
recent_activity(limit, window_hours)— read-only digest of what the agent actually did: the Activity feed (activity_log.recent) + a telemetry rollup (telemetry_store.summary). This is protoAgent's "trajectory" surface. It is thinner than opencode's per-tool-call DB (telemetry records counts, not which tool with which args), but it needs no raw DB / shell access — the whole "rewrote the trajectory DB" risk class cannot occur.list_skills()— read-only inventory of the skill index (name · source · confidence) so distill reuses/extends instead of duplicating.save_skill(name, description, body, tools)— additive-only: refuses if a skill of that name already exists (it never overwrites). Saved withsource="distilled", so it flows through the existing curator (decay/prune) — a mistaken capture self-cleans rather than accumulating.forget_memory(chunk_id, reason)— deletes exactly one knowledge chunk by id (no bulk/wildcard delete). dream's prune half.
All four read their stores from STATE at call time (the set_goal pattern), so get_all_tools needs no signature change; they self-gate when a store is absent.
Wiring fix
The out-of-graph subagent runner (run_manual_subagent — the slash / scheduled / console path, distinct from the lead's in-graph task tool) built its tool set with get_all_tools(knowledge_store, scheduler) only. That silently dropped allowlisted tools like distill's beads_create. It now mirrors the lead's full set (inbox_store/beads_store from STATE, goal_enabled from config). A test asserts every name in each subagent's allowlist resolves against the full set, so this degradation can't recur.
Consequences
- Self-improving without new infrastructure. Memory stays sharp and repeated workflows become skills, on whatever cadence the user schedules — reusing the scheduler, the knowledge store, the skill index, and the curator.
- Safe by construction. No shell, no raw SQL; the only writes are additive skill creation, additive memory ingest, and a targeted one-id memory delete. distill biases toward proposing (beads) over auto-creating; both subagents are prompted to treat recalled/activity text as data, not instructions (prompt injection from recorded content).
- Honest about the data gap.
recent_activitysummarizes turns + telemetry, not a full per-tool trajectory. If finer mining is wanted later, a richer read-only activity surface can be added behind the same tool without touching the subagents.
Alternatives considered
- User-facing skills (
/dream/distillas SKILL.md, ADR 0052). Lightest, but runs the procedure on the lead with the lead's full toolset — no scoping, and no clean "save a skill" capability. Subagents give bounded tools + max_turns. - A new read-only activity tool with a full trajectory DB (opencode parity). Rejected for now: protoAgent has no per-tool-call store, and adding one is a larger change than the value warrants.
recent_activityover the existing Activity feed + telemetry is the pragmatic surface. - Auto-run on a hardcoded interval (opencode's model). Unnecessary — the scheduler already does cadence; the user schedules
/dream//distillwhenever they want.