Repository navigation
Conversation
route(human = True) asks a person through the controller. The executor opens the route's request and parks it: tasks that do not depend on the route keep dispatching, and the route settles when the request is answered (labels plus the decision record), expires (every question uncertain), or the run halts. The request carries the evidence the approver sees: dependency outputs and captured files, spend and ceilings, the tasks each answer starts, and an optional review template rendered with every value escaped for CommonMark. The run reaches the controller on its ingest surface with the pod's ingest credential; `human` is available exactly when that surface is. Contract 1.15.0 adds the request types and the decision_wait event. The RFC amendment specifies the human decider, its evidence, and run:approve. Assisted-by: Claude
The run opens and polls requests on its pod ingest surface; a local run gets a credential minted at spawn for its local-<run> pod name, revoked when the run completes. A person allowed run:approve lists open requests, reads one with its evidence, and answers it against the evidence digest; the first answer wins, a repeat is acknowledged, and an accepted answer is audited in the same transaction. Requests still open when their run completes are withdrawn. run:approve defaults to the principal the run is attributed to and a maintainer of an owning team, and a run principal is forbidden it. Assisted-by: Claude
Approvals lists the open requests the signed-in user may answer, and /decisions/:id shows one: the rendered review, each dependency's output and files (images, CSV, Markdown, JSON, and HTML or SVG in a sandboxed frame with no network), the tasks each answer starts, and the answer controls. The shell raises a browser notification when a request the user may answer opens; clicking it opens the request. The decision DTOs take a Decision prefix: utoipa keeps the last schema registered under a name, so EvidenceFileDto had silently replaced the task-evidence schema. examples/human-gate holds a priced training launch for approval and runs locally against `just controller-local`. Assisted-by: Claude
A human-decided route can now ask a choice that takes several labels (`choice(multiple = True)`) and a pick whose options are a list a dependency produced (`pick(source = plan.nodes)`). Picked values land under the question id as a string list, so `over = gate.nodes` fans out over what the approver chose. Models and output deciders still answer one label; validation refuses picks and multi-select on them, a `when` naming a pick, and a question named `decision`. Answers are a list of values per question on the wire and in the store. The controller and the engine both refuse values a question does not offer, repeats, and more than one value on a single-answer question. Run-end cleanup no longer marks an already-expired request withdrawn; expiry is computed on read, so the row still said open. The decision store returns typed errors instead of anyhow::bail. The UI gets a root error boundary and one around the routed page, so a page that throws while rendering shows the error and a reload button instead of a blank window.
|
Docs preview for this PR is built and attached as the Download Rebuilt for 819f563. |
decide now works on plain inputs (each dependency's status and output, a captured-file lookup) and returns a Settled value; exec builds those inputs and turns Settled into a TaskResult. The modgraph check forbids the cycle.
runs::ingest_drop merged decisions::ingest_routes while decisions reads IngestState from it, a module cycle the modgraph check refuses. The server now merges both routers on the shared ingest state.
Resolves the overlap with the OpenAI decider (#226): - QuestionKind keeps Score beside Choice { multiple } and Pick; Answer carries score, asked_as and labels. A person's score answer records its level position. - The broker's new decide module refuses pick questions; plan validation already keeps them off model routes. - needs = "decision" replaces "systemone" in the route checks. - Routes may now map over a list, but a human route may not: one request per route. A human route may not declare files either; its evidence already carries them. - The runner's captured_file hook is main's, keyed by producer name. - CONTRACT_VERSION 1.17.0; the decision tables move to migration 0055.
Takes main's base64 0.23 beside the minijinja dependency in crucible/Cargo.toml.
Main took migration 0055 for rerun launches; the decision tables move to 0056. The local run environment keeps both the ingest variables and main's CRUCIBLE_RUN_NAME.
Main's run tokens (#243) moved playbook pods off the pod ingest surface: a run now carries CRUCIBLE_CONTROLLER_URL, CRUCIBLE_RUN_NAME and a crr_ token bound to it. Decision requests follow: - A run opens and polls its requests at /api/runs/{run}/decisions with its run token; a token reaches only its own run's requests. The pod routes, the local_run_tokens table and the local branch of ingest auth are gone. - A local run gets a run token minted at spawn alongside the controller URL, and run_ended revokes the run's tokens. - The engine's desk reads the same three variables the evidence client does. Main also took migrations 0055 and 0056; the decision table is 0057.
getent exits non-zero when it cannot resolve a name, and under pipefail the J_HOST assignment then ended the pods shard with no message, before the gateway fallback or the script's own error ran. The gateway lookup's grep had the same failure. main's kind-e2e fails the same way.
Main fixed the same scenario JP host lookup in #245; its version of pods.sh replaces ours.
Main renamed run tokens to pod tokens (#246): CRUCIBLE_RUN_TOKEN is CRUCIBLE_POD_TOKEN and grants name a Subject. Decision requests, the local run's token and the engine's desk follow the new names. Main took migration 0057 and contract 1.18.0; the decision table is 0058 and this branch's contract changes are 1.19.0. C-RUN-PRINCIPAL keeps main's pod evidence routes wording and says decision requests use the run's credential beside them.
Main took migration 0058 and contract 1.19.0 (#250); the decision table is 0059 and this branch's contract changes are 1.20.0.
This branch has not been deployed
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
A route can now be decided by a person:
route(human = True, review = "...", timeout = "20m"). The engine opens a decision request with the controller carrying the evidence (dependency outputs, declared files, spend, what each answer starts, a rendered review), parks the route while independent tasks keep running, and polls until someone allowedrun:approveanswers or the request expires. Expiry answersuncertain, which the author routes like any other label.The controller holds requests (one per run and route, digest-bound answers, audited), lists them on Approvals, and renders the evidence page with sandboxed HTML/SVG visualizations, Markdown, CSV and JSON. The web UI raises a browser notification when a request opens. Local runs authenticate with a per-run token.
Questions can be single choice, multi-select, or a pick from a list a dependency produced;
over = gate.nodesfans out over the picked values.examples/human-gateruns the whole flow on a laptop againstjust controller-local.The UI also gains an error boundary, so a page that throws shows the error instead of a blank window.
Changelog: Added: routes decided by a person, answered from the controller UI with the evidence, review and visualizations shown inline, and a browser notification when one opens