Add a name on client and resume from it #1

Closed
opened 2026-10-25 23:02:05 +00:00 by SebastienDenooz · 7 comments

When we use not-ai agent like codex or claude, add a name to the session that came from the branch name and try to resume from it.

Functional analysis

Status: ready. All open points were settled by @SebastienDenooz on 2026-10-26, who accepted the proposed defaults for q1–q6.

Understanding of the request

Sometimes a run is executed by a local coding-agent CLI (Claude Code, Codex, … — §16.5) instead of a model provider API. In that case the engine names the CLI session after the issue's agent branch name ({BRANCH_PREFIX}issue-{iid}-{slug}, §7.3). On every later run for the same issue, it tries to resume that session. This way the agent keeps the context it built in earlier runs: analysis, plan, code it wrote, CI fixes.

Decisions

# Topic Decision
q1 Runtimes in scope Every local adapter whose CLI supports resumable sessions (claude-code, codex, opencode, copilot, grok-build). Each adapter declares a capability for this (§16.3). API mode is excluded.
q2 Session name The deterministic branch name computed from the issue (e.g. agent/issue-128-add-name). It is used from the very first run, so analysis phases use it too, whether or not the branch has been pushed yet.
q3 Runs that resume Every run of the same issue mailbox, whatever the phase (analysis, development, ci_fix, review_fix, conflict_resolution, answer). There is one continuous memory per issue.
q4 Resume not possible Silent fallback: a fresh session is started under the same name and the run continues. The fallback is recorded on the session row. It never fails the run, never counts as an LLM retry and never posts a comment.
q5 Session state lifetime Session state is kept per issue mailbox between runs. It is removed together with the worktree cleanup when the issue is done or cancelled, or the mailbox is orphaned (404, §21.3). Non-goal 3 is reworded so that a run may resume a prior run's context.
q6 Configuration and deliverable On by default, with a runtime setting to turn it off (e.g. AGENT_SESSION_RESUME, default true, scopes organization and project, §23.3). The deliverable is an update to docs/specification.md.

Expected behaviour

  1. At the start of a local-agent run, if the setting is on and the bound adapter declares the resumable-session capability, the engine computes the session name from the issue's deterministic branch name.
  2. If session state for that name exists in the issue mailbox's persisted session storage, the CLI is started in resume mode on that session. Otherwise a new session is created under that name.
  3. If resuming fails for any reason, the run is not blocked. Possible reasons: no prior session (always the case on the first run), state lost, a CLI version that does not support resume, or the CLI refusing the resume. In all these cases the run continues in a fresh session under the same name.
  4. The session row (llm_sessions, §22.3) stores the session name and whether the run was resumed or started fresh. This lets the audit trail show what the agent "remembered".
  5. The CLI's session state survives between runs of the same issue mailbox. It stays inside the sandbox rules (§17): it is scoped to that mailbox only and is never shared across issues or projects.
  6. When the mailbox is cleaned up (done, cancelled, orphaned), its session state is removed along with the worktree.
  7. API mode, local adapters without the capability, and runs with the setting turned off behave exactly as today.

Acceptance criteria

  • A first run on an issue, bound to a local agent that supports sessions, creates a session named after the issue's branch name. The session row stores the name and resumed = false.
  • This holds for an analysis run too, before the branch exists: it uses the same deterministic name.
  • A second run on the same issue, in any phase, starts the CLI with its resume option for that name. The session row stores resumed = true.
  • If the named session cannot be found or the resume fails, the run completes normally in a new session with resumed = false. The fallback changes no status, counter or label and posts no comment.
  • A run in API mode, a run with an adapter that does not declare the capability, and a run with AGENT_SESSION_RESUME = false at organization or project scope all behave exactly as today.
  • Session state of an issue is removed when its mailbox is cleaned up (done, cancelled, 404 / orphaned).
  • Session state of one issue mailbox is never visible to runs of another mailbox.
  • docs/specification.md is updated in these places:
    • §16.2 / §16.3: adapter contract and capability.
    • §16.5: session naming, resume and fallback for local agents.
    • §17.2: persisting session state per mailbox within the sandbox.
    • §21.3: removal of session state at cleanup.
    • §22.3: llm_sessions gains the session name and resumed columns.
    • §23.3: AGENT_SESSION_RESUME setting.
    • §28.1: testing contract for resume, fallback, setting off, cleanup and isolation.
    • §29: non-goal 3 reworded.

Out of scope

  • Resuming sessions in API mode, which would need an engine-side conversation store.
  • Interactive or human-driven sessions with the CLI. Each run is still one prompt and one execution, optionally continuing a prior run's context.
  • Sharing sessions across issues, merge requests of other issues, or projects.
