feat(orchestrator): milestone rotation and thrash detection (S11)
Closes the last two S11 triggers. internal/herdr/activity.go normalizes tool/function calls per harness (ClaudeActivity verified against the existing transcript format, CodexActivity best-effort/unverified, OpenCodeActivity refuses — no confirmed per-tool-call source exists) and implements the three thrash rules plus a narrow milestone check (successful git commit as the last call). CLIAdapter.RequestHandoffReason asks the agent to write a handoff with meta.reason set, same "ask, don't invent" pattern as the existing handoff/ report requests. rotate() and TurnDecision generalize the manual-bypass shortcut to manual/milestone/thrash and request (never directly release) on a detected trigger. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W1rkJ2hBMybnJctPbcy4tT
This commit is contained in:
@@ -136,6 +136,60 @@ func (a CLIAdapter) RequestHandoff(ctx context.Context, s Session) error {
|
||||
return a.Client.Prompt(ctx, s.PaneID, handoffPrompt, time.Minute)
|
||||
}
|
||||
|
||||
// ReasonedHandoffRequester is RequestHandoff's counterpart for the two
|
||||
// orchestrator-detected triggers (S11: milestone, thrash) rather than the
|
||||
// occupancy-driven ones. It exists separately from HandoffRequester because
|
||||
// these prompts need to say *why* — naming the detected dead ends for thrash,
|
||||
// or the recognized completion point for milestone — instead of the generic
|
||||
// "context budget reached" framing handoffPrompt uses.
|
||||
type ReasonedHandoffRequester interface {
|
||||
RequestHandoffReason(ctx context.Context, s Session, reason string, deadEnds []continuity.DeadEnd) error
|
||||
}
|
||||
|
||||
func (a CLIAdapter) RequestHandoffReason(ctx context.Context, s Session, reason string, deadEnds []continuity.DeadEnd) error {
|
||||
var sb strings.Builder
|
||||
fmt.Fprintf(&sb, "Orchestra has detected a %q rotation trigger for this task.\n", reason)
|
||||
switch reason {
|
||||
case "thrash":
|
||||
sb.WriteString("Signs of thrashing were detected (repeated failing test runs, repeated edits to the same file, or the same tool call repeated back to back). Stop the current approach rather than trying it again.\n")
|
||||
case "milestone":
|
||||
sb.WriteString("A coherent unit of work looks complete (a successful commit). If the next step is independent of what you just did, this is a good point to hand off.\n")
|
||||
}
|
||||
fmt.Fprintf(&sb, "Before you stop, write a §6.1 handoff to %s at the worktree root with meta.reason=%q", HandoffFile, reason)
|
||||
if len(deadEnds) > 0 {
|
||||
sb.WriteString(" and a dead_ends entry for each of the following:\n")
|
||||
for _, d := range deadEnds {
|
||||
fmt.Fprintf(&sb, "- tried: %q, why_failed: %q\n", d.Tried, d.WhyFailed)
|
||||
}
|
||||
} else {
|
||||
sb.WriteString(".\n")
|
||||
}
|
||||
sb.WriteString("Use the same anchor convention as any other handoff: the real git_sha via 'git rev-parse HEAD', the real branch, and a real sha256 of any uncommitted files — never fabricated. Do not edit TASK.md. Once written, stop normally.")
|
||||
return a.Client.Prompt(ctx, s.PaneID, sb.String(), time.Minute)
|
||||
}
|
||||
|
||||
// Activity resolves the harness's tool-call history the same way Occupancy
|
||||
// resolves its session file (Session.SessionFile if set, otherwise a fresh
|
||||
// per-harness lookup), then dispatches to the harness-specific parser.
|
||||
func (a CLIAdapter) Activity(ctx context.Context, s Session) ([]ToolCall, error) {
|
||||
path := s.SessionFile
|
||||
if path == "" {
|
||||
resolved, err := a.resolveSessionFile(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("adapter: resolve session file: %w", err)
|
||||
}
|
||||
path = resolved
|
||||
}
|
||||
switch a.Harness {
|
||||
case "claude":
|
||||
return ClaudeActivity(path)
|
||||
case "codex":
|
||||
return CodexActivity(path)
|
||||
default:
|
||||
return OpenCodeActivity(path)
|
||||
}
|
||||
}
|
||||
|
||||
// conventionsPrompt is §6.3's "orchestra injects a notice to agents whose
|
||||
// current task is adjacent" — staleness is tracked here, not by trusting the
|
||||
// agent's cached view of the shared docs.
|
||||
|
||||
Reference in New Issue
Block a user