From 5940d0292b8bc38605431eafb795baec054eb442 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 14 Nov 2026 07:14:19 -0800 Subject: [PATCH 1/2] Pin the Harness at `1664eaf3` and drop the output row The Engine at `1664eaf363569d629d8ec65f3e0381bded3449bd` has no `output:` frontmatter key, because a prompt returns its output as its return value. The four `runvil-harness` entries in `Cargo.toml` name that commit, the contract route sends no `output` field, and the Run window draws no output row. - `ContractResponse` has no `output` field. `FileDto` and its `From<&FileDecl>` impl are deleted. - `run-api.ts` does not narrow `output`, and `RunContractFile` and `parseFile` are deleted. The old narrowing refused a contract with the key missing, so the route and the client change together. - `run-rows.ts` draws no `output` row. A prompt with no arguments shows the `name` and `max_tool_iterations` rows alone. - `Cargo.lock` changes only the 15 source lines of the crates that come from `runvil-harness`. - No other code, test, or prompt changes with the pin. - The tests of the route and the Run window lose their `output` fixtures and assertions. No test here covers a prompt that still declares `output:`. Plan: runvil-harness/plan/2026-11-14-1-private-store.plan.md --- Cargo.lock | 30 +++++++++---------- Cargo.toml | 8 ++--- .../server/src/routes/prompts-tests.rs | 8 ----- crates/workshop/server/src/routes/prompts.rs | 23 +------------- crates/workshop/ui/src/parts/run/run-rows.ts | 6 +--- crates/workshop/ui/src/services/run-api.ts | 23 -------------- crates/workshop/ui/test/run-api.mjs | 11 +------ crates/workshop/ui/test/run-panel.mjs | 8 ++--- 8 files changed, 24 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ade573b7..8f13c51f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2242,7 +2242,7 @@ dependencies = [ [[package]] name = "harness-gateway-client" version = "0.3.0" -source = "git+https://github.com/cppalliance/runvil-harness?rev=0d938885abc7fb293ae449a787c50e7c7eb60e92#0d938885abc7fb293ae449a787c50e7c7eb60e92" +source = "git+https://github.com/cppalliance/runvil-harness?rev=1664eaf363569d629d8ec65f3e0381bded3449bd#1664eaf363569d629d8ec65f3e0381bded3449bd" dependencies = [ "async-trait", "bytes", @@ -2258,7 +2258,7 @@ dependencies = [ [[package]] name = "harness-plugin-loader" version = "0.3.0" -source = "git+https://github.com/cppalliance/runvil-harness?rev=0d938885abc7fb293ae449a787c50e7c7eb60e92#0d938885abc7fb293ae449a787c50e7c7eb60e92" +source = "git+https://github.com/cppalliance/runvil-harness?rev=1664eaf363569d629d8ec65f3e0381bded3449bd#1664eaf363569d629d8ec65f3e0381bded3449bd" dependencies = [ "libloading 0.8.9", "runvil-plugin", @@ -2269,7 +2269,7 @@ dependencies = [ [[package]] name = "harness-runner" version = "0.3.0" -source = "git+https://github.com/cppalliance/runvil-harness?rev=0d938885abc7fb293ae449a787c50e7c7eb60e92#0d938885abc7fb293ae449a787c50e7c7eb60e92" +source = "git+https://github.com/cppalliance/runvil-harness?rev=1664eaf363569d629d8ec65f3e0381bded3449bd#1664eaf363569d629d8ec65f3e0381bded3449bd" dependencies = [ "futures-util", "rand 0.9.5", @@ -4064,7 +4064,7 @@ dependencies = [ [[package]] name = "plugin-mcp" version = "0.3.0" -source = "git+https://github.com/cppalliance/runvil-harness?rev=0d938885abc7fb293ae449a787c50e7c7eb60e92#0d938885abc7fb293ae449a787c50e7c7eb60e92" +source = "git+https://github.com/cppalliance/runvil-harness?rev=1664eaf363569d629d8ec65f3e0381bded3449bd#1664eaf363569d629d8ec65f3e0381bded3449bd" dependencies = [ "reqwest", "rmcp", @@ -4088,7 +4088,7 @@ dependencies = [ [[package]] name = "plugin-web" version = "0.3.0" -source = "git+https://github.com/cppalliance/runvil-harness?rev=0d938885abc7fb293ae449a787c50e7c7eb60e92#0d938885abc7fb293ae449a787c50e7c7eb60e92" +source = "git+https://github.com/cppalliance/runvil-harness?rev=1664eaf363569d629d8ec65f3e0381bded3449bd#1664eaf363569d629d8ec65f3e0381bded3449bd" dependencies = [ "async-trait", "encoding_rs", @@ -4734,7 +4734,7 @@ dependencies = [ [[package]] name = "runvil" version = "0.3.0" -source = "git+https://github.com/cppalliance/runvil-harness?rev=0d938885abc7fb293ae449a787c50e7c7eb60e92#0d938885abc7fb293ae449a787c50e7c7eb60e92" +source = "git+https://github.com/cppalliance/runvil-harness?rev=1664eaf363569d629d8ec65f3e0381bded3449bd#1664eaf363569d629d8ec65f3e0381bded3449bd" dependencies = [ "harness-plugin-loader", "harness-runner", @@ -4745,7 +4745,7 @@ dependencies = [ [[package]] name = "runvil-engine" version = "0.3.0" -source = "git+https://github.com/cppalliance/runvil-harness?rev=0d938885abc7fb293ae449a787c50e7c7eb60e92#0d938885abc7fb293ae449a787c50e7c7eb60e92" +source = "git+https://github.com/cppalliance/runvil-harness?rev=1664eaf363569d629d8ec65f3e0381bded3449bd#1664eaf363569d629d8ec65f3e0381bded3449bd" dependencies = [ "runvil-lua", "runvil-model-client", @@ -4758,7 +4758,7 @@ dependencies = [ [[package]] name = "runvil-lua" version = "0.3.0" -source = "git+https://github.com/cppalliance/runvil-harness?rev=0d938885abc7fb293ae449a787c50e7c7eb60e92#0d938885abc7fb293ae449a787c50e7c7eb60e92" +source = "git+https://github.com/cppalliance/runvil-harness?rev=1664eaf363569d629d8ec65f3e0381bded3449bd#1664eaf363569d629d8ec65f3e0381bded3449bd" dependencies = [ "mlua", "runvil-markdown", @@ -4772,7 +4772,7 @@ dependencies = [ [[package]] name = "runvil-markdown" version = "0.3.0" -source = "git+https://github.com/cppalliance/runvil-harness?rev=0d938885abc7fb293ae449a787c50e7c7eb60e92#0d938885abc7fb293ae449a787c50e7c7eb60e92" +source = "git+https://github.com/cppalliance/runvil-harness?rev=1664eaf363569d629d8ec65f3e0381bded3449bd#1664eaf363569d629d8ec65f3e0381bded3449bd" dependencies = [ "pulldown-cmark", "serde_json", @@ -4782,7 +4782,7 @@ dependencies = [ [[package]] name = "runvil-model-client" version = "0.3.0" -source = "git+https://github.com/cppalliance/runvil-harness?rev=0d938885abc7fb293ae449a787c50e7c7eb60e92#0d938885abc7fb293ae449a787c50e7c7eb60e92" +source = "git+https://github.com/cppalliance/runvil-harness?rev=1664eaf363569d629d8ec65f3e0381bded3449bd#1664eaf363569d629d8ec65f3e0381bded3449bd" dependencies = [ "runvil-types", "serde", @@ -4792,7 +4792,7 @@ dependencies = [ [[package]] name = "runvil-plugin" version = "0.3.0" -source = "git+https://github.com/cppalliance/runvil-harness?rev=0d938885abc7fb293ae449a787c50e7c7eb60e92#0d938885abc7fb293ae449a787c50e7c7eb60e92" +source = "git+https://github.com/cppalliance/runvil-harness?rev=1664eaf363569d629d8ec65f3e0381bded3449bd#1664eaf363569d629d8ec65f3e0381bded3449bd" dependencies = [ "runvil-types", "runvil-vfs", @@ -4815,7 +4815,7 @@ source = "git+https://github.com/cppalliance/runvil-plugin-api?rev=03a9fa37d2fe6 [[package]] name = "runvil-prompt" version = "0.3.0" -source = "git+https://github.com/cppalliance/runvil-harness?rev=0d938885abc7fb293ae449a787c50e7c7eb60e92#0d938885abc7fb293ae449a787c50e7c7eb60e92" +source = "git+https://github.com/cppalliance/runvil-harness?rev=1664eaf363569d629d8ec65f3e0381bded3449bd#1664eaf363569d629d8ec65f3e0381bded3449bd" dependencies = [ "pulldown-cmark", "runvil-lua", @@ -4828,7 +4828,7 @@ dependencies = [ [[package]] name = "runvil-runtime" version = "0.3.0" -source = "git+https://github.com/cppalliance/runvil-harness?rev=0d938885abc7fb293ae449a787c50e7c7eb60e92#0d938885abc7fb293ae449a787c50e7c7eb60e92" +source = "git+https://github.com/cppalliance/runvil-harness?rev=1664eaf363569d629d8ec65f3e0381bded3449bd#1664eaf363569d629d8ec65f3e0381bded3449bd" dependencies = [ "mlua", "regex", @@ -4846,7 +4846,7 @@ dependencies = [ [[package]] name = "runvil-types" version = "0.3.0" -source = "git+https://github.com/cppalliance/runvil-harness?rev=0d938885abc7fb293ae449a787c50e7c7eb60e92#0d938885abc7fb293ae449a787c50e7c7eb60e92" +source = "git+https://github.com/cppalliance/runvil-harness?rev=1664eaf363569d629d8ec65f3e0381bded3449bd#1664eaf363569d629d8ec65f3e0381bded3449bd" dependencies = [ "serde", "serde_json", @@ -4856,7 +4856,7 @@ dependencies = [ [[package]] name = "runvil-vfs" version = "0.3.0" -source = "git+https://github.com/cppalliance/runvil-harness?rev=0d938885abc7fb293ae449a787c50e7c7eb60e92#0d938885abc7fb293ae449a787c50e7c7eb60e92" +source = "git+https://github.com/cppalliance/runvil-harness?rev=1664eaf363569d629d8ec65f3e0381bded3449bd#1664eaf363569d629d8ec65f3e0381bded3449bd" [[package]] name = "rust-embed" diff --git a/Cargo.toml b/Cargo.toml index 8a4144c2..50b0d05f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,10 +25,10 @@ repository = "https://github.com/cppalliance/runvil-workshop" # in. All four name one commit, because two commits would be two copies # of every type the crates share. `version` beside `git` keeps cargo-deny's # wildcard check quiet. -runvil = { version = "0.3.0", git = "https://github.com/cppalliance/runvil-harness", rev = "0d938885abc7fb293ae449a787c50e7c7eb60e92" } -harness-gateway-client = { version = "0.3.0", git = "https://github.com/cppalliance/runvil-harness", rev = "0d938885abc7fb293ae449a787c50e7c7eb60e92" } -plugin-web = { version = "0.3.0", git = "https://github.com/cppalliance/runvil-harness", rev = "0d938885abc7fb293ae449a787c50e7c7eb60e92" } -plugin-mcp = { version = "0.3.0", git = "https://github.com/cppalliance/runvil-harness", rev = "0d938885abc7fb293ae449a787c50e7c7eb60e92" } +runvil = { version = "0.3.0", git = "https://github.com/cppalliance/runvil-harness", rev = "1664eaf363569d629d8ec65f3e0381bded3449bd" } +harness-gateway-client = { version = "0.3.0", git = "https://github.com/cppalliance/runvil-harness", rev = "1664eaf363569d629d8ec65f3e0381bded3449bd" } +plugin-web = { version = "0.3.0", git = "https://github.com/cppalliance/runvil-harness", rev = "1664eaf363569d629d8ec65f3e0381bded3449bd" } +plugin-mcp = { version = "0.3.0", git = "https://github.com/cppalliance/runvil-harness", rev = "1664eaf363569d629d8ec65f3e0381bded3449bd" } # The safe layer over the C Plugin contract, version 0.4, from its own # repository, pinned by commit. Only the library Plugin crates under # `crates/workshop/` name it, for `export_plugin!`. The commit is the one diff --git a/crates/workshop/server/src/routes/prompts-tests.rs b/crates/workshop/server/src/routes/prompts-tests.rs index 482a7c65..87e0e5ae 100644 --- a/crates/workshop/server/src/routes/prompts-tests.rs +++ b/crates/workshop/server/src/routes/prompts-tests.rs @@ -39,9 +39,6 @@ name: full description: does everything runvil: 0 max_tool_iterations: 5 -output: - path: out.md - description: the result uses: - web - fs @@ -78,10 +75,6 @@ async fn a_full_frontmatter_prompt_answers_every_contract_section() { assert_eq!(json["description"], "does everything"); assert_eq!(json["runvil"], 0); assert_eq!(json["max_tool_iterations"], 5); - assert_eq!( - json["output"], - serde_json::json!({ "path": "out.md", "description": "the result" }) - ); // Packs keep declaration order, each sent by its Pack id alone; the // map-backed sections are sorted by name or label. assert_eq!( @@ -197,7 +190,6 @@ async fn the_dto_serializes_absent_declarations_as_nulls() { let (status, json) = post_contract(body).await; assert_eq!(status, StatusCode::OK); // Absent declarations serialize as explicit nulls, not missing keys. - assert!(json["output"].is_null()); assert!(json["max_tool_iterations"].is_null()); assert!(json["runvil"].is_null()); } diff --git a/crates/workshop/server/src/routes/prompts.rs b/crates/workshop/server/src/routes/prompts.rs index 6ca8f818..4b98d698 100644 --- a/crates/workshop/server/src/routes/prompts.rs +++ b/crates/workshop/server/src/routes/prompts.rs @@ -18,7 +18,7 @@ use serde::{Deserialize, Serialize}; use runvil::Prompt; use runvil::model::ModelRole; use runvil::packs::PackRef; -use runvil::prompt::{FileDecl, Frontmatter, RoleRequirements}; +use runvil::prompt::{Frontmatter, RoleRequirements}; use crate::error::AppError; @@ -50,8 +50,6 @@ struct ContractResponse { runvil: Option, /// The declared tool-loop cap; `null` for the runtime default. max_tool_iterations: Option, - /// The declared output file; `null` when absent. - output: Option, /// The declared Packs, in declaration order. uses: Vec, /// The `args` declaration as JSON Schema. The root is an object node, @@ -61,24 +59,6 @@ struct ContractResponse { models: Vec, } -/// The declared output file. -#[derive(Debug, Serialize)] -struct FileDto { - /// The store-internal path. - path: String, - /// The human-readable purpose. - description: String, -} - -impl From<&FileDecl> for FileDto { - fn from(decl: &FileDecl) -> Self { - Self { - path: decl.path().to_owned(), - description: decl.description().to_owned(), - } - } -} - /// A declared Pack. #[derive(Debug, Serialize)] struct PackDto { @@ -125,7 +105,6 @@ impl From<&Frontmatter> for ContractResponse { max_tool_iterations: frontmatter .max_tool_iterations() .map(std::num::NonZeroU32::get), - output: frontmatter.output().map(FileDto::from), uses: frontmatter.uses().iter().map(PackDto::from).collect(), args: frontmatter.args().as_json().clone(), models: ModelRole::ALL diff --git a/crates/workshop/ui/src/parts/run/run-rows.ts b/crates/workshop/ui/src/parts/run/run-rows.ts index 4afbd049..802a50b4 100644 --- a/crates/workshop/ui/src/parts/run/run-rows.ts +++ b/crates/workshop/ui/src/parts/run/run-rows.ts @@ -2,7 +2,7 @@ // pure DOM building over the narrowed RunContract. Every frontmatter // key present in the prompt except `uses`, which has nothing to // bind, appears as exactly one row: name, description, and runvil -// read-only; output a text field; one row for each top-level property +// read-only; one row for each top-level property // of the `args` schema, its control chosen by the property's type, with // the required marker and the default prefilled; model roles // read-only; max_tool_iterations numeric with the runtime-default @@ -158,10 +158,6 @@ export function renderContractRows(container: HTMLElement, contract: RunContract rows.appendChild(row("runvil", [readOnly(String(contract.runvil))])); } - rows.appendChild( - row("output", [textField(contract.output?.path ?? "", contract.output?.description ?? "")]), - ); - const required = contract.args.required ?? []; for (const [name, arg] of Object.entries(contract.args.properties ?? {})) { // The description is a visible second line under the label, not a tooltip. diff --git a/crates/workshop/ui/src/services/run-api.ts b/crates/workshop/ui/src/services/run-api.ts index 96a1c7fc..8dda379a 100644 --- a/crates/workshop/ui/src/services/run-api.ts +++ b/crates/workshop/ui/src/services/run-api.ts @@ -11,12 +11,6 @@ import { CatalogError, ErrorCatalog } from "./error-catalog"; import { errorCode, errorMessage, isRecord, readJson, request } from "./json-request"; -/** The declared output file. */ -export interface RunContractFile { - readonly path: string; - readonly description: string; -} - /** A declared Pack: its one-segment name, such as `web`. */ export interface RunContractPack { readonly id: string; @@ -71,24 +65,12 @@ export interface RunContract { readonly runvil: number | null; /** The declared tool-loop cap; null for the runtime default. */ readonly maxToolIterations: number | null; - readonly output: RunContractFile | null; readonly uses: readonly RunContractPack[]; /** The `args` declaration: an object node. */ readonly args: RunContractSchema; readonly models: readonly RunContractModel[]; } -function parseFile(value: unknown): RunContractFile | null { - if (!isRecord(value)) { - return null; - } - const { path, description } = value; - if (typeof path !== "string" || typeof description !== "string") { - return null; - } - return { path, description }; -} - function parsePack(value: unknown): RunContractPack | null { if (!isRecord(value)) { return null; @@ -230,10 +212,6 @@ function parseContract(body: unknown): RunContract | null { if (max_tool_iterations !== null && typeof max_tool_iterations !== "number") { return null; } - const output = body.output === null ? null : parseFile(body.output); - if (body.output !== null && output === null) { - return null; - } if (!Array.isArray(uses) || !Array.isArray(models)) { return null; } @@ -263,7 +241,6 @@ function parseContract(body: unknown): RunContract | null { description, runvil: runvil ?? null, maxToolIterations: max_tool_iterations ?? null, - output, uses: parsedPacks, args, models: parsedModels, diff --git a/crates/workshop/ui/test/run-api.mjs b/crates/workshop/ui/test/run-api.mjs index fb205784..11429e62 100644 --- a/crates/workshop/ui/test/run-api.mjs +++ b/crates/workshop/ui/test/run-api.mjs @@ -38,7 +38,6 @@ const CONTRACT = { description: "Screens papers", runvil: 1, max_tool_iterations: 12, - output: { path: "out/verdicts.md", description: "the verdicts" }, uses: [{ id: "tools/web" }], args: { type: "object", @@ -97,11 +96,6 @@ const json = (body, status = 200) => ({ contract.runvil === 1 && contract.maxToolIterations === 12, ); - check( - "the output narrows", - contract.output?.path === "out/verdicts.md" && - contract.output?.description === "the verdicts", - ); check( "uses narrow", contract.uses.length === 1 && contract.uses[0].id === "tools/web", @@ -176,7 +170,6 @@ const json = (body, status = 200) => ({ description: "", runvil: null, max_tool_iterations: null, - output: null, uses: [], args: { type: "object", @@ -191,9 +184,7 @@ const json = (body, status = 200) => ({ const contract = await fetchPromptContract("bare.md", "text"); check( "absent sections narrow to null", - contract.runvil === null && - contract.maxToolIterations === null && - contract.output === null, + contract.runvil === null && contract.maxToolIterations === null, ); check( "the default declaration narrows to one optional string named text", diff --git a/crates/workshop/ui/test/run-panel.mjs b/crates/workshop/ui/test/run-panel.mjs index 50bd8977..91bcc491 100644 --- a/crates/workshop/ui/test/run-panel.mjs +++ b/crates/workshop/ui/test/run-panel.mjs @@ -127,7 +127,6 @@ function contractFor(name) { description: `contract for ${name}`, runvil: 1, max_tool_iterations: 12, - output: { path: "out/verdicts.md", description: "the verdicts" }, uses: [{ id: "tools/web" }], args: { type: "object", @@ -400,12 +399,10 @@ check( "the description renders as a read-only row", rowText("description")?.textContent.includes("contract for prompt.md") === true, ); -const outputField = rowText("output")?.querySelector("input"); -check("the output row is a prefilled text field", outputField?.value === "out/verdicts.md"); check( "the rows are one per contract item, the args in the order the server sends them", rows.map((row) => row.querySelector(".ws-run-panel__row-label")?.textContent).join(",") === - "name,description,runvil,output,deep,focus,limit,opts,text,tone,topic,strong,small,fast,max_tool_iterations", + "name,description,runvil,deep,focus,limit,opts,text,tone,topic,strong,small,fast,max_tool_iterations", ); const topicRow = rowText("topic"); check( @@ -515,7 +512,6 @@ renderContractRows(noArgsRows, { description: "", runvil: null, maxToolIterations: null, - output: null, uses: [], args: { type: "object", properties: {}, additionalProperties: false }, models: [], @@ -524,7 +520,7 @@ check( "a root with no properties draws no argument row", [...noArgsRows.querySelectorAll(".ws-run-panel__row-label")] .map((label) => label.textContent) - .join(",") === "name,output,max_tool_iterations", + .join(",") === "name,max_tool_iterations", ); // --- Tree drag: dragstart sets the MIME type; a drop loads the prompt ------- From 623eb7aa33c84fb3ceb7fc1628845120f607dfb7 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 14 Nov 2026 07:14:46 -0800 Subject: [PATCH 2/2] Name a store operation in the Engine's list of effects The definition of the Engine in `AGENTS.md` lists what a run asks its caller for, and the last item was "a file outside a prelude's install". It now says "a store operation", which is the effect the Engine at the pinned commit emits. - The diff changes one line of one document. It changes no code and no test. Plan: runvil-harness/plan/2026-11-14-1-private-store.plan.md --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 4f4c8809..0d34971a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,7 @@ Multi-crate Rust workspace for Workshop, a Host, and the tooling that builds it Seven terms have exactly one meaning each, everywhere in this repository: code comments, docs, rulebooks, and plans. Write them capitalized. -- **Engine**: the `runvil-*` crates. The Engine parses a prompt and steps a run. Whenever a run needs a model reply, a Pack's render of the tools a section offers it, a Pack tool's result, a timer, or a file outside a prelude's install, the Engine emits an effect and waits for its caller to answer it. In production the caller is the Harness. +- **Engine**: the `runvil-*` crates. The Engine parses a prompt and steps a run. Whenever a run needs a model reply, a Pack's render of the tools a section offers it, a Pack tool's result, a timer, or a store operation, the Engine emits an effect and waits for its caller to answer it. In production the caller is the Harness. - **Harness**: the `runvil` crate, which is the one crate a Host depends on, and the `harness-*` crates. The Harness steps the Engine, performs every effect, returns each answer, and records every run through the recorder the Host supplies. Production code runs prompts only through the Harness. - **Host**: an application that runs prompts through the Harness, such as Workshop or Papergate. The Host makes every policy decision. It may also use the Engine's parser and types to read prompts and show events. - **Pack**: code which installs an optional native prelude, an optional Lua prelude, and zero or more tools.