When we use not-ai agent like codex or claude, add a name to the session that came from the branch name and try to resume from it. <!-- agent:section:start --> ## Functional analysis Status: **ready**. All open points were settled by @SebastienDenooz on 2026-10-26, who accepted the proposed defaults for q1–q6. ### Understanding of the request Sometimes a run is executed by a **local coding-agent CLI** (Claude Code, Codex, … — §16.5) instead of a model provider API. In that case the engine names the CLI session after the issue's agent branch name (`{BRANCH_PREFIX}issue-{iid}-{slug}`, §7.3). On every later run for the same issue, it tries to resume that session. This way the agent keeps the context it built in earlier runs: analysis, plan, code it wrote, CI fixes. ### Decisions | # | Topic | Decision | |---|-------|----------| | q1 | Runtimes in scope | Every **local adapter** whose CLI supports resumable sessions (claude-code, codex, opencode, copilot, grok-build). Each adapter declares a capability for this (§16.3). API mode is excluded. | | q2 | Session name | The **deterministic branch name** computed from the issue (e.g. `agent/issue-128-add-name`). It is used from the very first run, so analysis phases use it too, whether or not the branch has been pushed yet. | | q3 | Runs that resume | **Every run of the same issue mailbox**, whatever the phase (analysis, development, ci_fix, review_fix, conflict_resolution, answer). There is one continuous memory per issue. | | q4 | Resume not possible | **Silent fallback**: a fresh session is started under the same name and the run continues. The fallback is recorded on the session row. It never fails the run, never counts as an LLM retry and never posts a comment. | | q5 | Session state lifetime | Session state is **kept per issue mailbox** between runs. It is removed together with the worktree cleanup when the issue is done or cancelled, or the mailbox is orphaned (404, §21.3). Non-goal 3 is reworded so that a run may resume a prior run's context. | | q6 | Configuration and deliverable | **On by default**, with a runtime setting to turn it off (e.g. `AGENT_SESSION_RESUME`, default `true`, scopes organization and project, §23.3). The deliverable is an update to `docs/specification.md`. | ### Expected behaviour 1. At the start of a local-agent run, if the setting is on and the bound adapter declares the resumable-session capability, the engine computes the session name from the issue's deterministic branch name. 2. If session state for that name exists in the issue mailbox's persisted session storage, the CLI is started in resume mode on that session. Otherwise a new session is created under that name. 3. If resuming fails for any reason, the run is not blocked. Possible reasons: no prior session (always the case on the first run), state lost, a CLI version that does not support resume, or the CLI refusing the resume. In all these cases the run continues in a fresh session under the same name. 4. The session row (`llm_sessions`, §22.3) stores the session name and whether the run was resumed or started fresh. This lets the audit trail show what the agent "remembered". 5. The CLI's session state survives between runs of the same issue mailbox. It stays inside the sandbox rules (§17): it is scoped to that mailbox only and is never shared across issues or projects. 6. When the mailbox is cleaned up (done, cancelled, orphaned), its session state is removed along with the worktree. 7. API mode, local adapters without the capability, and runs with the setting turned off behave exactly as today. ### Acceptance criteria - A first run on an issue, bound to a local agent that supports sessions, creates a session named after the issue's branch name. The session row stores the name and `resumed = false`. - This holds for an analysis run too, before the branch exists: it uses the same deterministic name. - A second run on the same issue, in any phase, starts the CLI with its resume option for that name. The session row stores `resumed = true`. - If the named session cannot be found or the resume fails, the run completes normally in a new session with `resumed = false`. The fallback changes no status, counter or label and posts no comment. - A run in API mode, a run with an adapter that does not declare the capability, and a run with `AGENT_SESSION_RESUME = false` at organization or project scope all behave exactly as today. - Session state of an issue is removed when its mailbox is cleaned up (done, cancelled, 404 / orphaned). - Session state of one issue mailbox is never visible to runs of another mailbox. - `docs/specification.md` is updated in these places: - §16.2 / §16.3: adapter contract and capability. - §16.5: session naming, resume and fallback for local agents. - §17.2: persisting session state per mailbox within the sandbox. - §21.3: removal of session state at cleanup. - §22.3: `llm_sessions` gains the session name and `resumed` columns. - §23.3: `AGENT_SESSION_RESUME` setting. - §28.1: testing contract for resume, fallback, setting off, cleanup and isolation. - §29: non-goal 3 reworded. ### Out of scope - Resuming sessions in API mode, which would need an engine-side conversation store. - Interactive or human-driven sessions with the CLI. Each run is still one prompt and one execution, optionally continuing a prior run's context. - Sharing sessions across issues, merge requests of other issues, or projects. <!-- agent:key=adc3e26987bb:kind=description --> <!-- agent:section:end -->
Owner

Thanks @1, I am on it. I will post my analysis here.

Thanks @1, I am on it. I will post my analysis here. <!-- agent:key=e73a356696f5:kind=acknowledgement -->
Owner

