Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions internal/agentkit/agentkit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,101 @@ func TestCoordinatorPrompt_StructuralResilience(t *testing.T) {
})
}

func TestWorkerPrompt_HardenedStructure(t *testing.T) {
// Verify the hardened worker.md has inline constraints, no separate
// Constraints section, mandatory language, reservation failure recovery,
// and stays under 35 lines.
data, err := content.ReadFile("content/agents/worker.md")
if err != nil {
t.Fatalf("read worker.md: %v", err)
}
text := string(data)
lines := strings.Split(text, "\n")

// Line count: must be <= 35 (design decision D4).
if len(lines) > 35 {
t.Errorf("worker.md has %d lines, want <= 35", len(lines))
}

// No separate "## Constraints" section.
if strings.Contains(text, "## Constraints") {
t.Error("worker.md still contains a '## Constraints' heading; should be removed")
}

// stepWindow returns the text of the line at idx plus the next windowSize
// lines, joined together. This allows assertions that tolerate content
// being split across sub-bullets without requiring single-line co-location.
const windowSize = 3
stepWindow := func(idx int) string {
end := idx + windowSize + 1
if end > len(lines) {
end = len(lines)
}
return strings.Join(lines[idx:end], "\n")
}

// Find the comms_reserve step and verify it contains MUST or NEVER
// language about file editing.
reserveIdx := -1
for i, line := range lines {
if strings.Contains(line, "comms_reserve") {
reserveIdx = i
break
}
}
if reserveIdx == -1 {
t.Fatal("worker.md: no line containing 'comms_reserve' found")
}
reserveStep := lines[reserveIdx]
if !strings.Contains(reserveStep, "MUST") && !strings.Contains(reserveStep, "NEVER") {
t.Errorf("comms_reserve step lacks MUST/NEVER constraint: %q", reserveStep)
}
if !strings.Contains(reserveStep, "NEVER") {
t.Errorf("comms_reserve step missing NEVER constraint about file editing: %q", reserveStep)
}

// Verify forge_progress step contains "MUST".
var progressStep string
for _, line := range lines {
if strings.Contains(line, "forge_progress") {
progressStep = line
break
}
}
if progressStep == "" {
t.Fatal("worker.md: no line containing 'forge_progress' found")
}
if !strings.Contains(progressStep, "MUST") {
t.Errorf("forge_progress step lacks MUST language: %q", progressStep)
}

// Verify hivemind_store step contains "MUST".
var storeStep string
for _, line := range lines {
if strings.Contains(line, "hivemind_store") {
storeStep = line
break
}
}
if storeStep == "" {
t.Fatal("worker.md: no line containing 'hivemind_store' found")
}
if !strings.Contains(storeStep, "MUST") {
t.Errorf("hivemind_store step lacks MUST language: %q", storeStep)
}

// Verify reservation failure recovery instruction is co-located with the
// comms_reserve step. Search within a window of lines (current + next 3)
// so the assertion tolerates sub-bullet reformatting.
reserveWindow := stepWindow(reserveIdx)
if !strings.Contains(reserveWindow, "comms_send") {
t.Errorf("comms_reserve step window lacks comms_send recovery instruction:\n%s", reserveWindow)
}
if !strings.Contains(reserveWindow, "STOP") {
t.Errorf("comms_reserve step window lacks STOP instruction for reservation failure:\n%s", reserveWindow)
}
}

