-
Notifications
You must be signed in to change notification settings - Fork 9
fix: harden /handoff command against DCP context compression #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| schema: unbound-force | ||
| created: 2026-08-02 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| ## Context | ||
|
|
||
| The `/handoff` command (`internal/agentkit/content/commands/handoff.md`) is a 25-line embedded prompt that instructs agents to perform a 5-step session teardown. The current structure uses a numbered list for step ordering and a separate section for the handoff note template. Under DCP context compression, numbered lists can be reordered or merged, and standalone template sections can be dropped entirely. | ||
|
|
||
| The related issue [unbound-force/unbound-force#346](https://github.com/unbound-force/unbound-force/issues/346) demonstrated that `/review-pr`'s confirmation gate was bypassed under the same compression conditions. The fix there added explicit "MANDATORY GATE" markers and session-resume guards -- patterns we adapt here. | ||
|
|
||
| ## Goals / Non-Goals | ||
|
|
||
| ### Goals | ||
| - Make step ordering survive DCP context compression by adding explicit ordering constraints | ||
| - Embed the handoff note template directly in the `org_session_end` step so it cannot be separated from the action | ||
| - Add a forge precondition check that prevents releasing reservations while workers are active | ||
| - Keep the prompt concise -- hardening should add minimal token overhead | ||
|
|
||
| ### Non-Goals | ||
| - Changing the `org_session_end` MCP tool itself (the tool is fine; the prompt is the issue) | ||
| - Adding runtime enforcement of step ordering (this is a prompt-level fix, not a code-level fix) | ||
| - Restructuring other commands for DCP resilience (that's a separate effort per command) | ||
| - Testing DCP compression behavior (compression is non-deterministic and not controllable by tests; structural content assertions verify the hardening properties, while DCP behavior is accepted as a known limitation per R2) | ||
|
|
||
| ## Decisions | ||
|
|
||
| ### D1: Inline the template into the tool call step | ||
|
|
||
| **Decision**: Move the handoff note template from a separate "Handoff Note Template" section into the `org_session_end` step itself, formatted as a code block showing the expected argument structure. | ||
|
|
||
| **Rationale**: When the template is a separate section, compression can drop it while keeping the workflow. When it's part of the step instruction, dropping it means dropping the step -- which is much harder for compression to justify since the step contains a tool call. This aligns with the Autonomous Collaboration principle: the artifact (handoff note) is fully described at the point of creation. | ||
|
|
||
| ### D2: Add explicit ordering constraint language | ||
|
|
||
| **Decision**: Add "Steps MUST execute in this exact order -- do not reorder or parallelize" at the top of the workflow section, and add sequencing rationale to each step. | ||
|
|
||
| **Rationale**: Numbered lists are not strong enough ordering signals under compression. Explicit MUST language with rationale for each dependency makes reordering require actively contradicting a stated constraint. | ||
|
|
||
| ### D3: Forge precondition as SHOULD, not MUST | ||
|
|
||
| **Decision**: The forge worker check is a SHOULD ("Check for active forge workers before proceeding") rather than a MUST hard gate. | ||
|
|
||
| **Rationale**: Per the Composability First principle, the handoff command must remain functional even when forge tools are unavailable. A MUST gate would break handoff in environments where forge is not configured. The check uses `forge_status` which is an existing MCP tool, maintaining artifact-based coordination. | ||
|
|
||
| ### D4: Single-file change | ||
|
|
||
| **Decision**: All hardening changes are contained within `handoff.md`. No changes to Go source, MCP tools, or other embedded assets. | ||
|
|
||
| **Rationale**: This is a prompt-level fix for a prompt-level vulnerability. The underlying tools (`comms_release_all`, `org_session_end`, `forge_status`) function correctly; the issue is that the prompt instructions degrade under compression. | ||
|
|
||
| ## Risks / Trade-offs | ||
|
|
||
| ### R1: Prompt length increase | ||
| Adding ordering constraints, inline templates, and precondition checks increases the prompt from ~25 lines to ~45-50 lines. This adds token overhead to every session that loads the command. Accepted because the reliability improvement outweighs the marginal token cost. | ||
|
|
||
| ### R2: Cannot fully prevent compression reordering | ||
| No prompt structure can guarantee preservation under aggressive compression. The hardening reduces the probability of step reordering and template loss but does not eliminate it. This is an inherent limitation of prompt-based workflows. | ||
|
|
||
| ### R3: Forge precondition may produce false positives | ||
| If `forge_status` returns stale data (e.g., a worker that crashed without cleanup), the agent may hesitate to proceed with handoff. The SHOULD (not MUST) designation means the agent can proceed with a warning rather than blocking entirely. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| ## Why | ||
|
|
||
| The `/handoff` command in `internal/agentkit/content/commands/handoff.md` defines a 5-step session teardown workflow where step ordering is critical: summarize before release, release before sync, sync before session end. Under DCP (Dynamic Context Protocol) context compression, three fragilities emerge: | ||
|
|
||
| 1. **Step ordering lost** -- compression can flatten or reorder the numbered steps, causing an agent to release reservations before summarizing (losing awareness of held files), or sync before closing cells (persisting stale state). | ||
| 2. **Handoff note template dropped** -- the structured template (Completed, In Progress, Blocked, Next Steps, Gotchas) is exactly the kind of content compression reduces to "write handoff notes," losing critical categories the next session depends on. | ||
| 3. **No forge precondition check** -- nothing prevents an agent from invoking `/handoff` mid-forge and releasing active worker reservations. | ||
|
|
||
| This is the same class of vulnerability as [unbound-force/unbound-force#346](https://github.com/unbound-force/unbound-force/issues/346), where the `/review-pr` command's confirmation gate was bypassed under compressed context. | ||
|
|
||
| Fixes [#50](https://github.com/unbound-force/replicator/issues/50). | ||
|
|
||
| ## What Changes | ||
|
|
||
| Harden `internal/agentkit/content/commands/handoff.md` against DCP context compression by restructuring the prompt to survive lossy summarization. | ||
|
|
||
| ## Capabilities | ||
|
|
||
| ### New Capabilities | ||
| - `forge-precondition-guard`: Handoff command checks for active forge workers before releasing reservations | ||
|
|
||
| ### Modified Capabilities | ||
| - `handoff-workflow`: Restructured with DCP-resistant ordering constraints, inline template, and mandatory precondition check | ||
|
|
||
| ### Removed Capabilities | ||
| - None | ||
|
|
||
| ## Impact | ||
|
|
||
| - **File**: `internal/agentkit/content/commands/handoff.md` -- single file change | ||
| - **Behavioral**: Agents invoking `/handoff` will now verify no forge workers are active before proceeding, and will produce structured handoff notes even under compressed context | ||
| - **Embedded asset**: The file is embedded via `agentkit.go` and distributed with the binary -- a rebuild is required to pick up the change | ||
| - **Test**: The agentkit embedding test (`agentkit_test.go`) validates file presence. A new structural content test will verify the hardening properties (ordering constraint placement, template co-location, forge precondition presence) on the embedded file | ||
|
|
||
| ## Constitution Alignment | ||
|
|
||
| Assessed against the Replicator constitution (`.specify/memory/constitution.md`). | ||
|
|
||
| ### I. Autonomous Collaboration | ||
|
|
||
| **Assessment**: PASS | ||
|
|
||
| This change improves artifact-based collaboration by ensuring handoff notes remain structured and complete under compression. The precondition check uses the existing `forge_status` tool via MCP, maintaining artifact-based coordination rather than introducing runtime coupling. | ||
|
|
||
| ### II. Composability First | ||
|
|
||
| **Assessment**: N/A | ||
|
|
||
| This change modifies an embedded prompt file, not a tool or binary capability. The handoff command remains functional whether forge tools are available or not -- the precondition check is a SHOULD, not a hard gate that blocks session teardown. | ||
|
|
||
| ### III. Observable Quality | ||
|
|
||
| **Assessment**: PASS | ||
|
|
||
| The structured handoff note template produces consistently formatted output that downstream consumers (the next session's `org_session_start`) can parse reliably. Moving the template inline with the tool call step eliminates the gap between instruction and execution. | ||
|
|
||
| ### IV. Testability | ||
|
|
||
| **Assessment**: PASS | ||
|
|
||
| Structural content assertions verify the hardening properties of the embedded markdown (ordering constraint placement, template co-location, forge precondition presence). DCP compression behavior is inherently untestable and is accepted as a known limitation. No external services or mutable state are involved. |
66 changes: 66 additions & 0 deletions
66
openspec/changes/harden-handoff-dcp/specs/handoff-dcp-hardening.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| ## ADDED Requirements | ||
|
|
||
| ### Requirement: Handoff step ordering constraint | ||
|
|
||
| The `/handoff` command workflow MUST include an explicit ordering constraint statement before the step list. The constraint MUST use RFC 2119 MUST language to prohibit reordering or parallelizing steps. | ||
|
|
||
| #### Scenario: Ordering constraint is structurally distinct from step list | ||
| - **GIVEN** the hardened `handoff.md` file is read | ||
| - **WHEN** the workflow section is examined | ||
| - **THEN** the ordering constraint statement MUST appear as a distinct paragraph before the first numbered step in the markdown file | ||
|
|
||
| ### Requirement: Forge precondition check | ||
|
|
||
| The `/handoff` command SHOULD instruct the agent to check for active forge workers before executing `comms_release_all`. If active workers are detected, the agent SHOULD warn the user and request confirmation before proceeding. If the agent has no active forge context (no known `epic_id` or `project_key`), the forge precondition check SHOULD be skipped, same as when forge tools are unavailable. | ||
|
|
||
| #### Scenario: Handoff invoked with active forge workers | ||
| - **GIVEN** a forge session is active with running workers | ||
| - **WHEN** the agent invokes `/handoff` | ||
| - **THEN** the agent SHOULD check `forge_status` and warn that active workers will lose their reservations | ||
| - **AND** the agent SHOULD request user confirmation before calling `comms_release_all` | ||
|
|
||
| #### Scenario: Handoff invoked with no forge activity | ||
| - **GIVEN** no forge session is active | ||
| - **WHEN** the agent invokes `/handoff` | ||
| - **THEN** the agent SHOULD proceed through the workflow without blocking on the forge check | ||
|
|
||
| #### Scenario: Forge tools unavailable | ||
| - **GIVEN** forge tools are not available in the current MCP session | ||
| - **WHEN** the agent invokes `/handoff` | ||
| - **THEN** the agent MUST proceed with handoff without the forge check (graceful degradation) | ||
|
|
||
| ### Requirement: Inline handoff note template | ||
|
|
||
| The handoff note template MUST be embedded directly within the `org_session_end` workflow step rather than in a separate section. The template MUST specify all five categories: Completed, In Progress, Blocked, Next Steps, Gotchas. | ||
|
|
||
| #### Scenario: Handoff note template is co-located with tool call | ||
| - **GIVEN** the hardened `handoff.md` file is read | ||
| - **WHEN** the `org_session_end` step is examined | ||
| - **THEN** the handoff note template (all five categories) MUST appear within the same markdown section as the `org_session_end` tool call instruction | ||
|
|
||
| ### Requirement: Step dependency rationale | ||
|
|
||
| Each workflow step MUST include a brief rationale explaining why it depends on the previous step completing first. | ||
|
|
||
| #### Scenario: Each step contains a dependency rationale | ||
| - **GIVEN** the hardened `handoff.md` file is read | ||
| - **WHEN** each numbered workflow step is examined | ||
| - **THEN** each step MUST contain a rationale clause following the tool call instruction that explains why it depends on the previous step | ||
|
|
||
| ## MODIFIED Requirements | ||
|
|
||
| ### Requirement: Workflow section structure | ||
|
|
||
| Previously: A numbered list of 5 steps followed by a separate "Handoff Note Template" section. | ||
|
|
||
| The workflow section MUST now contain: | ||
| 1. An ordering constraint header | ||
| 2. An optional forge precondition check | ||
| 3. The existing 5 steps with inline rationale for each dependency | ||
| 4. The handoff note template embedded within step 5 (`org_session_end`) | ||
|
|
||
| The separate "Handoff Note Template" section MUST be removed. | ||
|
|
||
| ## REMOVED Requirements | ||
|
|
||
| None. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| <!-- | ||
| [P] marks tasks eligible for parallel execution. | ||
| Add [P] when a task: (a) touches different files from | ||
| other [P] tasks in the group, (b) has no dependency | ||
| on prior tasks in the group, (c) can safely execute | ||
| without ordering constraints. | ||
| Do NOT add [P] when tasks modify the same file -- | ||
| parallel workers will cause merge conflicts. | ||
| Tasks without [P] run sequentially first, then [P] | ||
| tasks run in parallel. | ||
| --> | ||
|
|
||
| ## 1. Harden handoff.md | ||
|
|
||
| All tasks in this group modify the same file (`internal/agentkit/content/commands/handoff.md`), so none are parallel-eligible. | ||
|
|
||
| - [x] 1.1 Add ordering constraint header to the workflow section: insert "Steps MUST execute in this exact order -- do not reorder or parallelize" before the numbered step list | ||
| - [x] 1.2 Add forge precondition check before the existing step 1 (renumber existing steps accordingly): "SHOULD check for active forge workers. If workers are active, warn and request confirmation before proceeding. If forge tools are unavailable or the agent has no active forge context, skip this check." | ||
| - [x] 1.3 Add step dependency rationale to each workflow step: brief inline explanation of why each step depends on the previous one completing first (e.g., "summarize first so you know what to report in handoff notes") | ||
| - [x] 1.4 Inline the handoff note template into the `org_session_end` step: move the 5-category template (Completed, In Progress, Blocked, Next Steps, Gotchas) from the separate "Handoff Note Template" section into the step 5 instruction, formatted as a code block showing the expected `handoff_notes` argument structure | ||
| - [x] 1.5 Remove the separate "## Handoff Note Template" section (currently the last section in the file) | ||
|
|
||
| ## 2. Verify | ||
|
|
||
| - [x] 2.1 Run `make build` to verify the embedded asset compiles cleanly | ||
| - [x] 2.2 Run `make test` to verify agentkit embedding tests pass (file presence and prefix matching) | ||
| - [x] 2.3 Add a structural content test in `internal/agentkit/agentkit_test.go` that reads the embedded `handoff.md` and asserts: (1) ordering constraint text appears before the first numbered step, (2) the handoff note categories (Completed, In Progress, Blocked, Next Steps, Gotchas) appear within the `org_session_end` step section, (3) the separate "## Handoff Note Template" section is absent, (4) the forge precondition check text is present | ||
| - [x] 2.4 Verify constitution alignment: confirm the forge check uses SHOULD (not MUST), no new runtime dependencies are introduced, and the handoff note template is co-located with the tool call instruction | ||
| <!-- spec-review: passed --> | ||
| <!-- code-review: passed --> |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[MEDIUM] Category co-location check could be stronger
afterSessionEndis everything fromorg_session_endto the end of the file. If the categories appeared in a completely different section that happens to followorg_session_end, this would still pass. The spec requires co-location "within the same markdown section."A stronger assertion would find the next
##heading afterorg_session_endand verify categories appear before it. Not blocking — the test is directionally correct.