Files
orchestra/HANDOFF-2026-08-26.md
T
kami 7f12c7fc37 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>
2026-08-26 18:31:20 +04:00

8.8 KiB

Handoff — orchestra v3 intent/context/workflow foundation

Written 2026-08-26. Read this with CLAUDE.md and AUDIT.md. Everything below was verified with go build ./... && go vet ./... && go test ./... passing, 20 test packages, at the end of the session.

What this session built

Eight units, in this order. Each one landed complete with tests before the next started.

  1. Human decision events plus a reducer.
  2. Source cursors and reconcile-before-launch.
  3. Turn-boundary reconciliation for a live lease.
  4. internal/agentctx as the single renderer.
  5. ace-fca phases with sealed artifacts.
  6. Federated turn decision, then the trajectory gate.
  7. Bounded grilling.
  8. Independent review, task pr enforcement, then human review reflection.

The loop now closes:

task → frame → research → plan → implement → ai review → task pr
     → human review ─┬─ comments → implementation
                     └─ merge → completed

The invariants that hold, and where they are enforced

Invariant Enforced at
Human decisions outrank contract, plan, and handoff domain.ReduceIntent, rendering order in agentctx.Build
Supersession is explicit, never inferred from subject domain.ReduceIntent
Reduction is order-independent and replay-deterministic domain.ReduceIntent, sorted by (At, ID)
No ownership begins without reconciling human input Store.PreLease, called inside Store.Lease, the only minter of TaskLeased
Live sessions see corrections at a verified turn boundary Coordinator.TurnDecision, Coordinator.RemoteTurn
One renderer decides what an agent sees agentctx.Build, sole caller of every context section
A phase reads sealed artifacts, never prior conversations agentctx.renderSealed, phase table
Leaving research or plan requires a sealed artifact Store.Append, WorkPhaseChanged branch
Only Orchestra changes phase operations.AdvanceWorkPhase, domain.CanTransitionPhase
One question at a time, bounded, with a finite budget operations.RequestHumanDecision
Review is independent of the implementation agentctx.Build excludes handoff and research in review phase
Review is bound to one commit Task.ReviewTargetSHA, Task.ReviewSatisfied
Submission requires gate sha == review sha == head sha domain.CheckSubmission
Submission is idempotent and not completion Task.Submitted, StateInReview
Only the bound pull request can move its task operations.ReflectSubmission
Completion requires an actual merge operations.ReflectSubmission, domain.CompletionReceipt

Package map for the new work

internal/agentctx/     the only renderer of model-facing context
internal/human/        human input, reconciliation, trust, PR observations
internal/review/       review evidence, findings, reviewer instructions
internal/workphase/    sealed research and plan artifacts
internal/domain/       decision.go, decision_request.go, workphase.go, submission.go
internal/operations/   workphase.go, trajectory.go, human_decision.go,
                       review.go, submission.go, reflect.go
internal/store/        cursor.go, plus projections in store.go
internal/provider/     gitea_comments.go, gitea_pr.go

New event types, all validated in domain.ValidateEvent: HumanDecisionRecorded, HumanDecisionSuperseded, WorkPhaseChanged, DeferredFindingRecorded, ReviewRecorded, TaskSubmitted, TaskChangesRequested.

New task state: in_review. New block reasons: trajectory_gate, human_decision, operator_required.

Things that will bite the next session

  • bootstrapPrompt and taskLaunchPrompt are gone. Do not reintroduce a second renderer. grep 'prompt = ' internal cmd should only find handoffPrompt and conventionsPrompt in internal/herdr, which request output rather than supply authority.
  • herdr.Session is no longer comparable with ==. It carries DeliveredDecisions []string. cmd/orchestra-worker/main.go:584 uses reflect.DeepEqual.
  • The reflector must not move behind PreLease. An in-review task cannot be leased, so a pre-lease hook cannot observe the feedback that reopens it.
  • go vet catches t.Context() because go.mod declares go1.22 while the toolchain is newer. Use context.Background() in tests.
  • Two pre-existing gofmt offenders, internal/provider/provider.go and internal/webui/webui.go, are untouched by this work. Ignore them or fix them deliberately.
  • The router still swallows lease errors with a bare continue (internal/router/router.go:197). That cost real debugging time this session when a PreLease refusal looked like "no candidates".