type — I could not tell the type of this issue. Please add one of these labels: task, bug, analysis, question.

  • task — a change to implement
  • bug — a defect to fix
  • analysis — an analysis to write, no code (default)
  • question — a question to answer

You can also answer every question at once in a comment of your own.

<!-- agent:questions:block=1 --> **type — I could not tell the type of this issue. Please add one of these labels: task, bug, analysis, question.** <!-- agent:q=type --> - [ ] task — a change to implement - [ ] bug — a defect to fix - [x] analysis — an analysis to write, no code *(default)* - [ ] question — a question to answer You can also answer every question at once in a comment of your own. <!-- agent:key=b24e3398c3e9:kind=questions -->
Owner

I need a few answers before going further. Reply in a comment; without an answer by 2026-10-27 00:08 UTC, I will apply the defaults.

q1 — Which runtimes are in scope? I read "not-ai agent like codex or claude" as the local agent mode (§16.5: a coding-agent CLI run inside the runner container), as opposed to API mode. Which adapters must get the named-session-and-resume behaviour?

  • claude-code and codex only — Only the two CLIs named in the issue
  • every local adapter whose CLI supports resumable sessions — claude-code, codex, opencode, copilot, grok-build — each adapter declares a capability; API mode is excluded (default)
  • all runtimes including API mode — API mode would need an engine-side conversation store

q2 — What exactly is the session name? The agent branch {BRANCH_PREFIX}issue-{iid}-{slug} (§7.3) only exists from in_development on, but analysis phases run before it. Also, two projects of one organization can produce the same branch name.

  • the deterministic branch name computed from the issue, from the first run — e.g. agent/issue-128-add-name, same value whether or not the branch has been pushed yet (default)
  • the branch name qualified by the project — e.g. {org}-{project}-agent-issue-128-add-name, avoids cross-project collisions
  • branch name only once the branch exists — analysis runs get no named session; resume starts at development

q3 — Which runs should try to resume the session? The same issue goes through several phases (analysis, development, ci_fix, review_fix, conflict_resolution, answer).

  • every run of the same issue mailbox, whatever the phase — One continuous memory per issue (default)
  • read-write phases only — development, ci_fix, review_fix, conflict_resolution; analysis runs always start fresh
  • only runs of the same phase — e.g. a ci_fix resumes the previous ci_fix only

q4 — What must happen when resuming is not possible (no session with that name yet — always the case on the first run —, session storage lost, CLI version that does not support it, or the CLI refuses the resume)?

  • start a fresh session under that name and continue — Silent fallback, recorded in the session row; the run is never failed for this reason (default)
  • start fresh and post a comment on the issue — Humans are told the agent lost its memory
  • fail the run — Counts as an LLM retry

q5 — Today the runner container is disposable with a temporary home directory (§17.2), so a CLI's session files vanish at the end of each run; resuming therefore implies keeping the agent's session state somewhere between runs of the same issue. How long should that session state live, and is persisting it acceptable given §17 and non-goal 3 ("one prompt, one execution")?

  • kept per issue mailbox until the issue is done or cancelled, then removed with the worktree cleanup — Non-goal 3 is reworded to allow resuming a prior run's context (default)
  • kept per issue mailbox but subject to RETENTION_DAYS — Purged like prompts and outputs
  • not persisted — resume only works if the CLI itself stores sessions elsewhere — Resume would effectively never succeed with the current sandbox

q6 — Should the behaviour be configurable, and what is the deliverable in this repository, which only contains docs/specification.md?

  • always on, deliverable is a specification update — Sections 16.2/16.3/16.5, 17.2, 22.3 (llm_sessions), 28.1 and non-goal 3 amended
  • on by default with a runtime setting to disable it (organization/project scope), deliverable is a specification update — e.g. AGENT_SESSION_RESUME, default true, added to §23.3 (default)
  • off by default, opt-in per binding — Only bindings that explicitly ask for it resume

You can also answer every question at once in a comment of your own.

