v3 workflow: intent, phases, review, submission, enforcement, burn-in
The v3 stack, previously an uncommitted working tree, plus this session's two units and the burn-in instrument. This commit is the burn-in build identity: coordinator and worker must both report this revision before a task is created. Workflow (earlier sessions, uncommitted until now): human decision events and reduction, source cursors and reconcile-before-launch, turn-boundary reconciliation, internal/agentctx as the single renderer, ace-fca phases with sealed artifacts, the trajectory gate, bounded grilling, independent review, task pr enforcement, and human review reflection. Capability restrictions at the agent boundary: an authz.Agent surface at GatedWrite may ask and may not act. It also fixes two bugs the unit exposed -- gated surfaces could not reach the two endpoints written for them, and RequestHumanDecision would block an unowned task while rejecting a question from the session that did own it. Turn-boundary reconcile-failure escalation: a streak of consecutive failures asks the session to hand off, fenced on the lease epoch, with reconcile_failure as a real handoff reason. The worker was dropping the coordinator's verdict on the floor; it now acts on it. Burn-in: herdr.WriteLaunchContext dumps the exact agentctx.Build result to <worktree>/.orchestra/launch.md at every launch, local and federated. BURNIN.md is the runbook. deploy/build.sh stamps both binaries from one commit. go build, go vet and go test ./... pass, 20 packages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
package domain
|
||||
|
||||
import "fmt"
|
||||
|
||||
// WorkPhase is the cognitive phase of a task. It is orthogonal to TaskState:
|
||||
// a task can be leased in any phase, and a phase change is not a lifecycle
|
||||
// transition. Keeping them separate is what stops a rotation from looking
|
||||
// like progress and a failed experiment from looking like a failed task.
|
||||
type WorkPhase string
|
||||
|
||||
const (
|
||||
WorkPhaseFrame WorkPhase = "frame"
|
||||
WorkPhaseResearch WorkPhase = "research"
|
||||
WorkPhasePlan WorkPhase = "plan"
|
||||
WorkPhaseImplement WorkPhase = "implement"
|
||||
WorkPhaseReview WorkPhase = "review"
|
||||
)
|
||||
|
||||
// EventWorkPhaseChanged is emitted by Orchestra, never by an agent. An agent
|
||||
// asks for a phase change through the approval surface and Orchestra decides.
|
||||
const EventWorkPhaseChanged = "WorkPhaseChanged"
|
||||
|
||||
func (p WorkPhase) Valid() bool {
|
||||
switch p {
|
||||
case WorkPhaseFrame, WorkPhaseResearch, WorkPhasePlan, WorkPhaseImplement, WorkPhaseReview:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// legalPhaseTransitions is the full set of moves Orchestra may make. A
|
||||
// project's declared path is a subset of this, checked where the registry is
|
||||
// visible. Skipping ahead is allowed, going backwards is not, except for
|
||||
// review sending work back to implement.
|
||||
var legalPhaseTransitions = map[WorkPhase][]WorkPhase{
|
||||
WorkPhaseFrame: {WorkPhaseResearch, WorkPhaseImplement},
|
||||
WorkPhaseResearch: {WorkPhasePlan, WorkPhaseImplement},
|
||||
WorkPhasePlan: {WorkPhaseImplement},
|
||||
WorkPhaseImplement: {WorkPhaseReview},
|
||||
WorkPhaseReview: {WorkPhaseImplement},
|
||||
}
|
||||
|
||||
// CanTransitionPhase reports whether Orchestra may move from one phase to
|
||||
// another. An empty from is treated as frame, the phase every task starts in.
|
||||
func CanTransitionPhase(from, to WorkPhase) bool {
|
||||
if from == "" {
|
||||
from = WorkPhaseFrame
|
||||
}
|
||||
if !from.Valid() || !to.Valid() {
|
||||
return false
|
||||
}
|
||||
for _, allowed := range legalPhaseTransitions[from] {
|
||||
if allowed == to {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidateWorkPhaseChanged checks the payload shape. Whether the transition
|
||||
// is legal from the task's current phase is checked at the append boundary,
|
||||
// where the current phase is visible.
|
||||
func ValidateWorkPhaseChanged(p map[string]any) error {
|
||||
phase, _ := p["phase"].(string)
|
||||
if !WorkPhase(phase).Valid() {
|
||||
return fmt.Errorf("%w: phase invalid", ErrInvalid)
|
||||
}
|
||||
if v, ok := p["from"]; ok {
|
||||
s, ok := v.(string)
|
||||
if !ok || !WorkPhase(s).Valid() {
|
||||
return fmt.Errorf("%w: from invalid", ErrInvalid)
|
||||
}
|
||||
}
|
||||
// A sealed artifact is what makes the next phase's context cheap. It is
|
||||
// required when leaving research or plan, because those phases exist to
|
||||
// produce one.
|
||||
if v, ok := p["result_sha"]; ok {
|
||||
s, ok := v.(string)
|
||||
if !ok || len(s) != 40 {
|
||||
return fmt.Errorf("%w: result_sha invalid", ErrInvalid)
|
||||
}
|
||||
}
|
||||
if v, ok := p["artifact_ref"]; ok {
|
||||
s, _ := v.(string)
|
||||
if err := requiredHash(map[string]any{"artifact_ref": s}, "artifact_ref"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user