Stop the handoff carrying the write Orchestra asked for

Handoff.Command is the last command observed in the pane. The handoff prompt
tells the agent to write .orchestra-handoff-report.md and stop, so that write
is almost always the last command there. Validate then rejects it as circular:
"must not point to a handoff or report".

Every phase rotation therefore failed on Orchestra's own instruction. Live on
run 5, one stage past F36.

lastObservedCommand now skips commands the validator would call circular, so
the successor gets the last command that was real work. The rule stays in
continuity and is asked, not restated, which is how F36 happened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xsXyr5J1RACo71YeKG3Pu
This commit is contained in:
2026-08-28 01:45:11 +04:00
parent 294cbcb078
commit cb7782db01
3 changed files with 49 additions and 2 deletions
+6
View File
@@ -151,6 +151,12 @@ const maxAuthoredAction = 2*maxAuthoredLine + len(" — ")
var circularAction = regexp.MustCompile(`(?i)handoff|report\.md|^continue the task`)
var circularCommand = regexp.MustCompile(`(?i)\.orchestra-handoff|handoff-report|report\.md`)
// IsCircularCommand reports whether Validate would reject this as a command
// that points at a handoff. Producers need the same answer before they build a
// handoff, so the rule lives in one place rather than being restated as a
// second regexp that can drift.
func IsCircularCommand(s string) bool { return circularCommand.MatchString(s) }
func (h Handoff) Validate() error {
if strings.TrimSpace(h.Meta.ID) == "" || !reasons[h.Meta.Reason] || h.Meta.RotationIndex < 0 {
return errors.New("invalid handoff meta")
+12 -2
View File
@@ -538,9 +538,19 @@ func (a CLIAdapter) lastObservedCommand(s Session) string {
return ""
}
for i := len(calls) - 1; i >= 0; i-- {
if calls[i].Kind == "command" {
return calls[i].Key
if calls[i].Kind != "command" {
continue
}
// Skip the write Orchestra itself asked for. The handoff prompt tells
// the agent to write HandoffReportFile and stop, so that write is
// almost always the last command in the pane. Carrying it into
// Handoff.Command made every rotation fail its own circularity check
// with "must not point to a handoff or report" (F37). The successor
// wants the last command that was real work.
if continuity.IsCircularCommand(calls[i].Key) {
continue
}
return calls[i].Key
}
return ""
}
+31
View File
@@ -334,3 +334,34 @@ func TestEveryReasonTheAdapterProducesIsAcceptedByTheValidator(t *testing.T) {
}
}
}
// TestLastObservedCommandSkipsOrchestrasOwnHandoffWrite guards F37. The handoff
// prompt tells the agent to write HandoffReportFile and stop, so that write is
// almost always the last command in the pane. Carrying it into Handoff.Command
// made every rotation fail Validate's own circularity check. Live on run 5:
// "adapter: upload handoff: invalid handoff command: must not point to a
// handoff or report".
func TestLastObservedCommandSkipsOrchestrasOwnHandoffWrite(t *testing.T) {
for _, last := range []string{
"cat > " + HandoffReportFile,
"vim .orchestra-handoff.json",
"less handoff-report.md",
} {
if !continuity.IsCircularCommand(last) {
t.Fatalf("%q is not recognised as circular, so this test proves nothing", last)
}
h := continuity.Handoff{
Meta: continuity.Meta{ID: "agent-1", Reason: "phase_changed"},
Anchor: continuity.Anchor{GitSHA: "0000000000000000000000000000000000000000", Branch: "main"},
Action: "write the research note",
Command: "go test ./...",
}
if err := h.Validate(); err != nil {
t.Fatalf("a real command was rejected: %v", err)
}
h.Command = last
if err := h.Validate(); err == nil {
t.Fatalf("%q was accepted, so skipping it in the producer is not what keeps rotations alive", last)
}
}
}