Decopilot context compactification — parked design, resume after #3337
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 407
- Forks
- 57
- Avg merge
- 5h 6m
- Merged PRs (30d)
- 880
Description
Status: Parked. Resume after #3337 (refactor(automations): use DBOS for scheduling and firing) merges.
That PR replaces the in-process cron worker, JetStream job queue, and semaphore with DBOS workflows. The Observer in this design is an async worker — riding DBOS rather than building parallel async infra is the right path.
The task-list / TodoWrite-style feature has been designed and shipped independently of this (see the matching todo_write spec/PR).
Goals (ranked)
- A. Threads never hit the model context ceiling. Hard requirement.
- D. Multi-day thread continuity. A user returning on day 14 finds the agent coherent.
- (Secondary) Token cost, lost-in-the-middle quality.
Current state of decopilot
- Memory loads the last 50 messages per turn (
apps/mesh/src/api/routes/decopilot/memory.ts,DEFAULT_WINDOW_SIZE). - Prompt assembly: system prompts + threadMessages + requestMessage → AI SDK
convertToModelMessages→streamText(apps/mesh/src/api/routes/decopilot/stream-core.ts). - No summarization, rollup, or token-aware truncation exists. A long thread will eventually break.
- Docs (
apps/docs/.../decopilot/context.mdx) sketch a 6-slot context layout and a 40/80 rule, marked as intended-not-implemented.
Options surveyed
| # | Name | Trigger | Authorship | Mutability | Cache-friendly | Provider-portable | UI-inspectable |
|---|---|---|---|---|---|---|---|
| 1 | Rolling Digest + Recent Window | threshold | system | rewritten | ❌ | ✅ | prose |
| 2 | Tiered Agentic Memory (MemGPT/Letta-style) | agent decides | agent via MCP tools | append + edit | ⚠️ | ❌ varies by model | structured |
| 3 | Typed ProjectState + Event Log | threshold | extractor side-call | patched | ⚠️ | ✅ | structured |
| 4 | Asynchronous Observational Log (Mastra-style) | threshold | async observer | append-only + reflections | ✅✅ | ✅ | prose |
Lineage: Claude Code /compact, Aider repo-map, SWE-agent observation elision, Anthropic memory tool + context editing, Letta/MemGPT (Packer 2023), CoALA (Sumers 2024), Reflexion (Shinn 2023), Mastra Observational Memory (2025), "Lost in the middle" (Liu 2024), recursive summarization (Wu 2021).
Selected design: hybrid 4 + 3
Per-thread durable artifacts:
- Observational log — append-only markdown observations written by an async Observer (cheap model, e.g. Gemini Flash or Haiku). Triggered when unobserved-tokens > 30k. Sync fallback at 1.2× threshold to prevent overflow. When the log itself grows past ~40k tokens, a reflection pass compacts older observations into denser ones (two-level hierarchy).
- Typed
ProjectStatesidecar — small Zod-schemaed snapshot extracted from observations during reflection. Approximate shape:{ goals: string[]; open_questions: string[]; files: Record<string, { summary: string; last_seen_hash: string }>; blockers: string[]; }ProjectStateis queryable and human-inspectable in the UI. It is derived from observations, not authored independently — single source of truth. - Task list — decoupled from this design, model-managed via the
todo_writetool (separate PR), per-thread, ephemeral, chat-UI-rendered.
Per-turn prompt assembly
Ordered stable → mutable for prompt-cache reuse:
1. system prompts (cached)
2. ProjectState (cached until next reflection)
3. Observations + reflections (cached, append-only)
4. Open task list (small mutable)
5. Last K raw turns (K ≈ 10–20; was 50)
6. New user message
Anthropic prompt caching benefits 1–3 directly. Stages 4–6 are small.
Why hybrid over pure 4
- Pure observational log is prose. Goal D ("user returns on day 14") is partly a UX problem — the user wants to see what the agent remembers. A typed
ProjectStatepanel makes that legible. - Multi-provider via AI SDK means a typed sidecar survives model swaps mid-thread; a prose summary's style does not.
- Extracting
ProjectStateonly during reflection (not every observation) keeps the extractor cost amortized.
Why async observer matters
- No latency tax on user turns. Compaction is invisible.
- Burst handling via sync fallback at 1.2× threshold preserves goal A.
- Decopilot's existing event bus + NATS notify could carry this — but the DBOS migration in #3337 changes the async substrate. Building on the post-PR primitives avoids rework.
Replaced/changed surface
apps/mesh/src/api/routes/decopilot/memory.ts—loadHistory(50)→loadRecentMessages(K)+ newloadObservations(threadId)+loadProjectState(threadId).apps/mesh/src/api/routes/decopilot/stream-core.ts— prompt assembly updated; emit "message-saved" events for the Observer.- New migrations:
thread_observations(id, thread_id, kind 'observation'|'reflection', content, tokens_covered, created_at)thread_project_state(thread_id PK, state_jsonb, updated_at)
- New DBOS workflow:
observation-workersubscribed to message-saved events, debounced per thread, threshold-gated. - New cheap-model provider configuration: which model the Observer uses (default Gemini 2.5 Flash, configurable per org).
Open questions to resolve on resume
- Exact thresholds (30k / 40k are Mastra defaults — validate against decopilot's typical thread shape).
- Tool-call output handling: SWE-agent-style elision of stale
tool_resultblocks (replace with<elided n_tokens=...>placeholders + path-keyed cache for file reads). How aggressive? - Cross-thread memory: out of scope here, but worth a brief note on whether
ProjectStateshould escape thread boundary later (e.g. per-project facts). - Cost ceiling for the Observer (per-org rate limit?).
- Failure modes: Observer crashes → fall back to raw window + hard ceiling guard.
Out of scope
- Cross-thread / cross-project semantic memory (CoALA semantic tier).
- Vector retrieval over archived turns (could be a later phase).
- User-editable observations or
ProjectState(Observer is authoritative). - The task-list /
todo_writefeature (separate PR).
Resume checklist
When #3337 lands:
- Read the merged DBOS workflow API and update the Observer worker design.
- Validate thresholds against real thread data (
SELECT thread_id, SUM(tokens) FROM thread_messages GROUP BY thread_id). - Spec the cheap-model provider plumbing (org-level config, fallback chain).
- Resume brainstorming → writing-plans flow.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
When #3337 merges, read the DBOS workflow API and revisit apps/mesh/src/api/routes/decopilot/memory.ts and stream-core.ts. Validate the 30k/40k thresholds with the listed thread_messages query, then resolve provider, tool-result, and failure-mode questions before writing the plan. Done means the post-DBOS design is specified for observations, ProjectState, prompt assembly, and the observation worker.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- ai, backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100