I need a few answers before going further. Reply in a comment; without an answer by 2026-10-27 00:08 UTC, I will apply the defaults. <!-- agent:questions:block=2 --> **q1 — Which runtimes are in scope? I read "not-ai agent like codex or claude" as the **local agent** mode (§16.5: a coding-agent CLI run inside the runner container), as opposed to API mode. Which adapters must get the named-session-and-resume behaviour?** <!-- agent:q=q1 --> - [ ] claude-code and codex only — Only the two CLIs named in the issue - [x] every local adapter whose CLI supports resumable sessions — claude-code, codex, opencode, copilot, grok-build — each adapter declares a capability; API mode is excluded *(default)* - [ ] all runtimes including API mode — API mode would need an engine-side conversation store **q2 — What exactly is the session name? The agent branch `{BRANCH_PREFIX}issue-{iid}-{slug}` (§7.3) only exists from `in_development` on, but analysis phases run before it. Also, two projects of one organization can produce the same branch name.** <!-- agent:q=q2 --> - [x] the deterministic branch name computed from the issue, from the first run — e.g. `agent/issue-128-add-name`, same value whether or not the branch has been pushed yet *(default)* - [ ] the branch name qualified by the project — e.g. `{org}-{project}-agent-issue-128-add-name`, avoids cross-project collisions - [ ] branch name only once the branch exists — analysis runs get no named session; resume starts at development **q3 — Which runs should try to resume the session? The same issue goes through several phases (analysis, development, ci_fix, review_fix, conflict_resolution, answer).** <!-- agent:q=q3 --> - [x] every run of the same issue mailbox, whatever the phase — One continuous memory per issue *(default)* - [ ] read-write phases only — development, ci_fix, review_fix, conflict_resolution; analysis runs always start fresh - [ ] only runs of the same phase — e.g. a ci_fix resumes the previous ci_fix only **q4 — What must happen when resuming is not possible (no session with that name yet — always the case on the first run —, session storage lost, CLI version that does not support it, or the CLI refuses the resume)?** <!-- agent:q=q4 --> - [x] start a fresh session under that name and continue — Silent fallback, recorded in the session row; the run is never failed for this reason *(default)* - [ ] start fresh and post a comment on the issue — Humans are told the agent lost its memory - [ ] fail the run — Counts as an LLM retry **q5 — Today the runner container is disposable with a temporary home directory (§17.2), so a CLI's session files vanish at the end of each run; resuming therefore implies keeping the agent's session state somewhere between runs of the same issue. How long should that session state live, and is persisting it acceptable given §17 and non-goal 3 ("one prompt, one execution")?** <!-- agent:q=q5 --> - [x] kept per issue mailbox until the issue is done or cancelled, then removed with the worktree cleanup — Non-goal 3 is reworded to allow resuming a prior run's context *(default)* - [ ] kept per issue mailbox but subject to RETENTION_DAYS — Purged like prompts and outputs - [ ] not persisted — resume only works if the CLI itself stores sessions elsewhere — Resume would effectively never succeed with the current sandbox **q6 — Should the behaviour be configurable, and what is the deliverable in this repository, which only contains `docs/specification.md`?** <!-- agent:q=q6 --> - [ ] always on, deliverable is a specification update — Sections 16.2/16.3/16.5, 17.2, 22.3 (`llm_sessions`), 28.1 and non-goal 3 amended - [x] on by default with a runtime setting to disable it (organization/project scope), deliverable is a specification update — e.g. `AGENT_SESSION_RESUME`, default true, added to §23.3 *(default)* - [ ] off by default, opt-in per binding — Only bindings that explicitly ask for it resume You can also answer every question at once in a comment of your own. <!-- agent:key=815b04180f3e:kind=questions -->
Author
Owner

Ok with your proposition, you can go further with that.

Ok with your proposition, you can go further with that.
Owner

Plan

Implementation plan — named, resumable local-agent sessions per issue

The repository contains only docs/specification.md, so the only deliverable is an update to that file. It has no code, no test suite and no CI, so the "tests" below are the new rows of the testing contract (§28.1).

Findings from the spec that shape the design

  1. Worktrees are created per run and deleted after it. The path is {base}/.worktrees/{organization}-{project}-{target}-{run} (§3.4), and the worktree is removed in a finally block (§7.3, §13.1). Session state therefore cannot live in the worktree. It needs its own directory on the host, scoped to the mailbox.
  2. The working directory inside the container is always /workspace (§17.2). CLIs that key their sessions on the working directory (Claude Code, for example) will find their prior session even though the host worktree path changes on every run.
  3. Events on the agent MR are routed to the issue mailbox (§11.1). ci_fix, review_fix and conflict_resolution therefore share the issue's memory with no extra routing. MR mailboxes (the human-MR review phase, §8.5) are not issue mailboxes, so they behave exactly as today.
  4. Not every CLI lets the caller choose the session id. Codex, for example, assigns its own id and resumes with codex exec resume <id>. Claude Code's --session-id must be a UUID. So the "session name" is the engine's logical name. Each adapter maps it to the CLI's native session id, and the engine stores that id in the database. It is never read back from a file the container can write.
  5. Bindings can differ per phase (§16.7). For example, analysis can run on claude-code and development on codex. Session state is not portable between CLIs.
  6. The branch slug depends on the issue title, and BRANCH_PREFIX can change. A name recomputed on every run could drift.

