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:
@@ -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")
|
||||
|
||||
@@ -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 ""
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user