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

194 lines
8.8 KiB
Markdown

# 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:
```jsonc
"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.