From 2f5e9486f8ed56e698230dcaca9c9dfaa111f176 Mon Sep 17 00:00:00 2001 From: Marcel Hild Date: Mon, 31 Aug 2026 10:49:16 +0200 Subject: [PATCH] docs(adr): resume agent sessions from JSONL transcripts Record the contributors-meeting decision that continuation replays a prior-run transcript into a new ephemeral sandbox, rather than keeping the process alive or introducing cross-run memory. Numbered 0094 because 0092 and 0095 landed on main while the first PR was closed by the vouch gate, and 0093 is in-flight. Signed-off-by: Marcel Hild Co-authored-by: Cursor --- .../0021-jsonl-reasoning-trace-exposure.md | 6 + ...e-agent-sessions-from-jsonl-transcripts.md | 110 ++++++++++++++++++ docs/architecture.md | 15 ++- docs/problems/agent-architecture.md | 2 +- docs/problems/agent-infrastructure.md | 2 +- docs/problems/cross-run-memory.md | 4 +- docs/problems/security-threat-model.md | 2 +- 7 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 docs/ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md diff --git a/docs/ADRs/0021-jsonl-reasoning-trace-exposure.md b/docs/ADRs/0021-jsonl-reasoning-trace-exposure.md index 062e030d59..559790b8ce 100644 --- a/docs/ADRs/0021-jsonl-reasoning-trace-exposure.md +++ b/docs/ADRs/0021-jsonl-reasoning-trace-exposure.md @@ -19,6 +19,12 @@ Date: 2026-04-15 Accepted +Platform resume of a prior run (new ephemeral sandbox + JSONL conversation +tree) is decided in +[ADR 0094](0094-resume-agent-sessions-from-jsonl-transcripts.md). This ADR +still governs JSONL exposure; 0094 governs using that JSONL as the next +run's starting conversation. + ## Context The retro agent needs access to the raw JSONL conversation transcripts that diff --git a/docs/ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md b/docs/ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md new file mode 100644 index 0000000000..9891681cf2 --- /dev/null +++ b/docs/ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md @@ -0,0 +1,110 @@ +--- +title: "94. Resume agent sessions from JSONL transcripts" +status: Accepted +relates_to: + - agent-infrastructure + - security-threat-model + - cross-run-memory +topics: + - session + - transcript + - sandbox + - dispatch + - jsonl +--- + +# 94. Resume agent sessions from JSONL transcripts + +Date: 2026-08-27 + +## Status + +Accepted + +Builds on JSONL exposure +([ADR 0021](0021-jsonl-reasoning-trace-exposure.md)), ephemeral sandboxes +([ADR 0016](0016-unidirectional-control-flow.md), +[ADR 0036](0036-agent-execution-sandbox.md)), and is distinct from the +conversation surface +([ADR 0086](0086-conversation-surface-for-agent-participation.md)). + +Motivated by the 2026-08-26 contributors meeting and +[#459](https://github.com/fullsend-ai/fullsend/issues/459). + +## Context + +Every fullsend dispatch starts a new ephemeral sandbox and rebuilds agent +context from the work item. That is the right isolation default, but it is +expensive for small follow-ups: a `/fs-fix` that changes one line still pays +the full cold-start token cost. + +Keeping the GitHub Action alive until a human replies is not viable (job +timeouts, idle compute). Cross-run memory is a different problem +([cross-run-memory.md](../problems/cross-run-memory.md)). Re-injecting forge +comments is also not a conversation tree. + +JSONL transcripts are already extracted +([ADR 0021](0021-jsonl-reasoning-trace-exposure.md)). Runtimes already resume +from them (`claude --resume`, Pi's session tree). Loading a CI artifact into +a local OpenShell sandbox has been demonstrated. + +## Options + +### A. Keep the process alive + +Hold the runner until the human replies. Rejected: timeouts, idle cost, and +it does not survive "an hour later." + +### B. Cross-run memory / third-party session store + +Persist lessons or session blobs in a new store. Rejected: poisoning, +staleness, and a second instruction channel. Resume is not memory. + +### C. Re-inject forge comments only + +The next run reads issue/PR comments as today. Loses tool-call history and +KV-cache continuity. Already the default; not a continuation. + +### D. Restore the JSONL conversation tree into a new sandbox (chosen) + +Start a new ephemeral run whose runtime session is the prior transcript +(optionally forked at a turn). Sandbox filesystem and process state are not +restored; the repo is cloned fresh. + +## Decision + +Adopt **Option D**. + +Session continuation means: **replay a prior run's JSONL transcript as the +starting conversation of a new ephemeral sandbox**, scoped to the **same +agent** and **same work item**. + +- The sandbox stays ephemeral. No process, volume, or third-party memory + store survives the first run. +- The transcript is an **input**, like issue text: untrusted, access-controlled + by [ADR 0021](0021-jsonl-reasoning-trace-exposure.md). Suppressed JSONL + cannot be resumed. +- Provider prompt-cache hits are an optimization, not a requirement. Resume + must work even when the cache is cold. +- Scratch remains the default for unlabeled dispatch. Compacting, starting + from scratch, and resuming are distinct patterns; this ADR only adds + resume. +- Cross-role consumption of another agent's JSONL is not resume; it stays + under [cross-run-memory.md](../problems/cross-run-memory.md). +- Exact trigger UX (slash flag vs dedicated command) and local vs CI + packaging are follow-on implementation. Both surfaces use this model. + Local resume is tracked in + [#459](https://github.com/fullsend-ai/fullsend/issues/459). + +## Consequences + +- Cheap follow-ups (`/fs-fix` "remove that line", interactive skills that + wait on a human reply) can reuse the prior conversation instead of + rebuilding context. +- Injection surface grows by one hop: a poisoned first run's transcript + becomes context for the second. Same-agent, same-work-item, explicit + trigger, and ADR 0021 access control bound that hop. +- Rebuilt sandboxes may differ (image, tools, HEAD). Agents must tolerate + that; pinning the environment is a later choice. +- Long sessions may still need compaction — a later choice, not a + requirement of resume. diff --git a/docs/architecture.md b/docs/architecture.md index 9b05e588fd..68eca38f0d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -58,6 +58,7 @@ the dedicated org-level `/.fullsend` config repo is deprecated - Dispatch version-skew resolution: per-repo `reusable-dispatch.yml` inlines stage workflow jobs directly, eliminating `@v0` references to `reusable-{stage}.yml` ([ADR 0062](ADRs/0062-dispatch-version-skew.md)). - Ready-made configuration presets: `fullsend github setup --config ` installs a vendor preset as `.fullsend/config.base.yaml` and a stub `.fullsend/config.yaml` overlay in the target repository; mint URL, inference backend, and related settings live in configuration files resolved through accessor methods, not CLI flags. Shared-infrastructure presets will reduce per-adopter enrollment (target state): mint via `job_workflow_ref` trust per [ADR 0059](ADRs/0059-public-mint-mode-with-wildcard-allowlists.md); inference authorization model undecided ([ADR 0069](ADRs/0069-ready-made-configuration-presets.md)); enrollment remains required until follow-on ADRs land. - GitLab event dispatch: two-path model — native CI triggers (`merge_request_event`) for MR events, cron-based polling for issues/comments/labels. No external infrastructure (no webhook bridge). Bot PAT stored as a protected CI/CD variable. Per-repo only ([ADR 0067](ADRs/0067-gitlab-cron-polling-event-dispatch.md)). +- Do not hold CI jobs open for human follow-ups. Session continuation starts a **new** ephemeral run whose runtime session is the prior JSONL transcript ([ADR 0094](ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md)). **Open questions:** @@ -84,10 +85,12 @@ each target repository's **`.fullsend/`** directory - ~~How granular is network regulation? Allowlist of endpoints, or coarser controls?~~ Decided in [ADR 0065](ADRs/0065-provider-backed-policy-composition.md): network access is granted through provider profiles with per-endpoint allowlists. - Does the sandbox provide a pre-built environment (tools, language runtimes, repo clones), or does the agent set up its own workspace within the sandbox? - ~~Is the sandbox the same for all agent roles, or does each role get a differently-scoped sandbox?~~ Decided in [ADR 0020](ADRs/0020-composable-single-responsibility-agents-with-individual-sandboxes.md): each agent gets its own sandbox with policies designed for its responsibility. +- Should a resumed run pin sandbox image and tooling so the rebuilt environment matches the prior run? (Filesystem state is not restored; [ADR 0094](ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md).) **Decided:** - Provider-backed policy composition: network access is granted through provider profiles declared in harness files. Policy files define only non-composable sandbox restrictions (filesystem, landlock, process). A single `base.yaml` replaces per-agent policy files in the scaffold. Inline `network_policies` continue to work but providers are the recommended approach ([ADR 0065](ADRs/0065-provider-backed-policy-composition.md)). +- Session continuation restores a prior JSONL conversation tree into a **new** sandbox; filesystem and process state still do not persist ([ADR 0094](ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md)). ## Agent Harness @@ -293,6 +296,10 @@ The existing design principle is that [the repo is the coordinator](problems/age `admin`) using source-native role resolution; the resolved role feeds the same authorization gate with no cross-system identity verification ([ADR 0054](ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md)). +- Session continuation: an authorized dispatch may start a new run whose + runtime session is a prior JSONL transcript for the same agent and work + item. Scratch remains the default. Exact slash-command UX is follow-on + ([ADR 0094](ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md)). **Open questions:** @@ -309,6 +316,10 @@ The existing design principle is that [the repo is the coordinator](problems/age [ADR 0086](ADRs/0086-conversation-surface-for-agent-participation.md))? - How should concurrent agent runs that touch the same conversation thread be coordinated ([ADR 0086](ADRs/0086-conversation-surface-for-agent-participation.md))? +- How does the operator select which transcript or turn to resume from + (latest run on the work item, explicit run ID, conversation-tree node)? + (Session continuation itself is + [ADR 0094](ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md).) ## Policy Store @@ -353,7 +364,7 @@ Observability is a cross-cutting concern that touches every other component. Eac **Decided:** -- JSONL reasoning trace exposure: raw JSONL conversation transcripts are extracted from sandboxes and stored with owner-scoped access. Credential scanning acts as an invariant check on [ADR 0017](ADRs/0017-credential-isolation-for-sandboxed-agents.md)'s isolation model. Agents handling data from protected sources beyond the target repo can opt in to JSONL suppression via configuration ([ADR 0021](ADRs/0021-jsonl-reasoning-trace-exposure.md)). +- JSONL reasoning trace exposure: raw JSONL conversation transcripts are extracted from sandboxes and stored with owner-scoped access. Credential scanning acts as an invariant check on [ADR 0017](ADRs/0017-credential-isolation-for-sandboxed-agents.md)'s isolation model. Agents handling data from protected sources beyond the target repo can opt in to JSONL suppression via configuration ([ADR 0021](ADRs/0021-jsonl-reasoning-trace-exposure.md)). Those transcripts are also the payload for session continuation: a later run may replay one as its starting conversation ([ADR 0094](ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md)). - Event-driven stage dispatch remains traceable end-to-end in the GitHub Actions UI by using synchronous `workflow_call` dispatch (see [ADR 0041](ADRs/0041-synchronous-workflow-call-event-dispatch.md)). - Distributed tracing: framework-native OpenTelemetry instrumentation with zero-configuration baseline. Every run produces `run-telemetry.jsonl` locally; optional live OTLP export to any compatible backend. W3C trace context propagation links multi-agent pipelines into unified traces. OTEL GenAI semantic conventions enable LLM-aware backends ([ADR 0050](ADRs/0050-distributed-tracing-instrumentation.md)). - Eval measurements: the concept of scoring traces ([fail-open](glossary.md#fail-open)). [OTEL primary facts](glossary.md#otel-primary-facts) stay on the run trace (`run-telemetry.jsonl`); [OTEL derived products](glossary.md#otel-derived-products) are the scores (`eval-measurements.jsonl`) ([ADR 0087](ADRs/0087-eval-measurements-online-trace-scoring.md)). See [Eval Measurements](guides/infrastructure/eval-measurements.md). @@ -711,7 +722,7 @@ event ──► DISPATCHER - **Credentials never cross the sandbox boundary.** They exist in the agent runner layer; the sandbox and everything inside it operate without them. - **Control flows inward (setup) then outward (teardown).** The harness configures the sandbox; the sandbox constrains the runtime. No inner layer can modify an outer layer. - **Validation gates output.** When configured, no unvalidated output crosses from runner to external system. Exhausted retries are a hard failure, not a fallback. -- **The sandbox is ephemeral.** Created per-run, destroyed after extraction. No state carries between runs. +- **The sandbox is ephemeral.** Created per-run, destroyed after extraction. Sandbox filesystem and process state do not carry between runs. A later run may receive a prior JSONL transcript as input ([ADR 0094](ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md)); that is not sandbox state. ### MVP embodiment: GitHub + GitHub Actions + OpenShell + Claude Code diff --git a/docs/problems/agent-architecture.md b/docs/problems/agent-architecture.md index b4c21e8bfe..f7a3275382 100644 --- a/docs/problems/agent-architecture.md +++ b/docs/problems/agent-architecture.md @@ -211,7 +211,7 @@ The multi-agent framework space is expanding rapidly, with new entries appearing ## Open questions -- Should agents be stateless (fresh context per task) or stateful (accumulated knowledge of the codebase)? Stateless is safer (no poisoned state persists) but less efficient. +- Should agents be stateless (fresh context per task) or stateful (accumulated knowledge of the codebase)? Stateless is safer (no poisoned state persists) but less efficient. (Per-run sandbox remains ephemeral; replaying a prior JSONL conversation tree for the same agent and work item is decided in [ADR 0094](../ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md). Accumulated codebase knowledge and cross-run memory remain open — see [cross-run-memory.md](cross-run-memory.md).) - Should there be one instance of each agent type per repo, per org, or shared? Per-repo is simpler but more expensive. Shared agents need careful isolation. (Infrastructure constrains this — see [agent-infrastructure.md](agent-infrastructure.md).) - ~~What's the right model for agent identity? Agents need GitHub accounts to post comments and status checks. Separate bot accounts per agent role? A single bot account with role indicated in the comment? GitHub App installations?~~ Decided in [ADR 0007](../ADRs/0007-per-role-github-apps.md): per-role GitHub Apps with manifest-based creation. - How do we test the interaction model? Can we simulate adversarial scenarios (injection attempts, unauthorized changes, agent disagreements) in a sandbox repo? diff --git a/docs/problems/agent-infrastructure.md b/docs/problems/agent-infrastructure.md index 04859dac0a..154a3f5946 100644 --- a/docs/problems/agent-infrastructure.md +++ b/docs/problems/agent-infrastructure.md @@ -69,7 +69,7 @@ Agents are often discussed as if they run on a developer workstation: fast local - **CI feedback latency** — Keeping agents out of **local** execution for policy or isolation reasons often leaves only **asynchronous** CI (webhooks, queued pipeline runs). That weakens the tight edit–test–fix loop models assume on a laptop. The gap between “patch pushed” and “signal returned” affects whether an agent can clear syntax and unit failures within a single session; [repo-readiness.md](repo-readiness.md) covers CI maturity and reliable signals more broadly. -- **Workspace and context continuity** — Ephemeral jobs reset filesystem state between runs or stages. Carrying **in-progress repo state, partial edits, and task context** across those boundaries requires explicit design: shared volumes (for example PVCs in Kubernetes), artifact handoff between steps, branches or WIP commits, or external systems (issues, design docs). Without a deliberate handoff story, every run starts cold and context-window limits bite harder. +- **Workspace and context continuity** — Ephemeral jobs reset filesystem state between runs or stages. Carrying **in-progress repo state, partial edits, and task context** across those boundaries requires explicit design: shared volumes (for example PVCs in Kubernetes), artifact handoff between steps, branches or WIP commits, or external systems (issues, design docs). Without a deliberate handoff story, every run starts cold and context-window limits bite harder. (Conversation-tree handoff via JSONL into a new sandbox is decided in [ADR 0094](../ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md); filesystem/process persistence across runs is still not done.) - **Compute held open for human latency** — A long-lived pod that **blocks on PR approval, architecture sign-off, or escalation** consumes cluster quota and cost while idle. That misaligns with typical “always-on service” defaults. Better fits include **event-driven** scheduling (wake on comment or approval), aggressive scale-to-zero, or separating **planning** from **execution** so capacity is not reserved across human response times; see [human-factors.md](human-factors.md) and [autonomy-spectrum.md](autonomy-spectrum.md). diff --git a/docs/problems/cross-run-memory.md b/docs/problems/cross-run-memory.md index 24cb6ba5da..f979479107 100644 --- a/docs/problems/cross-run-memory.md +++ b/docs/problems/cross-run-memory.md @@ -25,6 +25,8 @@ Two mechanisms partially address this: Neither mechanism provides automatic, immediate feedback from one run's outcome to the next run's context. That gap is the problem space. +**Session continuation is not this problem.** Replaying a prior run's JSONL transcript into a new ephemeral sandbox, for the same agent and work item, is decided in [ADR 0094](../ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md). That restores a conversation tree; it does not promote lessons, observations, or instructions to later unrelated runs. Cross-role consumption of another agent's JSONL remains in this problem space. + ## The feedback-loop tension There are at least three different kinds of memory, and they should not be treated the same way: @@ -131,7 +133,7 @@ Retry loops can become flapping when the system does not converge. See [flapping - Should memory entries be observations only, or can any of them become instructions? - How should future agents distinguish system-derived facts from agent-authored interpretations? - Can non-review-gated memory be made safe enough, or should all durable memory promotion go through reviewed repo instructions or skills? -- How should memory interact with separate agent roles? Should triage memory ever influence review behavior directly? +- How should memory interact with separate agent roles? Should triage memory ever influence review behavior directly? (Same-agent JSONL resume is [ADR 0094](../ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md); cross-role transcript consumption is still this question.) - What retention model prevents stale memory from dominating: time-based, count-based, outcome-based, or explicit supersession? - Should the retro agent curate memory by pruning stale entries and proposing durable skill additions, or would that give it too much influence over future runs? - How should memory interact with structured agent output? Should agent output include an "observations" field that post-scripts can validate and classify? diff --git a/docs/problems/security-threat-model.md b/docs/problems/security-threat-model.md index cf77d4959b..902af5538a 100644 --- a/docs/problems/security-threat-model.md +++ b/docs/problems/security-threat-model.md @@ -88,7 +88,7 @@ In fullsend, agents cannot take forge actions directly — credentialed operatio Another injection variant documented in Shapira et al. (2026) bypasses the immutability principle by storing malicious instructions in an external resource (e.g., a GitHub Gist) that the agent references from its persistent state. The attacker convinces the agent to link to a shared document, then modifies the document after the fact to inject instructions the agent follows in subsequent sessions. -In fullsend's architecture, this is mitigated by several design decisions: agent configuration is immutable from within the sandbox ([ADR 0017](../ADRs/0017-credential-isolation-for-sandboxed-agents.md)), agents cannot modify their own guardrails, and the harness validates agent output against a schema ([ADR 0022](../ADRs/0022-harness-level-output-schema-enforcement.md)). However, the pattern is worth noting because any mechanism that allows agents to fetch and follow external content (URLs in issues, linked documents, referenced specifications) creates a potential injection surface that persists across sessions. +In fullsend's architecture, this is mitigated by several design decisions: agent configuration is immutable from within the sandbox ([ADR 0017](../ADRs/0017-credential-isolation-for-sandboxed-agents.md)), agents cannot modify their own guardrails, and the harness validates agent output against a schema ([ADR 0022](../ADRs/0022-harness-level-output-schema-enforcement.md)). However, the pattern is worth noting because any mechanism that allows agents to fetch and follow external content (URLs in issues, linked documents, referenced specifications) creates a potential injection surface that persists across sessions. Session continuation ([ADR 0094](../ADRs/0094-resume-agent-sessions-from-jsonl-transcripts.md)) replays a prior-run JSONL transcript into a new sandbox for the same agent and work item — it is not persistent agent state, but it does make the first run's transcript an input to the second. ### Open questions