Technical decisions taken (no human input needed)

  • The name is frozen on first use. On the first run of an issue mailbox, the engine computes {BRANCH_PREFIX}issue-{iid}-{slug} and writes it to mailboxes.branch_name immediately, even before the branch is pushed. That stored value is reused afterwards, so the session name and the later branch name are always the same, and editing the title cannot break resume. §7.3 is adjusted to say the name is fixed at the first run instead of at in_development.
  • Session state is keyed by (mailbox, adapter). A run resumes only from a prior session of the same adapter in the same mailbox. If the adapter changed, the run starts a fresh session under the same name, recorded as a fallback with reason adapter_changed. A change of provider or model with the same adapter still resumes.
  • The native id is looked up in the database. The engine takes it from the latest llm_sessions row of the mailbox with the same adapter_id and a non-null native_session_id.
  • Only the CLI's sessions subdirectory is mounted, never its whole config directory. Repository code runs in the container (§17.5) and could plant hooks or settings in ~/.claude, ~/.codex, etc. that would then run in later runs. Credentials in those directories must never be persisted either.
  • The prompt keeps carrying the full phase context (§13.3). Resume adds memory but replaces nothing, so a fresh fallback loses nothing the run strictly needs.
  • Retrying after a failed resume: when the adapter recognises the CLI's "cannot resume" failure, it re-invokes once, fresh, within the same run and the remaining phase timeout. This re-invocation is not an LLM retry. Any other failure keeps its usual Outcome and retry rules (§13.4).
  • With the setting off, nothing changes: no name, no mount, no lookup. Existing state is left as it is and is removed at cleanup as usual.

Changes to docs/specification.md

§3.4 Filesystem layout

Add the line {base}/.agent-sessions/{organization}-{project}-issue-{iid}/{adapter_id}/ session state of a local agent, per issue mailbox. Explain that this directory outlives runs, sits outside every worktree and is removed at mailbox cleanup (§21.3).

§7.3 Invariants

  • The branch name is fixed at the first run of the issue mailbox and stored in mailboxes.branch_name. The branch itself still exists only from in_development onward.
  • The finally clause removes the worktree and the container but not the mailbox's session state.

§13.1 Sequence

Add one step before "start container": resolve the session (setting, capability, name, prior native id) and mount its state directory. Add one step after the invocation: record the native id and resumed.

§16.2 Contract

  • LlmRequest gains ?SessionRef $session (null when resume is off or not supported):
    • name: the logical name, i.e. the branch name;
    • resumeId: nullable, the prior native id for this adapter;
    • stateDir: host path mounted by the sandbox.
  • LlmResult gains:
    • ?string $sessionId: the native id reported by the CLI;
    • bool $resumed;
    • ?string $resumeFallback: one of no_prior_session, adapter_changed, state_missing, unsupported_version, refused.

§16.3 Capabilities

  • Add bool $resumableSessions.
  • Add ?string $sessionStatePath: the path inside the container home that holds sessions only, never credentials or settings.
  • API mode always declares false.
  • Adapters declare true only for CLI versions verified to support non-interactive resume. Detection (§16.6) can lower it per installed version, and the agent_installations row records the result.

§16.5 Local agents

Add a "Named sessions and resume" subsection:

  • When the capability is used: the setting is on, the adapter declares the capability and the target is an issue mailbox.

  • Name derivation (frozen branch name).

  • The resume path versus the fresh path, and the silent fallback with its reasons.

  • The fallback changes no status, counter or label and posts no comment.

  • When the CLI accepts a human-readable label, the session is given the name. Otherwise the name exists only on the engine side.

  • Add a column "Resume (indicative)" to the agent table:

    • claude: --resume <id>; a fresh session is started with --session-id <uuid>; state in ~/.claude/projects/;
    • codex: codex exec resume <id>, id read from the JSON events; state in ~/.codex/sessions/;
    • opencode: --session <id>;
    • copilot: --resume <id>;
    • grok-build: only if verified.

    As with the other options, these belong to each adapter and its tests.

  • Add a trap to encode: detecting "session not found / cannot resume" from the exit code and output is per-CLI and fragile. When the cause is ambiguous, the failure is treated as a normal failure, never silently as a fallback.

§17.2 Container specification

  • Mounts row: add "plus, for a local agent with resumable sessions, the mailbox's session state directory, read-write, at the adapter's sessionStatePath, for every phase including read-only ones". It never overlaps AGENT_CREDENTIAL_MOUNTS. The "no home directory" rule still holds: only that subdirectory is mounted.
  • Lifetime row: the container is disposable, but the session state directory is not.
  • Add the isolation rule: one directory per (mailbox, adapter), never mounted for another mailbox, project or organization.

§17.5 What the sandbox does not cover

State the accepted risk: content written in one run can influence later runs of the same issue. That covers untrusted context the CLI saw and a tampered transcript planted by repository code. Persisted state is limited to the sessions subdirectory, so no config, hooks or credentials are kept.

§21.2 / §21.3 Reconciler

  • §21.3: full cleanup also removes the session state directory.
  • The same removal applies when the issue reaches done or cancelled (§7.2 rows: closed by merge, unassigned or closed without merge). blocked keeps the state, so retry resumes with memory.
  • §21.2: the "Orphaned worktrees and containers" sweep also removes session directories with no open mailbox.
  • §11.7: a reclaimed lease keeps the session state. A half-written state falls back to a fresh session.