func TestSkillTemplates_HaveNameField(t *testing.T) {
// Walk the embedded content filesystem and verify every SKILL.md
// has a "name: <directory-name>" field in its YAML frontmatter.
Expand Down
21 changes: 7 additions & 14 deletions internal/agentkit/content/agents/worker.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,10 @@ Executes scoped subtasks and reports to coordinator.

## Checklist

1. `comms_init` — initialize comms first
2. `hivemind_find` — check for prior learnings before coding
3. `comms_reserve` — reserve assigned files exclusively
4. Implement changes to reserved files
5. `forge_progress` — report at 25%, 50%, 75% milestones
6. `hivemind_store` — store any learnings discovered
7. `forge_complete` — mark subtask as done

## Constraints

- Only edit files you have reserved
- Report progress at regular intervals
- Store learnings for future agents
- Never modify files outside your assignment
1. `comms_init` — MUST initialize comms before any other action
2. `hivemind_find` — MUST check for prior learnings before coding
3. `comms_reserve` — MUST reserve assigned files exclusively. NEVER edit unreserved files. If reservation fails, expires, or is released: STOP and report to coordinator via `comms_send`
4. Implement changes — MUST only modify reserved files
5. `forge_progress` — MUST report progress at 25%, 50%, 75% milestones
6. `hivemind_store` — MUST store learnings discovered (gotchas, patterns, decisions)
7. `forge_complete` — MUST mark subtask as done
2 changes: 2 additions & 0 deletions openspec/changes/harden-worker-prompt/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: unbound-force
created: 2026-08-02
64 changes: 64 additions & 0 deletions openspec/changes/harden-worker-prompt/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
## Context

`internal/agentkit/content/agents/worker.md` is 27 lines with two sections: a numbered Checklist (lines 14-20) and a flat-bullet Constraints section (lines 22-27). Under LLM context compression, the Checklist survives well because it has structure (numbered steps with tool names), but the Constraints section is vulnerable to summarization because it's a flat list of behavioral restrictions separated from the actions they govern.

The proposal identifies three specific fragilities: reservation-check constraints buried in a droppable section, no recovery path for reservation failures, and progress reporting steps that look like optional middleware.

## Goals / Non-Goals

### Goals
- Integrate critical constraints inline with the checklist steps they govern, so they survive compression as a unit
- Add an explicit recovery path for reservation failures (expired, released, or failed to acquire)
- Frame progress reporting and learning storage as mandatory structural steps, not optional extras
- Consolidate redundant constraint phrasings ("Only edit files you have reserved" and "Never modify files outside your assignment") into a single, clear statement at the point of action

### Non-Goals
- Changing the worker's behavioral semantics — the worker does the same things, just described more durably
- Modifying the coordinator prompt or forge tool implementations
- Adding new MCP tools or changing tool response shapes
- Restructuring other agent files (background-worker, coordinator) — those are separate concerns
- Adding programmatic enforcement of reservations at the tool level — this change is prompt-level hardening only

## Decisions

### D1: Inline constraints with checklist steps, eliminate separate Constraints section

The Constraints section will be removed entirely. Each constraint will be integrated into the checklist step it governs:

- "Only edit files you have reserved" / "Never modify files outside your assignment" → merged into step 3 (`comms_reserve`) and step 4 (implement)
- "Report progress at regular intervals" → already expressed in step 5, will be strengthened with mandatory language
- "Store learnings for future agents" → already expressed in step 6, will be strengthened

**Rationale**: A single numbered list is the most compression-resistant prompt structure. Constraints co-located with their actions form atomic units that a compressor must keep or drop together. This aligns with Autonomous Collaboration (Principle I) by making the reservation enforcement — the primary collision prevention mechanism — structurally durable.

### D2: Add reservation failure recovery as a sub-step of step 3

Step 3 will include an explicit "if reservation fails" clause instructing the worker to STOP and report to the coordinator via `comms_send`. This covers three failure modes: initial acquisition failure, TTL expiration, and coordinator-initiated release.

**Rationale**: Without recovery guidance, a worker with a lost reservation will either stall silently or proceed unsafely. The recovery path uses the existing comms protocol (Autonomous Collaboration) and produces an observable failure state (Observable Quality, Principle III).

### D3: Use imperative "MUST" / "NEVER" language inline

Rather than soft phrasing ("Only edit..."), constraints will use imperative RFC 2119 language: "NEVER edit unreserved files", "MUST report progress". This language is more likely to be preserved by compressors because it signals importance.

**Rationale**: Compressors weight imperative/directive language higher than descriptive language when deciding what to preserve.

### D4: Keep the file under 35 lines

The hardened version should stay concise. Adding recovery instructions and inline constraints should not balloon the file. Target: under 35 lines (up from 27).

**Rationale**: Longer prompts are themselves more susceptible to compression. The goal is to make 27 lines more durable, not to add 50 lines of instructions.

## Risks / Trade-offs

### R1: Removing the Constraints section reduces scanability
**Risk**: Developers reviewing the worker prompt can no longer scan a dedicated section for all constraints.
**Mitigation**: The checklist with inline constraints is still scannable — each step now reads as "do X, and Y is the boundary." The file is short enough that full reading takes seconds.

### R2: Compression resistance is not guaranteed
**Risk**: No prompt structure is fully compression-proof. Even inline constraints could be summarized away by an aggressive compressor.
**Mitigation**: This change makes compression resistance significantly better, not perfect. The structural approach (constraints co-located with actions) is a well-known hardening pattern. Further defense would require programmatic enforcement at the tool level (out of scope).

### R3: Imperative language may seem harsh to human readers
**Risk**: "NEVER" and "MUST" phrasing reads differently than "Only edit...".
**Mitigation**: These prompts are consumed by LLM agents, not human users. RFC 2119 language is the project convention for requirements (per constitution and convention packs). Human-facing documentation uses softer language.
58 changes: 58 additions & 0 deletions openspec/changes/harden-worker-prompt/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
## Why

The `worker.md` agent prompt (27 lines) contains critical behavioral constraints — file reservation enforcement, progress reporting, and learning storage — in a separate "Constraints" section that is vulnerable to context compression. When LLM context windows fill up, compressors tend to summarize or drop flat bullet lists of constraints while preserving structured checklists. This means workers can silently edit unreserved files (causing collisions with other workers), skip progress reporting (leaving coordinators blind), or ignore learning storage.

This is the same class of vulnerability identified in [unbound-force/unbound-force#346](https://github.com/unbound-force/unbound-force/issues/346). Fixing it in `worker.md` hardens the most critical agent in the forge pipeline — the one that actually touches code files.

Ref: [unbound-force/replicator#49](https://github.com/unbound-force/replicator/issues/49)

## What Changes

Restructure `worker.md` to make critical constraints compression-resistant by integrating them into the numbered checklist steps rather than keeping them in a separate section. Add a recovery path for reservation failures. Consolidate redundant constraint phrasings.

## Capabilities

### New Capabilities
- `reservation-failure-recovery`: Worker now has explicit instructions for handling expired, released, or failed reservations — STOP and report to coordinator via `comms_send`.

### Modified Capabilities
- `worker-checklist`: Checklist steps now embed their constraints inline (e.g., step 3 includes "NEVER edit unreserved files" directly). Progress reporting and learning storage are framed as mandatory structural steps, not optional middleware.

### Removed Capabilities
- _None_

## Impact

- **File changed**: `internal/agentkit/content/agents/worker.md`
- **Embedded asset**: This file is embedded via Go's `embed` package into the binary, so changes take effect at next build
- **Behavioral**: Workers become more resilient to context compression — critical constraints survive alongside the checklist they govern
- **Test impact**: Parity tests for agent file embedding should still pass (content changes, not structural changes to the embedding mechanism)
- **Coordinator impact**: None — the coordinator's protocol for spawning/reviewing workers is unchanged

## Constitution Alignment

Assessed against the Unbound Force org constitution.

### I. Autonomous Collaboration

**Assessment**: PASS

This change strengthens autonomous collaboration by ensuring worker agents retain their reservation constraints and progress reporting requirements even under context compression. Reservation enforcement is the mechanism that prevents workers from colliding on shared files. Progress reporting via `forge_progress` is the mechanism coordinators use to track worker state. Both are artifact-based coordination patterns that this change makes more durable.

### II. Composability First

**Assessment**: N/A

This change modifies an agent prompt file. It does not affect binary independence, Dewey integration graceful degradation, or database schema compatibility. The worker agent continues to function identically regardless of which external services are available.

### III. Observable Quality

**Assessment**: PASS

Progress reporting (`forge_progress` at milestones) is the worker's primary observability mechanism. By integrating it as a mandatory structural step rather than a droppable constraint, this change improves the reliability of observable quality in multi-agent workflows. No changes to MCP tool response shapes or parity test expectations.

### IV. Testability

**Assessment**: N/A

This change modifies prompt content, not testable code. The embedding mechanism and parity tests are unaffected. No new external service dependencies are introduced.
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
## ADDED Requirements

### Requirement: Reservation Failure Recovery

The worker MUST include an explicit recovery instruction for reservation failures. If `comms_reserve` fails, the reservation expires (TTL), or the reservation is released by the coordinator, the worker MUST STOP all work and report the failure to the coordinator via `comms_send`.

#### Scenario: Initial reservation acquisition fails
- **GIVEN** a worker has initialized comms and is attempting to reserve files
- **WHEN** `comms_reserve` returns an error (files already reserved by another worker)
- **THEN** the worker MUST stop, send a message to the coordinator via `comms_send` describing which files could not be reserved, and NOT proceed to implementation

#### Scenario: Worker discovers reservation no longer held
- **GIVEN** a worker has reserved files and is implementing changes
- **WHEN** the coordinator notifies the worker via `comms_send` that its reservation has been released, or the worker discovers the loss through a subsequent `comms_reserve` call
- **THEN** the worker MUST stop editing files immediately and report the situation to the coordinator via `comms_send`

Note: Detection depends on external notification (coordinator message) or a subsequent reserve/release call. There is no proactive TTL-expiry notification mechanism at the prompt level.

### Requirement: Inline Constraint Co-location

Critical behavioral constraints MUST be co-located with the checklist step they govern, not in a separate section. Each checklist step MUST contain both the action and its boundary condition as an atomic unit.

#### Scenario: Constraints are co-located with actions
- **GIVEN** the restructured worker.md content is read from the embedded FS
- **WHEN** the checklist step containing `comms_reserve` is examined
- **THEN** the same step text contains at least one MUST or NEVER constraint keyword about file editing

### Requirement: Mandatory Progress Reporting

Progress reporting via `forge_progress` MUST be described as a mandatory structural step, not an optional reporting activity. The checklist step MUST use imperative language ("MUST report") rather than descriptive language.

#### Scenario: Progress step uses mandatory language
- **GIVEN** the restructured worker.md content is read from the embedded FS
- **WHEN** the checklist step containing `forge_progress` is examined
- **THEN** the step text contains the word "MUST"

### Requirement: File Conciseness

The restructured worker.md MUST remain under 35 lines (including frontmatter) to avoid creating a new compression vulnerability through increased prompt length.

#### Scenario: File stays concise
- **GIVEN** the restructured worker.md
- **WHEN** the total line count is measured
- **THEN** the file MUST be under 35 lines (including frontmatter)

## MODIFIED Requirements

### Requirement: Checklist Structure

Previously: The checklist was a 7-step numbered list (lines 14-20) with constraints in a separate "Constraints" section (lines 22-27).

The checklist MUST be a self-contained numbered list where each step includes both the action and its associated constraints inline. The separate "Constraints" section SHALL be removed. Redundant constraint phrasings ("Only edit files you have reserved" and "Never modify files outside your assignment") MUST be consolidated into a single clear statement at the point of action.

#### Scenario: File reservation constraint is inline
- **GIVEN** the worker prompt has been restructured
- **WHEN** a reader examines step 3 (`comms_reserve`)
- **THEN** the step includes an explicit "NEVER edit unreserved files" constraint in the same text block

#### Scenario: Separate Constraints section removed
- **GIVEN** the worker prompt has been restructured
- **WHEN** the full prompt content is examined
- **THEN** there is no separate "## Constraints" heading or section

### Requirement: Learning Storage Framing

Previously: Step 6 read "hivemind_store — store any learnings discovered" and constraint "Store learnings for future agents" was in a separate section.

The learning storage step MUST use mandatory language and SHOULD include guidance on what constitutes a useful learning (gotchas, patterns, decisions).

#### Scenario: Learning storage reads as mandatory
- **GIVEN** the restructured worker prompt
- **WHEN** a reader examines the learning storage step
- **THEN** the step uses "MUST" language and is not dismissable as optional

## REMOVED Requirements

### Requirement: Separate Constraints Section

The standalone "## Constraints" section with flat bullet points is removed. All constraint content is migrated into the checklist steps. This removal is the core mechanism of the hardening — eliminating the structurally vulnerable section that compressors are likely to summarize away.
Loading