Deliberate gaps, with reasons

  • Turn-boundary reconcile failure is non-fatal. Recorded in MonitorHealth.Sessions[id].LastError, and the turn continues. Blocking would freeze live sessions during a source outage without making their intent less stale. The escalation to prepare_handoff after repeated failures is deliberately not built; wait for evidence.
  • The federated worker has no turn-decision path of its own. It reports its locally-evaluated verdict to POST /v1/federation/turn and delivers what comes back. It has no prepare_handoff escalation either.
  • Admission for grilling is stated, not enforced. Orchestra cannot mechanically tell a repo-answerable question from a real one without another semantic judge. What is enforced: bounds, budget, one-at-a-time, and Orchestra owning the lifecycle.
  • Vikunja is not marked in-review on submission. No Vikunja mutation client exists in this repo. Task.Submission holds everything a reflector needs.
  • Deferred findings do not become follow-up tasks yet. operations.DeferredFindings lists them; creating tasks is a separate step.
  • The review endpoint requires a full-control surface, so a gated agent surface cannot seal a review. That is the conservative default until capability restrictions land.
  • GiteaPublisher is untested against a live Gitea. Its PR create, update, read, and comment paths are shaped from the API docs, not from a live probe. Verify against the real instance before trusting them.

New configuration

deploy/config.example.jsonc, per project:

"work_phases": ["frame", "research", "plan", "implement", "review"],
"trajectory_gate": { "plan_to_implement": "required" },
"human_decisions": { "max_requests_per_task": 6 }

deploy/orchestra.env.example:

ORCHESTRA_HUMAN_RECONCILE=off        # disable pre-lease reconciliation
ORCHESTRA_PR_BASE=master             # pull request base branch
ORCHESTRA_REVIEW_ACTORS=kami         # who may reopen a submitted task
ORCHESTRA_REVIEW_IGNORE_ACTORS=...   # bots, always loses

Nothing in this session's work changes behaviour on a deployment with no Gitea source configured. Reconciliation, submission publishing, and reflection all stay inert without one.

Next unit: capability restrictions

This is the last item in the build order, and its point is narrow: the workflow exists now, so what remains is stopping an agent from stepping around it.

Agent sessions should get:

repo read/write
tests, build
task checkpoint
task decision-request
task deferred

They should not get:

vikunja mutation token
gitea administrative token
direct lifecycle mutation
task completion API
push or pull-request creation
phase transition
review sealing
submission

Orchestra keeps: push policy, pull request creation, task status, session state, decision persistence, phase transitions, review sealing, submission, and completion.

Prompt rules stay advisory. The enforcement is capability boundaries plus legal transitions, which is what internal/authz already models. Start there: CapabilityFor currently gives mcp and maven GatedWrite, and CanEmit allows only ApprovalRequested at that level. Decide which of the new event types an agent surface may emit, then make the endpoints agree.

Two concrete questions to settle first:

  1. Should a reviewing agent seal its own review through a gated surface, or should the worker seal it on the agent's behalf? Today it needs full control.
  2. Does the harness token surface (/v1/harness/turn) need its own capability level, distinct from mcp?

Acceptance proofs worth reading before changing anything

  • internal/integration/acefca_test.go — four proofs: phase boundaries carry authority, the trajectory gate correction outranks the sealed plan, a blocking question resumes with the answer on top, review is independent, and a submitted task is not reassigned.
  • internal/integration/reconcile_launch_test.go — reconciliation is upstream of every agent start, and an unreachable source refuses the lease.
  • internal/operations/reflect_test.go — the full human loop from rejection to merge.