From 3d1e501df6e440640bb0a9c71acb80c440eff633 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:11:30 -0400 Subject: [PATCH 1/5] fix(session-flow): carry the user's original goal across the handoff chain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A handoff chain preserved state perfectly and intent not at all. The goal appeared in exactly one place — a line inside the six-line Resumption brief, a section whose own contract is to restate facts owned below — so every hop re-derived it from a conversation that had already lost it, and what survived was the phase, the bundle, and the checklist in front of the writer. Each resumed session then optimized the wrong objective faithfully, with nothing on any resume path testing the work against what it was for. reference/structure.md now opens with body section 1, Original goal: the user's statement quoted verbatim with its date, an Amended: line changeable only on an explicit dated statement from whoever set the goal, and a drift-check line tying the first remaining action back to the goal. Immutability is enforced as a step rather than an adjective — whenever previous_handoff is emitted, the write procedure opens that file from disk THIS turn and reproduces its quote and amendments unchanged. Completion criteria now require both halves, the goal-state a criterion establishes and the command or diff that settles it, with process milestones demoted to a subordinate sub-heading. Sections renumbered 1-14. The alignment check on resume lives in three complementary homes, none of them sufficient alone. The resume-prompt directive carries it on every resume, including the dominant bare paste that invokes no skill at all. keep-going owns the interrupted path, running goal alignment before it restates position. reanchor covers the deliberate "is this still current" pass as a fifth premise check, and because it reads a chain it is the only one that can compare the quotes across links and report a re-derived goal as drift; it reports and hands to keep-going, never amending a goal or re-deriving the next action. handoff's post-write checklist, its gotchas index, and eval cases across all three skills carry the same rules, so a writer is checked against them. Co-authored-by: Claude Opus 5 (1M context) Co-authored-by: Claude Fable 5 --- .../session-flow/.claude-plugin/plugin.json | 4 +- plugins/session-flow/CHANGELOG.md | 66 ++++++++++ plugins/session-flow/reference/save-point.md | 29 ++++- plugins/session-flow/reference/structure.md | 119 +++++++++++++----- plugins/session-flow/skills/handoff/SKILL.md | 11 +- .../skills/handoff/context/gotchas.md | 8 ++ .../skills/handoff/evals/evals.json | 28 +++++ .../session-flow/skills/keep-going/SKILL.md | 26 ++-- .../skills/keep-going/evals/evals.json | 26 ++++ plugins/session-flow/skills/reanchor/SKILL.md | 33 ++++- .../skills/reanchor/evals/evals.json | 27 ++++ 11 files changed, 333 insertions(+), 44 deletions(-) diff --git a/plugins/session-flow/.claude-plugin/plugin.json b/plugins/session-flow/.claude-plugin/plugin.json index 7c05bfcc5..bfdbff07b 100644 --- a/plugins/session-flow/.claude-plugin/plugin.json +++ b/plugins/session-flow/.claude-plugin/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "session-flow", - "version": "0.17.22", - "description": "Session-lifecycle toolkit of thirteen skills: workflow (navigate a staged dev workflow and suggest the next stage), handoff (write a save-point and resume prompt for /clear-and-resume), continue-in-background (delegate the task to a fresh background agent that continues it now — same save-point engine as handoff, delivered by launching a detached claude --bg session seeded with the resume prompt; launches only on explicit user request), keep-going (recover and continue after any interruption OR when live off-thread work looks stalled — inventory off-thread work, inspect its real output, act only on evidence, then continue; after a usage limit lifts it continues rather than summarizing-and-stalling), find-handoff (recover a lost handoff after /clear — when the resume prompt was written but never copied — via a read-only detection ladder: known-location glob of the handoffs dir, then a bounded, recency-ranked transcript scan for the handoff directive and dashed-rail markers, then a confirm-before-resume gate; surfaces only the resume prompt + metadata, never raw transcript content), clean-stop (get to a durable, linked stopping point before the machine may go away — sweep every repo/worktree for uncommitted, unpushed, or PR-less work, push it durable, put breadcrumbs in PR/issue bodies, then give a free-and-clear verdict), retro (structured end-of-session retrospective with transcript metrics and learning codification), running-retro (in-flight retrospective checkpoints that spawn a subagent to analyze the transcript so far and append classified findings to a cumulative running ledger — capture and route only, the live counterpart to retro; also owns a detached-observer substrate that can watch a session out-of-band and run the checkpoint autonomously after the session ends), orient (read-only session orientation — synthesize where we stand, what we are doing, and why, from durable + off-thread state the built-in /recap never sees: ledgers, handoffs, workflow checklists, running-retro ledgers, open PRs and work-items, and git), orchestrate (arm a session or worker with proactive-orchestration imperatives), reanchor (verify a session's working assumptions are still true against live reality — referenced PRs/issues/branches, base-branch drift, renamed/version-drifted surfaces, stale memory-tier files — before building on them), reconcile (retire finished off-thread work and reconcile this session's task ledger with reality — the prune-and-reconcile counterpart to keep-going's resume: inventory the work this session spawned, inspect its real state, retire the finished and close proven-done tasks, auto-settling the finished and gating any kill of still-running work; sibling sessions in the project are reported read-only), and setup (check-centric verification of the observer's runtime prerequisites and configuration).", + "version": "0.17.23", + "description": "Session-lifecycle toolkit of thirteen skills: workflow (navigate a staged dev workflow and suggest the next stage), handoff (write a save-point and resume prompt for /clear-and-resume), continue-in-background (delegate the task to a fresh background agent that continues it now — same save-point engine as handoff, delivered by launching a detached claude --bg session seeded with the resume prompt; launches only on explicit user request), keep-going (recover and continue after any interruption OR when live off-thread work looks stalled — inventory off-thread work, inspect its real output, act only on evidence, then continue; after a usage limit lifts it continues rather than summarizing-and-stalling), find-handoff (recover a lost handoff after /clear — when the resume prompt was written but never copied — via a read-only detection ladder: known-location glob of the handoffs dir, then a bounded, recency-ranked transcript scan for the handoff directive and dashed-rail markers, then a confirm-before-resume gate; surfaces only the resume prompt + metadata, never raw transcript content), clean-stop (get to a durable, linked stopping point before the machine may go away — sweep every repo/worktree for uncommitted, unpushed, or PR-less work, push it durable, put breadcrumbs in PR/issue bodies, then give a free-and-clear verdict), retro (structured end-of-session retrospective with transcript metrics and learning codification), running-retro (in-flight retrospective checkpoints that spawn a subagent to analyze the transcript so far and append classified findings to a cumulative running ledger — capture and route only, the live counterpart to retro; also owns a detached-observer substrate that can watch a session out-of-band and run the checkpoint autonomously after the session ends), orient (read-only session orientation — synthesize where we stand, what we are doing, and why, from durable + off-thread state the built-in /recap never sees: ledgers, handoffs, workflow checklists, running-retro ledgers, open PRs and work-items, and git), orchestrate (arm a session or worker with proactive-orchestration imperatives), reanchor (verify a session's working assumptions are still true against live reality — referenced PRs/issues/branches, base-branch drift, renamed/version-drifted surfaces, stale memory-tier files, and the goal a handoff records, compared across the chain so a re-derived goal reports as drift — before building on them), reconcile (retire finished off-thread work and reconcile this session's task ledger with reality — the prune-and-reconcile counterpart to keep-going's resume: inventory the work this session spawned, inspect its real state, retire the finished and close proven-done tasks, auto-settling the finished and gating any kill of still-running work; sibling sessions in the project are reported read-only), and setup (check-centric verification of the observer's runtime prerequisites and configuration).", "author": { "name": "Melodic Software", "email": "info@melodicsoftware.com" diff --git a/plugins/session-flow/CHANGELOG.md b/plugins/session-flow/CHANGELOG.md index a5fe813c0..53669acd6 100644 --- a/plugins/session-flow/CHANGELOG.md +++ b/plugins/session-flow/CHANGELOG.md @@ -1,5 +1,71 @@ # Changelog — session-flow plugin +## [0.17.23] + +### Fixed + +- **A handoff chain preserved state perfectly and intent not at all: nothing made the user's own + goal a mandatory, immutable field, so each save-point serialized the process machinery as the + mission.** The goal appeared in exactly one place — a line inside the six-line `Resumption brief`, + a section whose own contract is to restate facts owned below. So every hop re-derived the goal + from a conversation that had already lost it, each paraphrase individually plausible, and what + survived was the phase, the bundle, and the checklist in front of the writer. `Completion + criteria` compounded it: the section demanded observability and said nothing about framing, so + criteria stated as process steps passed — and a process criterion is satisfiable while the goal is + no closer, reporting done when the process finished rather than when the work landed. Each resumed + session then optimized the wrong objective faithfully, with nothing on any resume path testing the + work against what it was for. + + `reference/structure.md` now opens with body section 1, **`Original goal`** — the user's statement + quoted verbatim with its date, never paraphrased; `Amended:` defaulting to `None.` and changeable + only on an explicit dated statement from whoever set the goal, prior goal retained above it; and a + drift-check line, `Next action serves it by:`, that ties the first remaining action back to the + goal and, when it cannot be written, says so as drift rather than staying silent. Immutability is + enforced as a step, not an adjective: whenever `previous_handoff` is emitted, the write procedure + opens that file from disk THIS turn and reproduces its quote and amendments unchanged — the same + did-the-read check the live `TaskList` call already carries. The `Resumption brief` stops + restating the goal and points at §1. `Completion criteria` now requires both halves — the + goal-state a criterion establishes AND the command or diff that settles it — with process + milestones demoted to a subordinate `Process milestones` sub-heading, since goal-framed + criteria are the harder ones to settle mechanically, which is exactly why writers drifted to + process framing. Sections renumbered 1-14; the doc's internal cross-references moved with them. + `skills/handoff`'s post-write checklist gains the matching assertions on both paths — the quote + copied off disk rather than rebuilt, the drift-check answered, criteria goal-framed, and the + verbatim goal line present on prompt-only — because a rule the writer is never checked against is + the rule it drifts from; `context/gotchas.md` carries the failure pattern, and the skill's eval + set covers both paths. + +- **Nothing re-anchored a resumed session to its goal, so the drift ran unnoticed across many + sessions.** The check now sits on all three surfaces a resume can cross. + `reference/save-point.md` gains an `Original goal — mandatory on BOTH paths` rule (prompt-only + writes no body sections, so it carries the verbatim goal line inline between the rails, above its + remaining-work bullets — it points at no file, and a prompt-only save-point listing just the + follow-ups is the precise shape that loses the goal), and the rails directive becomes `Read @…, + confirm its Original goal still governs the remaining next steps, then continue them.` — the + directive because it is the one artifact every resume passes through, including the dominant bare + paste that invokes no skill at all, the agent `continue-in-background` launches, and a + `find-handoff` recovery. Not a detection-contract change: signal 1 is matched on the + `…handoffs/-handoff-…` shape, which the clause leaves untouched. It does carry one structural + consequence, recorded where `structure.md` describes how that doc is cited elsewhere: the + directive now names `Original goal` by name (never by number), so renaming that one section + ripples out to it, where before no rename ripple existed. `skills/keep-going` owns the + skill-mediated path — its "Reconcile the main thread" step now runs goal alignment FIRST, before + restating position: read the handoff's `Original goal`, say in one sentence how the next action + serves it, and treat an unstatable connection as drift rather than a wording problem. A handoff + carrying no `Original goal` is itself a flagged defect — the goal is never inferred from the + process the file describes, since that process is the thing that drifted; the user is asked for it + in their own words first. `skills/reanchor` covers the third path — the deliberate "is this still + current" pass over an old plan, where neither of the other two ever runs — as a fifth premise + check beside its PR, base-drift, surface-rename, and stale-memory ones. That framing is the point: + a recorded goal is a documented claim about what the work is FOR, and it goes stale exactly the + way a PR's state does, so it sits inside reanchor's existing boundary rather than stretching it + toward intent. Because reanchor reads a chain, it can do what no single-document check can — open + the prior handoff and compare the quotes across links, reporting a re-derived goal as drift + between them. It reports and hands to `keep-going`; it never re-derives the next action or amends + a goal. None of the three is sufficient alone: reanchor is opt-in and fires only once staleness is + already suspected, which is precisely when a drifted chain looks healthiest. Both skills carry + eval cases for the new check, including the absent-goal case each must refuse to infer past. + ## [0.17.22] ### Fixed diff --git a/plugins/session-flow/reference/save-point.md b/plugins/session-flow/reference/save-point.md index 898505033..7a4c92c1a 100644 --- a/plugins/session-flow/reference/save-point.md +++ b/plugins/session-flow/reference/save-point.md @@ -98,6 +98,20 @@ remaining-work bullet carries the inherited status; a bullet that folds in an in "blocked" without `UNVERIFIED ()` reproduces the exact failure this rule exists to prevent, with no file left behind for a later review to catch it in. +## Original goal — mandatory on BOTH paths + +The goal in the user's own words travels with every save-point, and a chain of them carries it +forward unchanged. A save-point serializes the machinery in front of it effortlessly — the phase, +the checklist, the bundle — and hands the resuming session a mission made of process, which that +session then optimizes faithfully. State is what a save-point preserves for free; intent is what it +drops in silence, and no amount of detail elsewhere replaces it. + +On the full path this is body section 1, `Original goal` ([`structure.md`](structure.md)) — which +also owns the immutability rule and the disk-read copy step a successor handoff runs. +**Prompt-only writes no body sections, so it carries the verbatim goal line inline between the +rails**, above its remaining-work bullets. It has no file to point at, and a prompt-only save-point +listing only the follow-ups is the exact shape that loses the goal. + ## Writing the handoff file (full path) The body sections, the TaskList reconstitute format, and the frontmatter shape (including the @@ -218,7 +232,7 @@ display): `/clear`, then copy everything between the dashed lines: ────────────────────────────────────────────────────────── -Read @/-handoff-.md and continue its remaining next steps. +Read @/-handoff-.md, confirm its Original goal still governs the remaining next steps, then continue them. Prior session: . Handoff origin: , relative path /handoffs/-handoff-.md. ────────────────────────────────────────────────────────── @@ -291,10 +305,21 @@ leaving the token in or mangling the identity — the directory name loses neith nearly as well. When the next stage is a specific skill in the consuming repo, swap the directive to -`Read @… and execute /.` The `@`-reference is mandatory on the full path — the fresh session +`Read @…, confirm its Original goal still governs the remaining next steps, then execute /.` +The `@`-reference is mandatory on the full path — the fresh session loads it; do NOT inline the file's detail in the prompt. Prompt-only carries its remaining-work bullets inline between the rails instead, and needs no origin line: it references no file. +**The alignment clause rides in the directive because the directive is the one thing every resume +path passes through.** The dominant resume is a paste into a fresh session that invokes no skill at +all, so a check living only in a skill fires only when someone happens to call it — which is how a +chain of save-points can run for many sessions with nothing ever testing the work against its goal. +`/session-flow:keep-going` owns the same check on the skill-mediated path (its "Reconcile the main +thread" step); this covers the bare paste, the background agent +`/session-flow:continue-in-background` launches, and a `find-handoff` recovery alike. It is not a +detection-contract change: signal 1 below is matched on the `…handoffs/-handoff-…` shape the +directive names, which the added clause leaves untouched. + `` = this session's `$CLAUDE_CODE_SESSION_ID` (the frontmatter `session_id`) — it lets a fresh session or `/retro` chain-walker locate the transcript later. diff --git a/plugins/session-flow/reference/structure.md b/plugins/session-flow/reference/structure.md index 014df4b80..cf9cfefcb 100644 --- a/plugins/session-flow/reference/structure.md +++ b/plugins/session-flow/reference/structure.md @@ -9,34 +9,39 @@ handoffs cost the next session a re-investigation, which is the cost this docume ## Body sections -Ordered so the cheapest useful layer comes first. A reader can stop after **Resumption brief** and -still take the correct next action; everything below it is there for the reader who needs more. +Ordered so the cheapest useful layer comes first. A reader can stop after **Original goal** plus +**Resumption brief** and still take the correct next action; everything below is there for the +reader who needs more. | Order | Section | Owns | |---|---|---| -| 1 | Resumption brief | the resumption decision | -| 2 | Completion criteria | what "done" means, observably | -| 3 | Constraints that must hold | invariants whose violation breaks the work | -| 4 | Environment to re-establish | machine and session state `/clear` destroyed | -| 5 | Side effects already applied | persistent effects that must NOT be repeated | -| 6 | File roles in this work | which file plays which role, and how far its change got | -| 7 | Decisions already settled | closed choices, with the reasoning that closed them | -| 8 | Approaches tried and abandoned | directions walked and rejected | -| 9 | Findings that cost effort to discover | non-obvious system facts, expensive to re-derive | -| 10 | Remaining actions, in order | every action still to take, sequenced | -| 11 | Open questions to investigate | unknowns the resuming session can resolve itself | -| 12 | Blockers needing an outside decision | work that cannot proceed without someone else | -| 13 | Suggested skills | which skills to invoke for the remaining work | +| 1 | Original goal | what the work is FOR, in the user's own words | +| 2 | Resumption brief | the resumption decision | +| 3 | Completion criteria | what "done" means, observably | +| 4 | Constraints that must hold | invariants whose violation breaks the work | +| 5 | Environment to re-establish | machine and session state `/clear` destroyed | +| 6 | Side effects already applied | persistent effects that must NOT be repeated | +| 7 | File roles in this work | which file plays which role, and how far its change got | +| 8 | Decisions already settled | closed choices, with the reasoning that closed them | +| 9 | Approaches tried and abandoned | directions walked and rejected | +| 10 | Findings that cost effort to discover | non-obvious system facts, expensive to re-derive | +| 11 | Remaining actions, in order | every action still to take, sequenced | +| 12 | Open questions to investigate | unknowns the resuming session can resolve itself | +| 13 | Blockers needing an outside decision | work that cannot proceed without someone else | +| 14 | Suggested skills | which skills to invoke for the remaining work | **Every section is always present.** A section with nothing to report reads `None.` plus a half-line of reason. A cold reader cannot otherwise tell "nothing to report" from "the author forgot", and the absence is itself load-bearing — "no approaches abandoned" tells the resumer the ground is untrodden. +**`Original goal` is the one section `None.` never satisfies:** work with no statable goal is the +condition this document exists to surface, so an empty §1 is a defect to raise with the user, not a +box to tick. (Its `Amended:` line is the field that legitimately reads `None.`) **Emit body sections at `##`.** This document nests them under its own heading, so they appear here one level deeper than they are written. **Layering is not truncation.** No section carries a length budget except the brief. Progressive -disclosure governs the ORDER facts are met in, never whether they survive. Sections 7, 8, and 9 +disclosure governs the ORDER facts are met in, never whether they survive. Sections 8, 9, and 10 exist specifically for what a summarizer discards first — rationale, negative knowledge, and hard-won facts — because those read as "old" while being the most expensive to rediscover. @@ -46,13 +51,45 @@ too): plain statement only for what this session itself verified, an explicit `UNVERIFIED ()` marker on anything inherited. The met/unmet marks in Completion criteria carry the same rule. +### Original goal + +**The user's own words, quoted, and immutable across the chain.** This section owns the goal; every +other section is subordinate to it. It is the one thing a chain of save-points loses first, because +each writer serializes the machinery in front of them — the phase, the bundle, the checklist — and +machinery reads as mission to the session that inherits it. + +- **Goal (verbatim):** the user's goal statement quoted as they wrote it, with the date they stated + it. Quote it; never paraphrase, condense, or "clarify" — a paraphrase is a re-derivation, and this + section exists because re-derivation is what fails. Where the goal was never put in one sentence, + quote the closest thing the user actually wrote and mark it `RECONSTRUCTED`: a reconstruction is a + defect to settle with them, not a substitute for their words. +- **Amended:** `None.` until the goal changes. It changes ONLY on an explicit statement from whoever + set it — never because the work went somewhere else. Record an amendment as a new dated verbatim + quote with the prior goal kept above it, so the chain shows what the goal was and when it stopped + being that. A writer never amends the goal on its own authority. +- **Next action serves it by:** one sentence tying the first item of `Remaining actions, in order` + back to the goal. This couples to §11 deliberately — a reader who stops here has to be able to + tell whether the work is still pointed at the goal, and a pointer to another section cannot + answer that. + +**Cannot state that sentence? That is drift, and this is where it gets said.** Write what the next +action actually serves, then route it: re-derive an action that serves the goal, or ask whether the +goal has changed. Staying silent is what lets drift run — nothing else in this document would have +caught it, because every other section describes the work faithfully. + +**A successor handoff COPIES the goal and its amendments; it never restates them.** The write +procedure below makes that a disk read, not a recollection. The drift-check line is the one part +re-answered each hop — it is about the next action, which moved. + ### Resumption brief -Six lines maximum. The one section a reader may stop at. +Six lines maximum. The one section a reader may stop at *after* the goal above it. -Carries: when it was written and against which branch or commit, the goal in one line, where the -work stands in one line, and the single next concrete action. Name the section that governs that -action so a reader wanting more is routed rather than left searching. +Carries: when it was written and against which branch or commit, where the work stands in one line, +and the single next concrete action. Name the section that governs that action so a reader wanting +more is routed rather than left searching. It does NOT restate the goal — §1 owns that, verbatim, +and a six-line onboarding surface is exactly where a goal gets compressed into the process that was +serving it. The brief names the FIRST action only. It always points at `Remaining actions, in order`, which owns the full sequence — otherwise a session that completes the one named action has nothing to go on. @@ -69,17 +106,31 @@ One line of why the work exists, then each criterion as an observable test with A criterion nobody can check is not a criterion — rewrite until a command or a diff settles it. +**Each criterion names the goal-state it establishes, and keeps its observable.** A criterion reads +as a condition the goal in §1 requires — "the repo's docs follow conventions X, Y, and Z" — never as +the process step meant to produce it ("phase 3 done", "the bundle merged"). Process framing is what +turns a resumed session onto the machinery: it is satisfiable while the goal is no closer, and it +reports done when the process finished rather than when the work landed. + +This stacks on the observability rule; it does not relax it. Goal-framed criteria are the harder +ones to settle mechanically, which is precisely why writers drift to process framing — so each +criterion carries both halves, the goal-state and the command or diff that settles it. + ```markdown - [x] `dotnet test` green on the affected projects - [ ] the retry path is exercised by a test that fails without the fix ``` +**Process milestones are recorded subordinate to the criteria, never as criteria.** They pace the +work and cannot define done. Put them under a `Process milestones` sub-heading one level below this +section's own emitted heading, each tied to the criterion it advances. + ### Constraints that must hold Invariants whose violation breaks the work. One testable assertion per line, each followed by the consequence of violating it. -Only things that would actually break something. A preference is a decision — section 7. +Only things that would actually break something. A preference is a decision — section 8. Before closing the section, re-scan for *but*, *except*, *unless*, "the exception is", "the corner case" — those words mark constraints that emerged mid-discussion and never rose to a top-line @@ -203,7 +254,7 @@ and why it cannot be salvaged. Without this the next session repeats the dead end. Budget detail generously. -A fact about how the system behaves belongs in section 9; a path you walked belongs here. +A fact about how the system behaves belongs in section 10; a path you walked belongs here. ```markdown - Wrapping the call in `Polly` retry → the transport already retries, so failures multiplied to @@ -229,9 +280,9 @@ This is the section that beats compaction. Write it long. Every action still to take, sequenced. Not just the next one — the whole remainder, so finishing the first action does not leave the resuming session guessing at the second. -An action is something to *do*. An unknown to resolve is section 11; something you cannot proceed -on is section 12. Cross-reference those rather than duplicating them: an action that waits on a -blocker is listed here in its sequence position, marked as waiting, and named once in section 12. +An action is something to *do*. An unknown to resolve is section 12; something you cannot proceed +on is section 13. Cross-reference those rather than duplicating them: an action that waits on a +blocker is listed here in its sequence position, marked as waiting, and named once in section 13. The `Resumption brief` names only the first of these. This section owns the rest — it is the one place the full sequence exists, so it survives when the brief's single action is done. @@ -239,7 +290,7 @@ place the full sequence exists, so it survives when the brief's single action is ```markdown 1. Wire the retry policy into `OrderReader` (spec: `docs/adr/0012-retry-policy.md`). 2. Add the cancellation edge-case tests — the happy path is already covered. -3. Waiting on the staging credential grant: re-run the integration suite against staging (§12). +3. Waiting on the staging credential grant: re-run the integration suite against staging (§13). 4. Update the module README once 1-3 land. ``` @@ -278,8 +329,11 @@ When no skill maps to the remaining work, write `None — remaining work runs in ## How this document is referenced elsewhere -The emitted resume directive points at the handoff FILE and names no section. Renaming or -reordering a section here therefore requires no edit to `save-point.md`, and does not orphan +The emitted resume directive points at the handoff FILE and names exactly one section — `Original +goal`, by name and never by number — because its alignment clause has to say what the resuming +session confirms; `/session-flow:keep-going`, `/session-flow:reanchor`, and the handoff enforcement +checklist name that same section for the same reason. Renaming §1 therefore requires an edit to +those surfaces. Renaming or reordering any other section requires none, and no change here orphans handoffs already written to disk. Consumers cite this section list rather than restating it. A copy of the list in another file drifts @@ -371,6 +425,15 @@ The first handoff of a NEW task omits the field, even when older, unrelated hand directory. Older entries lacking `session_id` cause chain-walkers to break cleanly at the first absent field. +**Carrying the goal forward — read it off disk, never out of memory.** Whenever `previous_handoff` +is emitted, open that file THIS turn and reproduce its `Original goal` verbatim quote and every +recorded amendment into this handoff unchanged. Rebuilding the goal from the conversation is the +drift vector itself: the conversation is what already lost it, and each rebuild is individually +plausible, which is why the loss is invisible until many hops later. The prior file is on disk and +one read away — a writer that did not open it has not carried the goal forward, whatever its text +ends up saying. Same rule as the live `TaskList` call: the check is that the read happened, not that +the result looks right. + `CLAUDE_CODE_SESSION_ID` is set in the Bash tool subprocess (Claude Code v2.1.132+). Resolve it via Bash — skill markdown does not template-expand env vars. diff --git a/plugins/session-flow/skills/handoff/SKILL.md b/plugins/session-flow/skills/handoff/SKILL.md index ef07a84b2..d2a070682 100644 --- a/plugins/session-flow/skills/handoff/SKILL.md +++ b/plugins/session-flow/skills/handoff/SKILL.md @@ -130,7 +130,13 @@ ambiguous. frontmatter per the engine's structure doc (`${CLAUDE_PLUGIN_ROOT}/reference/structure.md`) - [ ] `previous_handoff` present IF this session continued a prior handoff's task (chain continuity per the same structure doc); omitted otherwise — including when the directory holds only - unrelated-task handoffs + unrelated-task handoffs. When present, that file was opened from disk THIS turn and its + `Original goal` quote and amendments copied over unchanged — never rebuilt from the conversation +- [ ] `Original goal` carries the user's goal in their own words, quoted with its date — not a + paraphrase and not the process serving it — and the drift-check sentence tying the next action + back to it is answered (structure doc, "Original goal") +- [ ] Completion criteria read as goal-states, each keeping the command or diff that settles it; + process milestones sit under the subordinate sub-heading, never as criteria - [ ] Every body section the structure doc defines is present — walked from that doc this turn, not written from memory; a section with nothing to report says so explicitly rather than being omitted - [ ] Claim provenance applied — inherited status marked `UNVERIFIED ()`, not stated as @@ -152,6 +158,9 @@ ambiguous. **Prompt-only path:** - [ ] Prompt-only justified (all auto-detect criteria hold, OR `prompt` explicitly passed) +- [ ] The verbatim goal line sits between the rails above the remaining-work bullets — prompt-only + writes no file, so the goal travels in the prompt or not at all (engine doc, "Original goal — + mandatory on BOTH paths") - [ ] Claim provenance applied to every inline remaining-work bullet — inherited status marked `UNVERIFIED ()`, not stated as plain fact (engine doc, "Claim provenance") - [ ] Redaction pass swept the prompt (secrets/tokens/credentials/PII replaced with shape markers) diff --git a/plugins/session-flow/skills/handoff/context/gotchas.md b/plugins/session-flow/skills/handoff/context/gotchas.md index 80583314b..e1c1ae08a 100644 --- a/plugins/session-flow/skills/handoff/context/gotchas.md +++ b/plugins/session-flow/skills/handoff/context/gotchas.md @@ -2,6 +2,14 @@ Failure patterns from real sessions. Loaded on demand from the handoff SKILL.md. +- **A chain that preserved every fact and lost the point** — a handoff chain preserves state + perfectly and intent not at all unless the goal field is mandatory and immutable. Each hop + serializes the machinery in front of it — the phase, the bundle, the checklist — as though that + were the mission, and the resumed session optimizes it faithfully. No single hop looks wrong: + every paraphrase is plausible, and the loss only shows up in the aggregate, many sessions later. + Quote the user's goal verbatim in section 1, copy it from the prior file read off disk instead of + re-deriving it, and write completion criteria as the goal-states they establish — a criterion that + can be satisfied while the goal is no closer is a process milestone under the wrong heading. - **Prompt-only when durability is required** — prompt-only fits small, self-contained follow-ups; when a plan artifact, dead-ends, or load-bearing decisions back the work, write the durable handoff file. Any doubt → full handoff. diff --git a/plugins/session-flow/skills/handoff/evals/evals.json b/plugins/session-flow/skills/handoff/evals/evals.json index b685c141c..8e6a9eb13 100644 --- a/plugins/session-flow/skills/handoff/evals/evals.json +++ b/plugins/session-flow/skills/handoff/evals/evals.json @@ -80,6 +80,34 @@ "The distinction drawn is credential-bearing versus non-credential userinfo, not ssh-versus-https as such", "Neither case is emitted as a shape marker" ] + }, + { + "id": 7, + "name": "original-goal-is-quoted-and-copied-never-resummarized", + "prompt": "Handoff. This continues the handoff I resumed from this morning — we're on phase 3 of the doc-alignment work and the first two bundles are merged.", + "expected_output": "The Original goal section quotes the user's own goal statement verbatim rather than the process the session was executing. Because this session continues a prior handoff, previous_handoff is emitted AND that prior file is opened from disk this turn so its goal quote and any recorded amendments are copied over unchanged instead of re-derived from the conversation. Completion criteria read as goal-states that each keep their observable; the phase and bundle milestones sit under the subordinate Process milestones sub-heading rather than standing in as criteria.", + "files": [], + "expectations": [ + "An Original goal section is present and quotes the user's goal in their own words — a paraphrase, a condensation, or the process framing ('finish phase 3') does not satisfy it", + "Because this continues a prior handoff, that prior file is READ FROM DISK this turn and its goal quote copied unchanged rather than reconstructed from the conversation", + "The goal is not amended — an amendment requires an explicit statement from whoever set the goal, recorded with its date", + "Completion criteria are stated as goal-states that each still name the command or diff that settles them, rather than solely as process steps", + "Milestones like 'phase 3 done' or 'the bundles merged' appear under the subordinate Process milestones sub-heading, not as completion criteria", + "The drift-check sentence tying the next action back to the goal is answered" + ] + }, + { + "id": 8, + "name": "prompt-only-still-carries-the-goal", + "prompt": "/handoff prompt — just two lint fixes left on the docs branch.", + "expected_output": "Prompt-only writes no body sections, so the verbatim goal line travels inline between the rails, above the remaining-work bullets. A prompt-only save-point listing only the follow-ups is the exact shape that loses the goal.", + "files": [], + "expectations": [ + "The verbatim goal line appears between the rails, above the inline remaining-work bullets", + "The goal is quoted in the user's own words rather than described as the remaining lint work", + "The reasoning recognizes that prompt-only points at no file, so the goal travels in the prompt or not at all", + "An active /goal still occupies the FIRST line between the rails — the goal line does not displace it" + ] } ] } diff --git a/plugins/session-flow/skills/keep-going/SKILL.md b/plugins/session-flow/skills/keep-going/SKILL.md index c3b5b2ba5..3fcd6a777 100644 --- a/plugins/session-flow/skills/keep-going/SKILL.md +++ b/plugins/session-flow/skills/keep-going/SKILL.md @@ -70,13 +70,25 @@ itself the thing this skill removes. - **Dead but safe to redo** → restart it (subject to the autonomy policy below). - **Unrecoverable** → surface it plainly; do not fake a recovery. -4. **Reconcile the main thread.** Restate where the primary task actually - stood — grounded in a fresh read of any plan / checklist / task - artifact backing it, not a prior turn's claim — then continue it. When - the interruption followed a `/session-flow:handoff`, read that file - rather than trusting memory — and when the handoff's path was lost (a - `/clear` without copying the resume prompt), recover it first with - `/session-flow:find-handoff`. +4. **Reconcile the main thread — goal alignment is the FIRST check.** + When the interruption followed a `/session-flow:handoff`, read that + file rather than trusting memory — and when the handoff's path was + lost (a `/clear` without copying the resume prompt), recover it first + with `/session-flow:find-handoff`. Read its `Original goal` section, + then test the planned next actions against it: **say in one sentence + how the next action serves that goal.** If you cannot, that is drift, + not a wording problem — stop, and either re-derive an action that does + serve the goal or ask the user whether the goal changed. **A handoff + carrying no `Original goal` is itself a defect:** do not infer the goal + from the process the file describes, which is the thing that drifted — + ask the user for it in their own words before continuing, and carry + their answer into the next save-point. Then restate where the primary + task actually stood — grounded in a fresh read of any plan / checklist + / task artifact backing it, not a prior turn's claim — and continue it. + + One sentence, not a new stage. Its cost is nothing and its absence is + the only signal that many sessions of faithful execution were aimed at + the wrong thing. 5. **Report.** One list: recovered, restarted, still-running, and lost / unrecoverable. diff --git a/plugins/session-flow/skills/keep-going/evals/evals.json b/plugins/session-flow/skills/keep-going/evals/evals.json index 89cc27eca..984b3c6c2 100644 --- a/plugins/session-flow/skills/keep-going/evals/evals.json +++ b/plugins/session-flow/skills/keep-going/evals/evals.json @@ -76,6 +76,32 @@ "If the work is progressing, leaves it alive rather than killing it", "Gates the kill/restart — does not kill live-but-slow work it cannot prove is dead" ] + }, + { + "id": 7, + "name": "goal-alignment-is-the-first-check-on-resume", + "prompt": "keep going — resume from the handoff at .work/handoffs/20260803T101500Z-handoff-doc-alignment.md, nothing was running in the background.", + "expected_output": "Before restating position or continuing, the skill reads the handoff's Original goal section and tests the planned next actions against it, stating in one sentence how the next action serves that goal. If it cannot state that, it treats the gap as drift — stopping to re-derive an action that serves the goal, or asking whether the goal changed — rather than continuing on the process the file describes.", + "files": [], + "expectations": [ + "Reads the handoff's Original goal section BEFORE reconciling position or continuing the work", + "States in one sentence how the planned next action serves that goal", + "Treats an unstatable connection as drift — stops and re-derives or asks, rather than proceeding", + "Still reconciles the main thread from a fresh read of the backing artifact afterwards and continues" + ] + }, + { + "id": 8, + "name": "handoff-missing-the-goal-is-a-defect-not-a-gap-to-infer", + "prompt": "keep going — here's the handoff from the last session. It lists the remaining phases and the completion criteria but has no Original goal section.", + "expected_output": "The absent Original goal is flagged as a defect in the handoff itself. The skill does NOT infer the goal from the phases and process the file describes — that process is exactly what may have drifted — and instead asks the user for the goal in their own words before continuing, carrying their answer into the next save-point.", + "files": [], + "expectations": [ + "Flags the missing Original goal as a defect rather than proceeding as though the file were complete", + "Does NOT reconstruct the goal from the phases, criteria, or process the handoff describes", + "Asks the user for the goal in their own words before continuing the work", + "Carries the user's answer forward so the next save-point records it" + ] } ] } diff --git a/plugins/session-flow/skills/reanchor/SKILL.md b/plugins/session-flow/skills/reanchor/SKILL.md index d764f3771..b4b3aee4c 100644 --- a/plugins/session-flow/skills/reanchor/SKILL.md +++ b/plugins/session-flow/skills/reanchor/SKILL.md @@ -61,12 +61,28 @@ back up. 4. **Stale memory-tier files.** For the session's handoff / todo / working-memory files, flag entries whose subjects have since merged or landed, so the next step neither re-does settled work nor chases a closed thread. +5. **The recorded goal, and whether the planned work still serves it.** A + handoff's `Original goal` is a premise like any other — a documented claim + about what the work is FOR — except nothing else on any resume path ever + tests it. Confirm it is present; confirm a document that chains from another + carries the prior link's quote **unchanged**, read from that file rather than + from the current document's own wording; then confirm the planned next + actions still connect to it, stated in one sentence. A quote that was + re-derived instead of copied is reported as drift between the links — the + paraphrase IS the drift, and it is invisible at any single hop because each + rewording is individually plausible. An absent `Original goal` is itself a + defect: never infer the goal from the process the document describes, because + that process is the thing that drifted — ask for it in the user's own words. + Where the next actions cannot be tied to the goal in a sentence, that is the + finding, not a wording problem. ## Flow -1. Gather the session's inputs — the handoff / plan / memory files and any PRs, - issues, branches, skills, or plugin versions they name. -2. Run the four checks above against live reality — fetch/read the live source +1. Gather the session's inputs — the handoff / plan / memory files, the + `Original goal` they record, and any PRs, issues, branches, skills, or plugin + versions they name. When an input chains from a prior handoff, open that file + too: the goal check below compares links, which a single document cannot do. +2. Run the five checks above against live reality — fetch/read the live source (`git fetch` the base, query `gh`, read installed vs repo-source manifests). When a check cannot reach what it needs (no network, no `gh`, no fetch), report that premise as **unverifiable** rather than assuming it is unchanged; @@ -92,7 +108,16 @@ back up. - **Does not re-anchor a standing rule or discipline.** Correcting behavioral doctrine mid-session (a standing-rule re-anchor) is a separate concern; this skill re-anchors factual assumptions against live reality, not rules. -- **Does not auto-fix drift.** It reports; the session decides. +- **Does not solely own the goal check.** Reanchor is opt-in, so a check living + only here fires only once staleness is already suspected — which is exactly + when a drifted chain looks healthiest. The resume-prompt directive carries it + on every resume and `/session-flow:keep-going` carries it on the interrupted + one; this copy serves the deliberate "is this still current" pass over an old + plan, where the other two never run. +- **Does not auto-fix drift.** It reports; the session decides. On a goal + misalignment that means naming it and handing to `/session-flow:keep-going` — + reanchor never re-derives the next action or amends a recorded goal, which + changes only on an explicit statement from whoever set it. ## Gotchas diff --git a/plugins/session-flow/skills/reanchor/evals/evals.json b/plugins/session-flow/skills/reanchor/evals/evals.json index ade8d3b5e..47189ea67 100644 --- a/plugins/session-flow/skills/reanchor/evals/evals.json +++ b/plugins/session-flow/skills/reanchor/evals/evals.json @@ -82,6 +82,33 @@ "Standing-rule/discipline correction routes to the discipline plugin when installed, else is treated as outside reanchor's scope", "Distinguishes factual-premise freshness (reanchor) from rule discipline" ] + }, + { + "id": 8, + "name": "recorded-goal-is-a-premise-and-is-compared-across-links", + "prompt": "Re-anchor before I keep going on this — the handoff is the fifth in a chain and I want to know whether anything it assumes has gone stale.", + "expected_output": "reanchor treats the recorded Original goal as a premise alongside the PR/branch/version checks: it opens the prior handoff the document chains from and compares the goal quote across links, reporting any re-derivation as drift, then states in one sentence how the planned next actions serve that goal. It reports the misalignment and hands to /session-flow:keep-going rather than re-deriving the next action or amending the goal itself.", + "files": [], + "expectations": [ + "Checks the recorded Original goal as a premise, not only the PRs, branches, and plugin versions", + "Opens the prior handoff in the chain and compares the goal quote across links rather than reading only the current document", + "Reports a paraphrased or re-derived goal as drift between links, with claimed-vs-actual framing", + "Ties the planned next actions to the goal in one sentence, and treats an unstatable connection as the finding", + "Does not amend the recorded goal or re-derive the next action itself — reports and hands to /session-flow:keep-going" + ] + }, + { + "id": 9, + "name": "absent-goal-is-a-defect-not-something-to-infer", + "prompt": "Reanchor this session against the plan file — heads up, it never recorded a goal section, it just lists the phases and what's left.", + "expected_output": "The absent Original goal is reported as a defect in the document rather than reconstructed. reanchor does not infer the goal from the phases and process the file describes — that process is precisely what may have drifted — and asks for the goal in the user's own words before the session builds on the plan.", + "files": [], + "expectations": [ + "Reports the missing Original goal as a defect in the input, not as an absent-therefore-fine premise", + "Does NOT reconstruct the goal from the phases, remaining work, or process the document describes", + "Asks for the goal in the user's own words", + "Still reports the other premise checks rather than stopping the re-anchor entirely" + ] } ] } From 24c4db56ab7aa07fc2e6055bcdade82f31a76db4 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:30:06 -0400 Subject: [PATCH 2/5] fix(session-flow): scope reanchor's goal-check rationale to the cross-link test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verifier finding: "nothing else on any resume path ever tests it" was pre-fix rationale that survived into post-fix text — false by this same change's design, and contradicted by the file's own "Does not solely own the goal check" item. Rescoped to the one test only reanchor performs: the cross-link quote comparison. Co-authored-by: Claude Fable 5 --- plugins/session-flow/skills/reanchor/SKILL.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/session-flow/skills/reanchor/SKILL.md b/plugins/session-flow/skills/reanchor/SKILL.md index b4b3aee4c..aec99046c 100644 --- a/plugins/session-flow/skills/reanchor/SKILL.md +++ b/plugins/session-flow/skills/reanchor/SKILL.md @@ -63,8 +63,9 @@ back up. step neither re-does settled work nor chases a closed thread. 5. **The recorded goal, and whether the planned work still serves it.** A handoff's `Original goal` is a premise like any other — a documented claim - about what the work is FOR — except nothing else on any resume path ever - tests it. Confirm it is present; confirm a document that chains from another + about what the work is FOR — and the cross-link comparison below is the one + test only reanchor performs (the other resume-path checks read a single + document). Confirm it is present; confirm a document that chains from another carries the prior link's quote **unchanged**, read from that file rather than from the current document's own wording; then confirm the planned next actions still connect to it, stated in one sentence. A quote that was From f0b672d9b8ab7f07035658a9629f12b802468906 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:36:52 -0400 Subject: [PATCH 3/5] docs(catalog): regenerate catalog block for session-flow 0.17.23 plugin-gate remediation: the catalog block in docs/CATALOG.md is generated from plugin manifests and went stale with the version bump. Co-authored-by: Claude Fable 5 --- docs/CATALOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CATALOG.md b/docs/CATALOG.md index 774fe392f..b9b846fab 100644 --- a/docs/CATALOG.md +++ b/docs/CATALOG.md @@ -89,7 +89,7 @@ plugin manifests and kept in sync by CI — never hand-edit it; the category voc ## Workflow -- [`session-flow`](../plugins/session-flow) — Session-lifecycle toolkit of thirteen skills: workflow (navigate a staged dev workflow and suggest the next stage), handoff (write a save-point and resume prompt for /clear-and-resume), continue-in-background (delegate the task to a fresh background agent that continues it now — same save-point engine as handoff, delivered by launching a detached claude --bg session seeded with the resume prompt; launches only on explicit user request), keep-going (recover and continue after any interruption OR when live off-thread work looks stalled — inventory off-thread work, inspect its real output, act only on evidence, then continue; after a usage limit lifts it continues rather than summarizing-and-stalling), find-handoff (recover a lost handoff after /clear — when the resume prompt was written but never copied — via a read-only detection ladder: known-location glob of the handoffs dir, then a bounded, recency-ranked transcript scan for the handoff directive and dashed-rail markers, then a confirm-before-resume gate; surfaces only the resume prompt + metadata, never raw transcript content), clean-stop (get to a durable, linked stopping point before the machine may go away — sweep every repo/worktree for uncommitted, unpushed, or PR-less work, push it durable, put breadcrumbs in PR/issue bodies, then give a free-and-clear verdict), retro (structured end-of-session retrospective with transcript metrics and learning codification), running-retro (in-flight retrospective checkpoints that spawn a subagent to analyze the transcript so far and append classified findings to a cumulative running ledger — capture and route only, the live counterpart to retro; also owns a detached-observer substrate that can watch a session out-of-band and run the checkpoint autonomously after the session ends), orient (read-only session orientation — synthesize where we stand, what we are doing, and why, from durable + off-thread state the built-in /recap never sees: ledgers, handoffs, workflow checklists, running-retro ledgers, open PRs and work-items, and git), orchestrate (arm a session or worker with proactive-orchestration imperatives), reanchor (verify a session's working assumptions are still true against live reality — referenced PRs/issues/branches, base-branch drift, renamed/version-drifted surfaces, stale memory-tier files — before building on them), reconcile (retire finished off-thread work and reconcile this session's task ledger with reality — the prune-and-reconcile counterpart to keep-going's resume: inventory the work this session spawned, inspect its real state, retire the finished and close proven-done tasks, auto-settling the finished and gating any kill of still-running work; sibling sessions in the project are reported read-only), and setup (check-centric verification of the observer's runtime prerequisites and configuration). +- [`session-flow`](../plugins/session-flow) — Session-lifecycle toolkit of thirteen skills: workflow (navigate a staged dev workflow and suggest the next stage), handoff (write a save-point and resume prompt for /clear-and-resume), continue-in-background (delegate the task to a fresh background agent that continues it now — same save-point engine as handoff, delivered by launching a detached claude --bg session seeded with the resume prompt; launches only on explicit user request), keep-going (recover and continue after any interruption OR when live off-thread work looks stalled — inventory off-thread work, inspect its real output, act only on evidence, then continue; after a usage limit lifts it continues rather than summarizing-and-stalling), find-handoff (recover a lost handoff after /clear — when the resume prompt was written but never copied — via a read-only detection ladder: known-location glob of the handoffs dir, then a bounded, recency-ranked transcript scan for the handoff directive and dashed-rail markers, then a confirm-before-resume gate; surfaces only the resume prompt + metadata, never raw transcript content), clean-stop (get to a durable, linked stopping point before the machine may go away — sweep every repo/worktree for uncommitted, unpushed, or PR-less work, push it durable, put breadcrumbs in PR/issue bodies, then give a free-and-clear verdict), retro (structured end-of-session retrospective with transcript metrics and learning codification), running-retro (in-flight retrospective checkpoints that spawn a subagent to analyze the transcript so far and append classified findings to a cumulative running ledger — capture and route only, the live counterpart to retro; also owns a detached-observer substrate that can watch a session out-of-band and run the checkpoint autonomously after the session ends), orient (read-only session orientation — synthesize where we stand, what we are doing, and why, from durable + off-thread state the built-in /recap never sees: ledgers, handoffs, workflow checklists, running-retro ledgers, open PRs and work-items, and git), orchestrate (arm a session or worker with proactive-orchestration imperatives), reanchor (verify a session's working assumptions are still true against live reality — referenced PRs/issues/branches, base-branch drift, renamed/version-drifted surfaces, stale memory-tier files, and the goal a handoff records, compared across the chain so a re-derived goal reports as drift — before building on them), reconcile (retire finished off-thread work and reconcile this session's task ledger with reality — the prune-and-reconcile counterpart to keep-going's resume: inventory the work this session spawned, inspect its real state, retire the finished and close proven-done tasks, auto-settling the finished and gating any kill of still-running work; sibling sessions in the project are reported read-only), and setup (check-centric verification of the observer's runtime prerequisites and configuration). - [`visualization`](../plugins/visualization) — On-demand visualization router: infers what in the current conversation should be shown visually, then decides the best FORM (a mermaid diagram, a markdown table, a hand-authored SVG/CSS chart, ASCII/Unicode art, or a rich rendered page) and the best MEDIUM (inline terminal, a local HTML file, or a published Artifact) via a decision matrix over content shape, complexity, and a configurable medium preference. Renders good defaults and asks only when the target is genuinely ambiguous and no form was named. A form-and-medium decision layer in front of the craft capabilities — it routes chart craft and artifact-design fundamentals to those capabilities when installed and never restates them. ## Project Management From 232cafff536a897a9eb242eb0ecf2416dfb47c2d Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:43:11 -0400 Subject: [PATCH 4/5] fix(session-flow): resolve four review findings on goal preservation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F1 (claude[bot]): the /goal-vs-goal-quote ordering on prompt-only was stated only in eval 8's expectation. save-point.md now states it where the prompt-only goal rule is introduced ("Original goal — mandatory on BOTH paths") and in the "Combining both" paragraph: an active /goal keeps the first line, the verbatim goal quote comes next, then the remaining-work bullets. The handoff checklist's prompt-only line carries the same clause. F2 (Codex): keep-going ran its goal-alignment check in step 4, after step 3 had already resumed/restarted off-thread work — so it could restart work serving a drifted goal and discover the misalignment after. Goal alignment is now its own step 3, after the read-only inventory/inspection (steps 1-2) and before any recovery action (step 4); reconcile and report renumber to 5-6. The nothing-off-thread case, save-point.md's cross-reference, and eval 7 track the new structure. F3 (Codex): the prompt-only path required a singular verbatim goal line, discarding the amendment history structure.md's Amended field preserves. save-point.md now defines the inline form: with recorded amendments the prompt carries the original dated quote plus every dated amendment ("amended :" lines), verbatim and copied unchanged on later hops; a bare single line is valid only when no amendment exists. The handoff checklist asserts the same. F4 (Codex): handoff eval 8's prompt supplied neither a quotable user goal nor an active /goal, so its expectations could not be exercised. The prompt now states the goal verbatim and an armed /goal, and the expectations match the F1 ordering rule. CHANGELOG [0.17.23] extended to cover the ordering rule, amendment carriage, and keep-going's dedicated goal-alignment step (no version bump; unreleased). Co-authored-by: Claude Fable 5 --- plugins/session-flow/CHANGELOG.md | 12 +++-- plugins/session-flow/reference/save-point.md | 26 +++++++--- plugins/session-flow/skills/handoff/SKILL.md | 8 +-- .../skills/handoff/evals/evals.json | 12 ++--- .../session-flow/skills/keep-going/SKILL.md | 51 ++++++++++--------- .../skills/keep-going/evals/evals.json | 4 +- 6 files changed, 69 insertions(+), 44 deletions(-) diff --git a/plugins/session-flow/CHANGELOG.md b/plugins/session-flow/CHANGELOG.md index 53669acd6..b6aa628be 100644 --- a/plugins/session-flow/CHANGELOG.md +++ b/plugins/session-flow/CHANGELOG.md @@ -38,8 +38,10 @@ - **Nothing re-anchored a resumed session to its goal, so the drift ran unnoticed across many sessions.** The check now sits on all three surfaces a resume can cross. `reference/save-point.md` gains an `Original goal — mandatory on BOTH paths` rule (prompt-only - writes no body sections, so it carries the verbatim goal line inline between the rails, above its - remaining-work bullets — it points at no file, and a prompt-only save-point listing just the + writes no body sections, so it carries the verbatim goal inline between the rails — below an + active `/goal` re-arm when one holds the first line, above its remaining-work bullets, and with + every dated amendment travelling under the original quote rather than collapsing to a single + line — it points at no file, and a prompt-only save-point listing just the follow-ups is the precise shape that loses the goal), and the rails directive becomes `Read @…, confirm its Original goal still governs the remaining next steps, then continue them.` — the directive because it is the one artifact every resume passes through, including the dominant bare @@ -49,8 +51,10 @@ consequence, recorded where `structure.md` describes how that doc is cited elsewhere: the directive now names `Original goal` by name (never by number), so renaming that one section ripples out to it, where before no rename ripple existed. `skills/keep-going` owns the - skill-mediated path — its "Reconcile the main thread" step now runs goal alignment FIRST, before - restating position: read the handoff's `Original goal`, say in one sentence how the next action + skill-mediated path — goal alignment is now its own step, sitting after the read-only + inventory/inspection and BEFORE any recovery action, because resuming or restarting work that + serves a drifted goal re-arms the drift before anything has tested it: read the handoff's + `Original goal`, say in one sentence how the next action serves it, and treat an unstatable connection as drift rather than a wording problem. A handoff carrying no `Original goal` is itself a flagged defect — the goal is never inferred from the process the file describes, since that process is the thing that drifted; the user is asked for it diff --git a/plugins/session-flow/reference/save-point.md b/plugins/session-flow/reference/save-point.md index 7a4c92c1a..7c5e345dc 100644 --- a/plugins/session-flow/reference/save-point.md +++ b/plugins/session-flow/reference/save-point.md @@ -108,9 +108,19 @@ drops in silence, and no amount of detail elsewhere replaces it. On the full path this is body section 1, `Original goal` ([`structure.md`](structure.md)) — which also owns the immutability rule and the disk-read copy step a successor handoff runs. -**Prompt-only writes no body sections, so it carries the verbatim goal line inline between the -rails**, above its remaining-work bullets. It has no file to point at, and a prompt-only save-point -listing only the follow-ups is the exact shape that loses the goal. +**Prompt-only writes no body sections, so it carries the verbatim goal inline between the rails**, +above its remaining-work bullets — and below an active `/goal` re-arm when one holds the first +line: the re-arm keeps that line ("Combining both", below), the goal quote comes next, the bullets +after. It has no file to point at, and a prompt-only save-point listing only the follow-ups is the +exact shape that loses the goal. + +**Amendments travel too.** A bare single goal line is valid only while the goal has no recorded +amendment (`Amended: None.` on the full path). Once an amendment exists, the prompt-only form +carries the original dated quote plus EVERY dated amendment — compact, one line each, +`amended : ""` under the original — still verbatim, still copied unchanged on +later hops. The full path preserves that history in §1's `Amended:` field; a prompt-only hop that +collapses it back to a single line discards the record of what the goal was and when it stopped +being that, which no later full-path handoff can reconstruct. ## Writing the handoff file (full path) @@ -223,7 +233,11 @@ copy, which goes stale the moment disk moved on without this conversation seeing invocation, and would silently fail to arm. Each is therefore its own message. `/goal` keeps its place as the first line between the rails (it is session-scoped and evaluated after every subsequent turn regardless of what invoked it, so arming it there covers the loop's later - iterations too); the `/loop` re-arm follows as the separate message described above. + iterations too); the `/loop` re-arm follows as the separate message described above. On + prompt-only, the verbatim goal quote — with its dated amendment lines, per "Original goal — + mandatory on BOTH paths" — sits directly BELOW the `/goal` line and above the remaining-work + bullets: an active `/goal` keeps the first line, the quote never displaces it, and with no active + `/goal` the quote itself opens the block. Full-path shape (minimum form — live: bare `─` rails, no fence; shown inside a fence here for display): @@ -314,8 +328,8 @@ bullets inline between the rails instead, and needs no origin line: it reference path passes through.** The dominant resume is a paste into a fresh session that invokes no skill at all, so a check living only in a skill fires only when someone happens to call it — which is how a chain of save-points can run for many sessions with nothing ever testing the work against its goal. -`/session-flow:keep-going` owns the same check on the skill-mediated path (its "Reconcile the main -thread" step); this covers the bare paste, the background agent +`/session-flow:keep-going` owns the same check on the skill-mediated path (its goal-alignment +step, which gates its recovery actions); this covers the bare paste, the background agent `/session-flow:continue-in-background` launches, and a `find-handoff` recovery alike. It is not a detection-contract change: signal 1 below is matched on the `…handoffs/-handoff-…` shape the directive names, which the added clause leaves untouched. diff --git a/plugins/session-flow/skills/handoff/SKILL.md b/plugins/session-flow/skills/handoff/SKILL.md index d2a070682..2074209a5 100644 --- a/plugins/session-flow/skills/handoff/SKILL.md +++ b/plugins/session-flow/skills/handoff/SKILL.md @@ -158,9 +158,11 @@ ambiguous. **Prompt-only path:** - [ ] Prompt-only justified (all auto-detect criteria hold, OR `prompt` explicitly passed) -- [ ] The verbatim goal line sits between the rails above the remaining-work bullets — prompt-only - writes no file, so the goal travels in the prompt or not at all (engine doc, "Original goal — - mandatory on BOTH paths") +- [ ] The verbatim goal sits between the rails above the remaining-work bullets — below an active + `/goal` first line, which it never displaces — and when the goal has recorded amendments, the + original dated quote travels with EVERY dated amendment line, never collapsed to a single line; + prompt-only writes no file, so the goal travels in the prompt or not at all (engine doc, + "Original goal — mandatory on BOTH paths") - [ ] Claim provenance applied to every inline remaining-work bullet — inherited status marked `UNVERIFIED ()`, not stated as plain fact (engine doc, "Claim provenance") - [ ] Redaction pass swept the prompt (secrets/tokens/credentials/PII replaced with shape markers) diff --git a/plugins/session-flow/skills/handoff/evals/evals.json b/plugins/session-flow/skills/handoff/evals/evals.json index 8e6a9eb13..e39524b24 100644 --- a/plugins/session-flow/skills/handoff/evals/evals.json +++ b/plugins/session-flow/skills/handoff/evals/evals.json @@ -99,14 +99,14 @@ { "id": 8, "name": "prompt-only-still-carries-the-goal", - "prompt": "/handoff prompt — just two lint fixes left on the docs branch.", - "expected_output": "Prompt-only writes no body sections, so the verbatim goal line travels inline between the rails, above the remaining-work bullets. A prompt-only save-point listing only the follow-ups is the exact shape that loses the goal.", + "prompt": "My goal, as I told you this morning: 'make every page of the user docs read consistently before the 1.0 release.' We armed a /goal for it at the start of this session and it's still running. /handoff prompt — just two lint fixes left on the docs branch.", + "expected_output": "Prompt-only writes no body sections, so the goal travels inline between the rails: the active /goal re-arm keeps the FIRST line, the verbatim goal quote comes directly below it, and the remaining-work bullets follow. A prompt-only save-point listing only the follow-ups is the exact shape that loses the goal.", "files": [], "expectations": [ - "The verbatim goal line appears between the rails, above the inline remaining-work bullets", - "The goal is quoted in the user's own words rather than described as the remaining lint work", - "The reasoning recognizes that prompt-only points at no file, so the goal travels in the prompt or not at all", - "An active /goal still occupies the FIRST line between the rails — the goal line does not displace it" + "An active /goal is detected from the conversation, so a /goal re-arm occupies the FIRST line between the rails", + "The verbatim goal quote — 'make every page of the user docs read consistently before the 1.0 release', in the user's own words rather than a description of the remaining lint work — sits directly below the /goal line and above the inline remaining-work bullets", + "The goal quote does not displace the /goal re-arm from the first line, and the bullets do not come between them", + "The reasoning recognizes that prompt-only points at no file, so the goal travels in the prompt or not at all" ] } ] diff --git a/plugins/session-flow/skills/keep-going/SKILL.md b/plugins/session-flow/skills/keep-going/SKILL.md index 3fcd6a777..f33e01a9f 100644 --- a/plugins/session-flow/skills/keep-going/SKILL.md +++ b/plugins/session-flow/skills/keep-going/SKILL.md @@ -60,7 +60,27 @@ itself the thing this skill removes. from the source of truth, per that doc's inspect-real-state invariant: do not infer "it probably finished" or "it probably died" — only the artifact tells you which. -3. **Recover per item — act on evidence.** Classify against the real +3. **Goal alignment — before any recovery ACTION.** When the resume + follows a `/session-flow:handoff`, read that file rather than trusting + memory — and when the handoff's path was lost (a `/clear` without + copying the resume prompt), recover it first with + `/session-flow:find-handoff`. Read its `Original goal` section, then + test the planned next actions against it: **say in one sentence how + the next action serves that goal.** If you cannot, that is drift, not + a wording problem — stop, and either re-derive an action that does + serve the goal or ask the user whether the goal changed. **A handoff + carrying no `Original goal` is itself a defect:** do not infer the goal + from the process the file describes, which is the thing that drifted — + ask the user for it in their own words before continuing, and carry + their answer into the next save-point. + + This check sits between inspection and action deliberately: steps 1-2 + only read, but step 4 resumes and restarts work, and restarting work + that serves a drifted goal re-arms the drift before anything has + tested it. One sentence, not a new stage. Its cost is nothing and its + absence is the only signal that many sessions of faithful execution + were aimed at the wrong thing. +4. **Recover per item — act on evidence.** Classify against the real output and act: - **Progressing** (even if slow) → leave it; report it is alive and moving. Do not kill work that is making progress. @@ -70,26 +90,10 @@ itself the thing this skill removes. - **Dead but safe to redo** → restart it (subject to the autonomy policy below). - **Unrecoverable** → surface it plainly; do not fake a recovery. -4. **Reconcile the main thread — goal alignment is the FIRST check.** - When the interruption followed a `/session-flow:handoff`, read that - file rather than trusting memory — and when the handoff's path was - lost (a `/clear` without copying the resume prompt), recover it first - with `/session-flow:find-handoff`. Read its `Original goal` section, - then test the planned next actions against it: **say in one sentence - how the next action serves that goal.** If you cannot, that is drift, - not a wording problem — stop, and either re-derive an action that does - serve the goal or ask the user whether the goal changed. **A handoff - carrying no `Original goal` is itself a defect:** do not infer the goal - from the process the file describes, which is the thing that drifted — - ask the user for it in their own words before continuing, and carry - their answer into the next save-point. Then restate where the primary - task actually stood — grounded in a fresh read of any plan / checklist - / task artifact backing it, not a prior turn's claim — and continue it. - - One sentence, not a new stage. Its cost is nothing and its absence is - the only signal that many sessions of faithful execution were aimed at - the wrong thing. -5. **Report.** One list: recovered, restarted, still-running, and lost / +5. **Reconcile the main thread.** Restate where the primary task + actually stood — grounded in a fresh read of any plan / checklist / + task artifact backing it, not a prior turn's claim — and continue it. +6. **Report.** One list: recovered, restarted, still-running, and lost / unrecoverable. ## Active-verification protocol — evidence before action @@ -145,8 +149,9 @@ back cleanly and stops. ## Nothing-off-thread case -If the inventory finds no off-thread work, say so and go straight to -step 4: reconcile the main thread from its real state and continue. The +If the inventory finds no off-thread work, say so, run step 3's +goal-alignment check when a handoff backs the resume, then go straight to +step 5: reconcile the main thread from its real state and continue. The interruption may have hit mid-turn on the main thread alone — recovering that is still the job. diff --git a/plugins/session-flow/skills/keep-going/evals/evals.json b/plugins/session-flow/skills/keep-going/evals/evals.json index 984b3c6c2..3373cd95a 100644 --- a/plugins/session-flow/skills/keep-going/evals/evals.json +++ b/plugins/session-flow/skills/keep-going/evals/evals.json @@ -81,10 +81,10 @@ "id": 7, "name": "goal-alignment-is-the-first-check-on-resume", "prompt": "keep going — resume from the handoff at .work/handoffs/20260803T101500Z-handoff-doc-alignment.md, nothing was running in the background.", - "expected_output": "Before restating position or continuing, the skill reads the handoff's Original goal section and tests the planned next actions against it, stating in one sentence how the next action serves that goal. If it cannot state that, it treats the gap as drift — stopping to re-derive an action that serves the goal, or asking whether the goal changed — rather than continuing on the process the file describes.", + "expected_output": "Before any recovery action, and before restating position or continuing, the skill reads the handoff's Original goal section and tests the planned next actions against it, stating in one sentence how the next action serves that goal. If it cannot state that, it treats the gap as drift — stopping to re-derive an action that serves the goal, or asking whether the goal changed — rather than continuing on the process the file describes.", "files": [], "expectations": [ - "Reads the handoff's Original goal section BEFORE reconciling position or continuing the work", + "Reads the handoff's Original goal section BEFORE resuming, restarting, or killing any work and BEFORE reconciling position — the check gates recovery actions, not just the position restatement", "States in one sentence how the planned next action serves that goal", "Treats an unstatable connection as drift — stops and re-derives or asks, rather than proceeding", "Still reconciles the main thread from a fresh read of the backing artifact afterwards and continues" From 4b7beb9a57c9f1a7b267451e02197b6d0d2ef32c Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:48:46 -0400 Subject: [PATCH 5/5] docs(session-flow): fold the goal-alignment stage into both coarse flow summaries Verifier observation: keep-going's one-line flow summary and the README command index elided the new stage without contradicting it; folded in so neither reads as inventory-then-straight-to-recovery. Co-authored-by: Claude Fable 5 --- plugins/session-flow/README.md | 2 +- plugins/session-flow/skills/keep-going/SKILL.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/session-flow/README.md b/plugins/session-flow/README.md index 2b974e413..b4038a7c4 100644 --- a/plugins/session-flow/README.md +++ b/plugins/session-flow/README.md @@ -91,7 +91,7 @@ running again); while a limit still holds it hands back via `handoff` rather tha scheduler. Intent is inferred from the conversation; arguments are optional. ```shell -/session-flow:keep-going # inventory → inspect → recover → reconcile → report +/session-flow:keep-going # inventory → inspect → goal-align → recover → reconcile → report ``` ### find-handoff diff --git a/plugins/session-flow/skills/keep-going/SKILL.md b/plugins/session-flow/skills/keep-going/SKILL.md index f33e01a9f..33b55487c 100644 --- a/plugins/session-flow/skills/keep-going/SKILL.md +++ b/plugins/session-flow/skills/keep-going/SKILL.md @@ -39,7 +39,7 @@ or not. against real output *before* acting; it may be alive and progressing. Either way the flow is identical: inventory → inspect the real state → -act on evidence → continue. +align on the recorded goal → act on evidence → continue. ## Intent comes from the conversation, not the arguments