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
6 changes: 5 additions & 1 deletion skills/first-officer/references/pi-first-officer-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Use `spacedock dispatch build` with `host: "pi"` in the input JSON. Forward the

A Pi first officer may dispatch via `subagent(...)` when that tool is available. The task must include the emitted dispatch-file prompt or the dispatch file content, the workflow directory, the entity path, and the completion checklist. The child must load the Spacedock ensign skill and Pi ensign runtime adapter before working.

For Spacedock stage dispatches through `pi-subagents`, call `subagent(...)` with explicit `context: "fresh"`. Do not rely on the worker agent's default context; Spacedock stage workers must be seeded by the task prompt/dispatch content, workflow directory, entity file, and completion checklist rather than inherited first-officer transcript context.

For Spacedock stage dispatches through `pi-subagents`, do not use the `subagent(... acceptance: ...)` contract. Put acceptance requirements in the task prompt/dispatch content instead. Spacedock owns the independent implementation-to-validation workflow: the gate is verification via entity stage reports, product/state commits, and independent validation, not same-agent acceptance finalization by the child that did the work.

## Awaiting Completion
Expand All @@ -27,7 +29,9 @@ For `pi-agent-teams`, completion is observed through the adapter's task/member n

## Follow-up and Reuse

Fresh redispatch is the default safe behavior for the first Pi slice. If a Pi substrate exposes a resumable worker handle, record only the minimum metadata needed to prevent stale reuse mistakes: worker label, substrate, run/session handle, entity slug, stage, state, and completion epoch. A follow-up assignment must increment the epoch, and a previous completion must never satisfy the new epoch.
Fresh redispatch is the default safe behavior for the first Pi slice. Normal follow-up and retry dispatches are fresh assignment cycles, not context resumes. If a Pi substrate exposes a resumable worker handle, record only the minimum metadata needed to prevent stale reuse mistakes: worker label, substrate, run/session handle, entity slug, stage, state, and completion epoch. A follow-up assignment must increment the epoch, and a previous completion must never satisfy the new epoch.

A non-fresh resume is only allowed as an explicit manual/debug exception. Mark the dispatch visibly as a manual/debug resume and tie it to durable metadata in the entity stage evidence, including worker label, substrate, run/session handle, entity slug, stage, state, and completion epoch.

## Shutdown

Expand Down
66 changes: 66 additions & 0 deletions skills/integration/skill_surface_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,37 @@ func TestPiRuntimeAdaptersAreLoadable(t *testing.T) {
}
}

func TestPiFirstOfficerRuntimeRequiresFreshSubagentContextForStages(t *testing.T) {
root := skillsRoot(t)
path := filepath.Join(root, "first-officer", "references", "pi-first-officer-runtime.md")
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read Pi first-officer runtime: %v", err)
}
content := string(data)
dispatch := sectionAfter(content, "## Dispatch")
if dispatch == "" {
t.Fatal("Pi first-officer runtime is missing the Dispatch section")
}

required := []string{
"Spacedock stage dispatches",
"pi-subagents",
"subagent(...)",
"context: \"fresh\"",
"Do not rely on the worker agent's default context",
"task prompt/dispatch content",
"workflow directory",
"entity file",
"completion checklist",
}
for _, want := range required {
if !strings.Contains(dispatch, want) {
t.Errorf("Pi first-officer Dispatch section does not contain required fresh-context invariant %q", want)
}
}
}

func TestPiFirstOfficerRuntimeForbidsSubagentAcceptanceForStages(t *testing.T) {
root := skillsRoot(t)
path := filepath.Join(root, "first-officer", "references", "pi-first-officer-runtime.md")
Expand Down Expand Up @@ -135,6 +166,41 @@ func TestPiFirstOfficerRuntimeForbidsSubagentAcceptanceForStages(t *testing.T) {
}
}

func TestPiFirstOfficerRuntimeFollowupsAreFreshByDefault(t *testing.T) {
root := skillsRoot(t)
path := filepath.Join(root, "first-officer", "references", "pi-first-officer-runtime.md")
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read Pi first-officer runtime: %v", err)
}
content := string(data)
followup := sectionAfter(content, "## Follow-up and Reuse")
if followup == "" {
t.Fatal("Pi first-officer runtime is missing the Follow-up and Reuse section")
}

required := []string{
"Fresh redispatch is the default safe behavior",
"Normal follow-up and retry dispatches are fresh assignment cycles",
"not context resumes",
"increment the epoch",
"previous completion must never satisfy the new epoch",
"non-fresh resume is only allowed as an explicit manual/debug exception",
"worker label",
"substrate",
"run/session handle",
"entity slug",
"stage",
"state",
"completion epoch",
}
for _, want := range required {
if !strings.Contains(followup, want) {
t.Errorf("Pi first-officer Follow-up and Reuse section does not contain required fresh-retry invariant %q", want)
}
}
}

func TestUserSkillReferenceClosureResolves(t *testing.T) {
root := skillsRoot(t)
for _, skill := range userSkills {
Expand Down
Loading