feat(cli): drive the fan-out goals engine from flynn goal --fanout#176
Merged
Conversation
A fan-out narrowed a child goal's grant to exactly the tool actions the delegation requested (or the bound Agent's capabilities), which dropped the implicit `model.generate` action. A child spawned with `actions:["write"]` was then admitted for its tools but refused the model call, so its loop could not generate and the run stalled. Add `withModelGenerate` so the model call is included in a child's requested authority before it is narrowed against the parent. Because it is still intersected with the parent's grant, it never widens authority: a child only gets the model call if the parent holds it. This mirrors how the host grants the root run its own model call.
The durable one-shot path assembled a single mission executor: one model, no delegation. The Router (per-goal model and loop) and the delegation spawner were only reachable through the embeddable agent, which records onto an ephemeral log, so a delegated run could not be sealed and verified. Add `flynn goal --fanout`. When set, the run is assembled over the durable store with a `driver.Router` plus an `orchestration.Spawner`, and `spawn` is added to the run grant, so the model may delegate self-contained sub-goals to concurrent child agents. The root and every child report onto one event stream, so the whole fan-out seals into a single record that `flynn spine verify` checks. A child that names an Agent archetype is routed to that archetype's model through a credential-backed resolver. Without the flag a run is a single conversation, unchanged. To make this work over the durable path, `driver.Spec` carries the governance `EventSink` and the `CompactionBudget`, mapped by the default driver, so a Router-built loop records each admission onto the run's stream and compacts a long transcript exactly as the direct assembly did. The fan-out root runs under a larger step budget because a parent spends a step per delegation and a step per poll while it waits for its children to finish. Covered by an end-to-end test: a fan-out run delegates to a concurrent child, folds it in, and seals into one record that verifies and fails closed on a single-byte tamper.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Add
flynn goal --fanout. With the flag, a one-shot run is assembled over the durable store with adriver.Router(per-goal model and loop) plus anorchestration.Spawner, and thespawnaction is added to the run grant, so the model may delegate self-contained sub-goals to concurrent child agents. The root run and every child report onto one event stream, so the whole fan-out seals into a single record thatflynn spine verifychecks tier by tier. A child that names an Agent archetype is routed to that archetype's model through a credential-backed resolver. Without the flag a run is a single governed conversation, unchanged.Two supporting changes make this work over the verifiable path:
driver.Specnow carries the governanceEventSinkand theCompactionBudget(mapped by the default driver), so a Router-built loop records each admission onto the run's stream and compacts a long transcript exactly as the direct assembly did; and a spawned child's grant always includes the model call, so a child delegated only tool actions can still generate.Why
The advanced orchestration the runtime already supports, per-goal model routing and nested delegation, was only reachable through the embeddable agent, which records onto an ephemeral log. The durable
flynn goalpath assembled a single executor with one model and no delegation, so a delegated run could not be sealed and verified. This exposes the full goals engine on the path that produces a signed, checkable record.The child-grant fix is an independent correctness bug: narrowing a child's grant to exactly the requested tool actions dropped the implicit
model.generate, so a child spawned withactions:["write"]was admitted for its tools but refused the model call and stalled. The model call is now added before the grant is narrowed against the parent, so it is still intersected with the parent and never widens authority.How to verify
go build ./... && go vet ./driver/ ./orchestration/ ./cmd/flynn/are clean.go test ./driver/ ./orchestration/ ./cmd/flynn/passes. The new end-to-end testTestFanoutRunSealsOneVerifiableRecorddrives a fan-out run with a scripted model that delegates to a concurrent child, confirms a child goal owned by the parent was created, seals the run, verifies the single record, and confirms a one-byte tamper fails to verify.TestChildAlwaysGrantedModelCallcovers the grant fix.End to end against a live provider:
flynn --fanout --model <provider:model> --no-learn --verify "<check>" goal "<objective that decomposes into sub-tasks>", thenflynn spine verify <run-id>reportsintegrity: VERIFIED,governance: OK, andground-truth: GROUNDEDfor the one record covering the parent and all children. Global flags precede the subcommand.