§22.3 llm_sessions

Add a row: session_name nullable, native_session_id nullable, resumed bool (default false), resume_fallback nullable. Rows stay null or false for API mode, MR mailboxes and runs with the setting off.

§23.3 Runtime settings

Add AGENT_SESSION_RESUME | true | organization, project.

§24.3 / §24.5 Security

  • §24.3 threats table: add "a run planting instructions or config for later runs" → only the sessions subdirectory is persisted, scoped per mailbox and removed at cleanup.
  • §24.5: session state is not subject to RETENTION_DAYS. It lives until mailbox cleanup (decision q5). The llm_sessions metadata follows the usual retention.

§25.2 Run traceability

The session detail shows the name, the native id and resumed or fallback with its reason.

§28.1 Testing contract

Add the rows below. §28.2: the fake LLM adapter and the fake container runtime support sessions, so mounts and arguments can be asserted.

Area What must be proven
Session naming The first run of an issue (analysis included, before the branch exists) uses the frozen branch name, stores it and records resumed = false
Session resume A second run in any phase on the same adapter passes the prior native id and records resumed = true; an agent MR's ci_fix resumes the issue's session
Resume fallback Missing state, refused resume, unsupported version or changed adapter → the run completes fresh with resumed = false and a fallback reason; no status, counter, label or comment changes; no LLM retry counted
Resume off API mode, an adapter without the capability, an MR mailbox, and AGENT_SESSION_RESUME = false at organization or project scope produce exactly today's request and mounts
Session isolation The container mounts only its own mailbox and adapter directory, never config or credential paths, and never another mailbox's directory
Session cleanup Done, cancelled, 404 and orphan sweep remove the directory; blocked and lease reclaim keep it
Name stability Editing the issue title after the first run changes neither the session name nor the branch

§29 Non-goals

Reword item 3 to: "No interactive session with the LLM: one prompt, one execution per run. A local agent may resume the context of earlier runs of the same issue (§16.5)."

§30 Appendix A

  • §30.1: add the "Session state" mapping: host directory, plus a Process mount option.
  • §30.2: CleanupWorktreesAndContainers also removes orphaned session directories.
  • §30.3: add the point that the native id comes from the database, never from the mounted directory.

Risks

  • Persistence of prompt injection within an issue. Mitigated by mounting only the sessions subdirectory and by per-mailbox scoping; documented as accepted in §17.5.
  • Fragile resume detection for each CLI and version. Mitigated by capability gating, detection and conservative classification.
  • Disk growth of long-lived issues, especially while blocked. They are monitored through cleanup sweeps; a size cap is deliberately not added yet (YAGNI).
  • Context growth over many runs. The CLIs compact on their own, and the full per-phase prompt is always sent.
  • Consistency of the spec. All cross-references must be updated together, especially §7.3 and §17.2's "no home directory".

Validation

This repository has no code or CI, so validation is a careful review of the spec. Check that every cross-reference resolves (§3.4, §7.3, §11.7, §13.1, §16.2–16.6, §17.2, §17.5, §21, §22.3, §23.3, §24, §25.2, §28, §29, §30), that tables keep valid Markdown, and that the Mermaid diagrams still render. Creating CI pipelines (GitLab, Forgejo/GitHub) or Playwright is out of scope here, since there is nothing executable to test.

The implementation plan is ready for validation. Add the ai::validate_plan label to let me start.

## Plan # Implementation plan — named, resumable local-agent sessions per issue The repository contains only `docs/specification.md`, so the only deliverable is an update to that file. It has no code, no test suite and no CI, so the "tests" below are the new rows of the testing contract (§28.1). ## Findings from the spec that shape the design 1. **Worktrees are created per run and deleted after it.** The path is `{base}/.worktrees/{organization}-{project}-{target}-{run}` (§3.4), and the worktree is removed in a `finally` block (§7.3, §13.1). Session state therefore **cannot live in the worktree**. It needs its own directory on the host, scoped to the mailbox. 2. **The working directory inside the container is always `/workspace`** (§17.2). CLIs that key their sessions on the working directory (Claude Code, for example) will find their prior session even though the host worktree path changes on every run. 3. **Events on the agent MR are routed to the issue mailbox** (§11.1). ci_fix, review_fix and conflict_resolution therefore share the issue's memory with no extra routing. MR mailboxes (the human-MR `review` phase, §8.5) are not issue mailboxes, so they behave exactly as today. 4. **Not every CLI lets the caller choose the session id.** Codex, for example, assigns its own id and resumes with `codex exec resume <id>`. Claude Code's `--session-id` must be a UUID. So the "session name" is the engine's logical name. Each adapter maps it to the CLI's **native session id**, and the engine stores that id in the database. It is never read back from a file the container can write. 5. **Bindings can differ per phase** (§16.7). For example, analysis can run on claude-code and development on codex. Session state is not portable between CLIs. 6. **The branch slug depends on the issue title**, and `BRANCH_PREFIX` can change. A name recomputed on every run could drift. ## Technical decisions taken (no human input needed) - **The name is frozen on first use.** On the first run of an issue mailbox, the engine computes `{BRANCH_PREFIX}issue-{iid}-{slug}` and writes it to `mailboxes.branch_name` immediately, even before the branch is pushed. That stored value is reused afterwards, so the session name and the later branch name are always the same, and editing the title cannot break resume. §7.3 is adjusted to say the name is fixed at the first run instead of at `in_development`. - **Session state is keyed by (mailbox, adapter).** A run resumes only from a prior session of the **same adapter** in the same mailbox. If the adapter changed, the run starts a fresh session under the same name, recorded as a fallback with reason `adapter_changed`. A change of provider or model with the same adapter still resumes. - **The native id is looked up in the database.** The engine takes it from the latest `llm_sessions` row of the mailbox with the same `adapter_id` and a non-null `native_session_id`. - **Only the CLI's sessions subdirectory is mounted, never its whole config directory.** Repository code runs in the container (§17.5) and could plant hooks or settings in `~/.claude`, `~/.codex`, etc. that would then run in later runs. Credentials in those directories must never be persisted either. - **The prompt keeps carrying the full phase context (§13.3).** Resume adds memory but replaces nothing, so a fresh fallback loses nothing the run strictly needs. - **Retrying after a failed resume:** when the adapter recognises the CLI's "cannot resume" failure, it re-invokes once, fresh, within the same run and the remaining phase timeout. This re-invocation is not an LLM retry. Any other failure keeps its usual `Outcome` and retry rules (§13.4). - **With the setting off, nothing changes:** no name, no mount, no lookup. Existing state is left as it is and is removed at cleanup as usual. ## Changes to `docs/specification.md` ### §3.4 Filesystem layout Add the line `{base}/.agent-sessions/{organization}-{project}-issue-{iid}/{adapter_id}/ session state of a local agent, per issue mailbox`. Explain that this directory outlives runs, sits outside every worktree and is removed at mailbox cleanup (§21.3). ### §7.3 Invariants - The branch name is fixed at the first run of the issue mailbox and stored in `mailboxes.branch_name`. The branch itself still exists only from `in_development` onward. - The `finally` clause removes the worktree and the container **but not** the mailbox's session state. ### §13.1 Sequence Add one step before "start container": resolve the session (setting, capability, name, prior native id) and mount its state directory. Add one step after the invocation: record the native id and `resumed`. ### §16.2 Contract - `LlmRequest` gains `?SessionRef $session` (null when resume is off or not supported): - `name`: the logical name, i.e. the branch name; - `resumeId`: nullable, the prior native id for this adapter; - `stateDir`: host path mounted by the sandbox. - `LlmResult` gains: - `?string $sessionId`: the native id reported by the CLI; - `bool $resumed`; - `?string $resumeFallback`: one of `no_prior_session`, `adapter_changed`, `state_missing`, `unsupported_version`, `refused`. ### §16.3 Capabilities - Add `bool $resumableSessions`. - Add `?string $sessionStatePath`: the path inside the container home that holds sessions only, never credentials or settings. - API mode always declares `false`. - Adapters declare `true` only for CLI versions verified to support non-interactive resume. Detection (§16.6) can lower it per installed version, and the `agent_installations` row records the result. ### §16.5 Local agents Add a "Named sessions and resume" subsection: - When the capability is used: the setting is on, the adapter declares the capability and the target is an issue mailbox. - Name derivation (frozen branch name). - The resume path versus the fresh path, and the silent fallback with its reasons. - The fallback changes no status, counter or label and posts no comment. - When the CLI accepts a human-readable label, the session is given the name. Otherwise the name exists only on the engine side. - Add a column "Resume (indicative)" to the agent table: - claude: `--resume <id>`; a fresh session is started with `--session-id <uuid>`; state in `~/.claude/projects/`; - codex: `codex exec resume <id>`, id read from the JSON events; state in `~/.codex/sessions/`; - opencode: `--session <id>`; - copilot: `--resume <id>`; - grok-build: only if verified. As with the other options, these belong to each adapter and its tests. - Add a trap to encode: detecting "session not found / cannot resume" from the exit code and output is per-CLI and fragile. When the cause is ambiguous, the failure is treated as a normal failure, never silently as a fallback. ### §17.2 Container specification - **Mounts row:** add "plus, for a local agent with resumable sessions, the mailbox's session state directory, read-write, at the adapter's `sessionStatePath`, for every phase including read-only ones". It never overlaps `AGENT_CREDENTIAL_MOUNTS`. The "no home directory" rule still holds: only that subdirectory is mounted. - **Lifetime row:** the container is disposable, but the session state directory is not. - Add the isolation rule: one directory per (mailbox, adapter), never mounted for another mailbox, project or organization. ### §17.5 What the sandbox does not cover State the accepted risk: content written in one run can influence later runs **of the same issue**. That covers untrusted context the CLI saw and a tampered transcript planted by repository code. Persisted state is limited to the sessions subdirectory, so no config, hooks or credentials are kept. ### §21.2 / §21.3 Reconciler - §21.3: full cleanup also removes the session state directory. - The same removal applies when the issue reaches `done` or cancelled (§7.2 rows: closed by merge, unassigned or closed without merge). `blocked` keeps the state, so `retry` resumes with memory. - §21.2: the "Orphaned worktrees and containers" sweep also removes session directories with no open mailbox. - §11.7: a reclaimed lease keeps the session state. A half-written state falls back to a fresh session. ### §22.3 `llm_sessions` Add a row: `session_name` nullable, `native_session_id` nullable, `resumed` bool (default false), `resume_fallback` nullable. Rows stay null or false for API mode, MR mailboxes and runs with the setting off. ### §23.3 Runtime settings Add `AGENT_SESSION_RESUME | true | organization, project`. ### §24.3 / §24.5 Security - §24.3 threats table: add "a run planting instructions or config for later runs" → only the sessions subdirectory is persisted, scoped per mailbox and removed at cleanup. - §24.5: session state is not subject to `RETENTION_DAYS`. It lives until mailbox cleanup (decision q5). The `llm_sessions` metadata follows the usual retention. ### §25.2 Run traceability The session detail shows the name, the native id and resumed or fallback with its reason. ### §28.1 Testing contract Add the rows below. §28.2: the fake LLM adapter and the fake container runtime support sessions, so mounts and arguments can be asserted. | Area | What must be proven | |---|---| | Session naming | The first run of an issue (analysis included, before the branch exists) uses the frozen branch name, stores it and records `resumed = false` | | Session resume | A second run in any phase on the same adapter passes the prior native id and records `resumed = true`; an agent MR's ci_fix resumes the issue's session | | Resume fallback | Missing state, refused resume, unsupported version or changed adapter → the run completes fresh with `resumed = false` and a fallback reason; no status, counter, label or comment changes; no LLM retry counted | | Resume off | API mode, an adapter without the capability, an MR mailbox, and `AGENT_SESSION_RESUME = false` at organization or project scope produce exactly today's request and mounts | | Session isolation | The container mounts only its own mailbox and adapter directory, never config or credential paths, and never another mailbox's directory | | Session cleanup | Done, cancelled, 404 and orphan sweep remove the directory; blocked and lease reclaim keep it | | Name stability | Editing the issue title after the first run changes neither the session name nor the branch | ### §29 Non-goals Reword item 3 to: "No interactive session with the LLM: one prompt, one execution per run. A local agent may resume the context of earlier runs of the same issue (§16.5)." ### §30 Appendix A - §30.1: add the "Session state" mapping: host directory, plus a `Process` mount option. - §30.2: `CleanupWorktreesAndContainers` also removes orphaned session directories. - §30.3: add the point that the native id comes from the database, never from the mounted directory. ## Risks - **Persistence of prompt injection** within an issue. Mitigated by mounting only the sessions subdirectory and by per-mailbox scoping; documented as accepted in §17.5. - **Fragile resume detection** for each CLI and version. Mitigated by capability gating, detection and conservative classification. - **Disk growth** of long-lived issues, especially while blocked. They are monitored through cleanup sweeps; a size cap is deliberately not added yet (YAGNI). - **Context growth** over many runs. The CLIs compact on their own, and the full per-phase prompt is always sent. - **Consistency of the spec.** All cross-references must be updated together, especially §7.3 and §17.2's "no home directory". ## Validation This repository has no code or CI, so validation is a careful review of the spec. Check that every cross-reference resolves (§3.4, §7.3, §11.7, §13.1, §16.2–16.6, §17.2, §17.5, §21, §22.3, §23.3, §24, §25.2, §28, §29, §30), that tables keep valid Markdown, and that the Mermaid diagrams still render. Creating CI pipelines (GitLab, Forgejo/GitHub) or Playwright is out of scope here, since there is nothing executable to test. The implementation plan is ready for validation. Add the ai::validate_plan label to let me start. <!-- agent:key=8d0686b101cc:kind=plan -->
Owner

Plan validated by @SebastienDenooz. Starting the development.

Plan validated by @SebastienDenooz. Starting the development. <!-- agent:key=143711b6775e:kind=plan_validated -->
Owner

No pipeline ran on this merge request. It is ready for review.

No pipeline ran on this merge request. It is ready for review. <!-- agent:key=0a2fd63bc0d8:kind=no_ci_ran -->
Alfred removed their assignment 2026-10-26 09:44:36 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Alchemic.Studio/Alfred#1
No description provided.