Skip to content

[design] The platform owns the conversation — an engine's own memory is an optimisation, not the source of truth #693

Description

@serge-ivo

The owner's words: "why do users have to know about sessions? why can't it just be seamless? when a user starts engaging with the agent it resurrects last everything as if the session never died" — and then: "can't it be fully persisted and work via DB completely, instead of relying on ephemeral sessions?"

The answer to the second question is where the first one is actually decided, so this issue is about ownership of conversation state. The user-facing consequences are filed separately (#695, #696, #697, #698) and are all cheap; this is the one with design in it.

The durable half already exists

coding_timeline is append-only in D1, 100 000 characters per entry (lib/coding-timeline.ts:59args.content.slice(0, 100_000)), and interleaves chat_user · chat_assistant · terminal · command · brain · outcome · system (:14-21). It is readable per session and per repo, the repo-scoped read tagging each row with its sessionId so the UI can draw boundaries (:31-35).

It works. The console reloads history from it, and #257 already uses the repo-scoped read so a finished run does not leave an empty screen.

It is read only ever FOR THE HUMAN. Nothing feeds it back INTO an engine.

The half that is not ours

Continuity today is a pointer, not a record. coding/headless.ts:266:

this.claudeSessionId = readState(config.statePath, config.id) ?? (config.resumeFrom ? readState(config.statePath, config.resumeFrom) : null);

readState (:938) reads a JSON file on the runner's disk (statePath: defaultStatePath(this.reposBaseDir), coding/runtime.ts:238) mapping our coding_sessions.idClaude Code's own session id. The conversation itself lives inside ~/.claude, owned by another program, on one machine.

Four consequences follow, all verified in code:

  1. Machine-bound. The state file is local to the runner that wrote it. See [bug] A coding session relocated to another machine silently starts with an empty conversation — the resume pointer is a file on the machine it left #694 for the case where this silently loses a live conversation.
  2. Not ours to keep. Clearing ~/.claude, or Claude Code changing its store format, ends continuity and no D1 row can help.
  3. Opaque. We cannot read, compact, or measure it. This is why RESUME_WINDOW_MS (lib/coding-session-continuity.ts:47) is four days: the header argues it from a long weekend, which is honest reasoning about a quantity we cannot observe.
  4. Claude-only. headless.ts:260 returns resumedConversation false "for a raw (non-Claude) engine under every circumstance". Codex and Grok have no conversation at all — for them seamlessness does not degrade, it has never existed.

Proposal: invert the dependency

The platform owns the conversation. An engine's own memory becomes an optimisation, not the source of truth. On wake, seed the engine from coding_timeline instead of relying on --resume.

Slices, ordered so the first cannot regress anything:

  1. Seed-from-timeline as the FALLBACK, used exactly where resolveSessionContinuity returns fresh today for a reason other than forceFresh: past the window, a different engine, a missing last_activity_at. Every one of those starts stone cold now, so any brief is an improvement and there is nothing to regress. The reason strings already distinguish these cases.
  2. Make it primary for raw engines. They gain multi-turn memory they have never had. Again nothing to regress — see headless.ts:256-260.
  3. Only once proven, decide whether Claude keeps preferring --resume (cheaper, higher fidelity when present) or moves over too.

Slice 1 also makes RESUME_WINDOW_MS a policy dial rather than a constraint, which is what the owner actually asked for ("night time or for a few days"). Filing a separate "make the window configurable" issue would be treating the symptom — a configurable window still falls off a cliff, it just falls later.

What this cannot buy — state plainly, do not let it surface later

  • Reconstruction, not restoration. Claude Code's context is not a message list; it holds tool state, file caches and its own compaction. Seeding gives continuity of understanding, not a byte-identical context. Often better — a curated brief beats a bloated transcript — but the claim must not be "as if it never died".
  • A process is not data. You cannot persist a running child process, only reconstitute one fast enough that nobody notices. Measured: a wake with --resume answered a memory question in 8 seconds (2026-08-17, instance bd43f4de).
  • The machine must be on. Unmovable, and it should be reported as "your Mac mini is offline", never in session vocabulary.
  • Token cost is unchanged. Re-sending context every turn is an LLM property, not a storage choice. Compaction matters either way — the platform already summarises chat after 20 messages and the same trick applies here.

Verified end-to-end, 2026-08-17

Instance bd43f4de (Chess coder 2), repo apps/chess-academy. Previous session csess_21b8d1b9 reaped at 6h idle. POST /v1/instances/:id/coding/sessions returned:

"resumed": true,
"continuity": { "mode": "resume", "resumeFrom": "csess_21b8d1b9-…",
                "reason": "the previous conversation on this repo was last touched 11 hours ago" }

The engine then recalled its previous closing question verbatim against coding_timeline seq 9227, and the correct 11-of-12 figure, with no tool use. So the mechanism works today — on one machine, for one engine, within four days, reachable from one surface. This issue is about removing those four qualifiers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3: laterDesign, strategy, or deferred pending a decision or demandbackendBackend / Worker / API workcoderThe Coder wedge agent (#68) — Engine, Pilot, Co-pilot, Loop, OverseerenhancementNew feature or requestrunnerpackages/browser-runner + packages/cli — ships only via an npm publish

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions