Sessions store + resume — disk-backed map and delta-prompt flow #6

Closed
opened 2026-04-16 22:44:42 +00:00 by claude-desktop · 0 comments
Collaborator

User story

As an agent re-dispatched on the same issue or PR, I want to resume my previous Claude Agent SDK session so that I retain the context I built up before, and the dispatcher only needs to send a delta prompt instead of replaying the whole skill template.

Note (2026-04-17): PR #16 landed sessionKey() and a dropSession() stub in src/main.ts. This story replaces the stub with the real disk-backed implementation and moves both symbols into src/sessions.ts; the /reset endpoint should continue to work unchanged.

Acceptance criteria

Sessions store (src/sessions.ts)

  • sessionKey(agent, repo, issue_or_pr_number): string — returns e.g. "boss:charles/peon:4" (moved from main.ts)
  • getSession(key): Promise<string | null> — returns the persisted session id, if any
  • setSession(key, id): Promise<void> — writes through to disk
  • dropSession(key): Promise<boolean> — replaces the main.ts stub; returns true when an entry was removed
  • dropAllForIssue(repo, issue_or_pr_number): Promise<void> — drops every agent's session for that issue
  • Backing store: ~/.local/state/claude-hooks/sessions.json, atomic writes via tmp + rename
  • Concurrent writes from multiple workers are safe (mutex or last-writer-wins-with-merge — choose and document)

runAgent integration (src/main.ts)

  • Compute sessionKey at task start (swap the import from ./main to ./sessions)
  • If getSession(key) returns an id, pass resume: id to query(); otherwise fresh session
  • Capture the session id from the first SDK message that carries it (path identified in the spike); call setSession(key, id)
  • If query() throws because the resumed session expired or is unknown, automatically retry once with a fresh session and log a warning
  • Cleanup webhooks (cleanup.ts) call dropAllForIssue
  • /reset endpoint in main.ts swaps its stub call for dropSession from ./sessions

Skill prompt: delta vs. full

  • When getSession returns null → use the full skill template as today
  • When resuming → send a short delta prompt (e.g. "The reviewer requested changes: <body>. Address them."); do not re-send the full template
  • Delta prompt content lives in the existing skills/ directory as a separate file per event type (or as an explicit branch in the skill template); pick one and document

Tests (src/sessions.test.ts)

  • Round-trip: set, get, drop
  • Atomic write: kill-mid-write simulation (or just verify temp+rename pattern) does not leave a partial file
  • Concurrent set from two workers: both writes land, no data loss
  • /reset tests in main.test.ts are updated to assert against the real dropSession (drop the "always false" stub expectation)

Out of scope

  • Sweeper for missed cleanup webhooks (#7)
  • Dashboard surfacing of session counts (later story)
  • Skill prompt content quality tuning (separate follow-up — this story only enforces the delta-vs-full split)

References

  • Discussion: chat history, "Phase 2 — session resumption"
  • Spike output (issue #2) — confirms which SDK message carries session_id
  • main.ts stubs added in PR #16 (commit c882b6a)

Dependencies

  • Blocked by: none open (#5 merged in PR #14)
  • Blocks: #7
  • Branch off: main
  • Full graph: #10
## User story As an **agent re-dispatched on the same issue or PR**, I want to resume my previous Claude Agent SDK session so that I retain the context I built up before, and the dispatcher only needs to send a delta prompt instead of replaying the whole skill template. > **Note (2026-04-17):** PR #16 landed `sessionKey()` and a `dropSession()` stub in `src/main.ts`. This story replaces the stub with the real disk-backed implementation and moves both symbols into `src/sessions.ts`; the `/reset` endpoint should continue to work unchanged. ## Acceptance criteria ### Sessions store (`src/sessions.ts`) - [ ] `sessionKey(agent, repo, issue_or_pr_number): string` — returns e.g. `"boss:charles/peon:4"` (moved from `main.ts`) - [ ] `getSession(key): Promise<string | null>` — returns the persisted session id, if any - [ ] `setSession(key, id): Promise<void>` — writes through to disk - [ ] `dropSession(key): Promise<boolean>` — replaces the main.ts stub; returns `true` when an entry was removed - [ ] `dropAllForIssue(repo, issue_or_pr_number): Promise<void>` — drops every agent's session for that issue - [ ] Backing store: `~/.local/state/claude-hooks/sessions.json`, atomic writes via `tmp + rename` - [ ] Concurrent writes from multiple workers are safe (mutex or last-writer-wins-with-merge — choose and document) ### `runAgent` integration (`src/main.ts`) - [ ] Compute `sessionKey` at task start (swap the import from `./main` to `./sessions`) - [ ] If `getSession(key)` returns an id, pass `resume: id` to `query()`; otherwise fresh session - [ ] Capture the session id from the first SDK message that carries it (path identified in the spike); call `setSession(key, id)` - [ ] If `query()` throws because the resumed session expired or is unknown, automatically retry once with a fresh session and log a warning - [ ] Cleanup webhooks (`cleanup.ts`) call `dropAllForIssue` - [ ] `/reset` endpoint in `main.ts` swaps its stub call for `dropSession` from `./sessions` ### Skill prompt: delta vs. full - [ ] When `getSession` returns null → use the full skill template as today - [ ] When resuming → send a short delta prompt (e.g. `"The reviewer requested changes: <body>. Address them."`); do not re-send the full template - [ ] Delta prompt content lives in the existing `skills/` directory as a separate file per event type (or as an explicit branch in the skill template); pick one and document ### Tests (`src/sessions.test.ts`) - [ ] Round-trip: `set`, `get`, `drop` - [ ] Atomic write: kill-mid-write simulation (or just verify temp+rename pattern) does not leave a partial file - [ ] Concurrent set from two workers: both writes land, no data loss - [ ] `/reset` tests in `main.test.ts` are updated to assert against the real `dropSession` (drop the "always false" stub expectation) ## Out of scope - Sweeper for missed cleanup webhooks (#7) - Dashboard surfacing of session counts (later story) - Skill prompt content quality tuning (separate follow-up — this story only enforces the delta-vs-full split) ## References - Discussion: chat history, "Phase 2 — session resumption" - Spike output (issue #2) — confirms which SDK message carries `session_id` - `main.ts` stubs added in PR #16 (commit `c882b6a`) ## Dependencies - **Blocked by:** none open (#5 merged in PR #14) - **Blocks:** #7 - **Branch off:** `main` - **Full graph:** #10
Sign in to join this conversation.
No project
No assignees
1 participant
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
charles/claude-hooks#6
No description provided.