Skip to content

Remove debt from #172's review and rewrite the README as a storefront - #176

Merged
vinniefalco merged 10 commits into
cppalliance:masterfrom
vinniefalco:master
Oct 27, 2026
Merged

vinniefalco merged 10 commits into
cppalliance:masterfrom
vinniefalco:master

Conversation

@vinniefalco

Copy link
Copy Markdown
Member

Summary

This pull request carries two series. The first 3 commits rewrite README.md as a storefront, update the documentation links in two READMEs, and add the vibelight config. The next 7 commits remove debt that a review of #172 found in its 27 merged commits (951e4bb9b..1d6937459). Two of those fixes are real bugs, each with a regression test that failed before its fix. The rest are cleanups with no behavior change. The plan that drove the series, vibe/2026-10-27-1-pr172-debt-removal.md, comes with it and gives the evidence for each fix. The labels in parentheses below match its Debt Inventory.

README and vibelight

  • README.md now tells a new reader what Runvil does, how to get Workshop, and what a prompt looks like. One screenshot, images/workshop-window.jpg, replaces the five banner images and the portrait, which stay in the repository.
  • The download link goes to the nightly release, because the workshop-latest tag has no release with installers, and the text says nightly builds don't update themselves. The Workshop text no longer says a user can review every recorded run, because no screen reads the run log yet.
  • The documentation links move from https://cppalliance.org/runvil/, which returns 404, to https://gh.cppalliance.org/runvil/, in the root README and in crates/gateway/app/README.md. The root README shows runvil.org, which redirects there, as the documentation site.
  • vibelight.toml has vibelight outline the whole workspace except the six build-* tooling crates and crates/harness-internal/test-plugin. .vibelight/.gitignore keeps the generated outlines out of the history.

Bug fixes

  • The shipped research prompt, prompts/research-person.md, told the model to use web/search and web/fetch. Those are tool ids, but the model only knows tools by their wire names, web_search and web_fetch today. A model that followed the prose literally would call a tool outside its sets, and the section would fail. The prose now describes searching and fetching by what they do, so it stays correct however a Pack names the tools to the model. A new test checks that no user message the model reads names an offered tool's id, and it failed before the prose change. The prompt's tools.offer line still names the tools by id, which is correct for Lua. (C-1)
  • An admission pass that hit an internal error returned partway through, after it had already taken the queues it was working through and marked earlier admissions in the same pass as holding their slots. A task that never started could then read as admitted, and a parked chain waiting to take its slots back could be dropped and never resume. Admission::plan now works on its own copies of both queues, the slot holders, and the given-back slots, and writes them back only when the pass succeeds. Which passes fail, their messages, and the order of admission stay the same. The only behavior change is the state admission keeps after an internal error, which only a scheduler bug can reach. The new regression tests in admission-tests.rs failed before the fix, and the existing bad-resumption test now also checks that its resumption stays queued. (D1-24)

Cleanups with no behavior change

  • The engine kept its own FrozenSet enum beside runvil-lua's OfferMode, two types for one concept, and the engine's error::sets and execute::scope modules imported each other because of it. FrozenSet is gone, and OfferMode gains a Display that writes the same Static Set and Dynamic Set text, so every merge error message reads exactly as before and the import cycle is gone. One scope test now asserts the full message for a wire-name clash across the two sets. (D1-11, D1-19)
  • The discovery tool ids, dynamic/search and dynamic/call, and the reserved Pack list were spelled out separately, so renaming one without the other would let a vendor Pack take the discovery namespace with nothing to catch it. A new runvil-lua test checks that every discovery tool id parses under a reserved Pack. It passes today and guards against a future rename. (D1-23)
  • prepare_dispatch and prepare_model_dispatch took an optional call counter, but the engine always passed nothing because it counts each call before dispatch. The parameter, its increment, and the clippy::too_many_arguments expectations it needed are gone, and the docs and tests stop describing a count taken there. ToolCallCounts itself is untouched. (D1-27)
  • display_chain's doc gave the Engine's LuaRuntime variant as an example, and that variant no longer exists. The example clause is gone, and the code is unchanged. (D1-31)

Breaking changes

None. crates/runvil/public-api.txt is unchanged, and the one enum that gained an impl, OfferMode, lives in the internal runvil-lua crate. Every merge error message keeps its exact text.

Guide

The guide change is on cppalliance/runvil-docs master (d81228d..6727f22): cppalliance/runvil-docs@d81228d...6727f22

It corrects one paragraph in the limits chapter that said the Harness can replace any default limit, so a run's limits could differ from the table. The Engine accepts other limits from the code that starts a run, but the Harness passes none today, so every run observes the defaults. (D1-34)

Test plan

Each step ran its own checks before its commit: nextest on the crates it touched, clippy with warnings denied on those crates, and fmt. The research prompt step also ran the docs-claims scan. The OfferMode step also ran the engine's private-items rustdoc gate and a check that nothing in the engine still names FrozenSet. The discovery-id step also ran cargo test -p build-xtask, because it wires a new test file.

The final step ran the full set over the whole series: workspace nextest, workspace clippy with warnings denied, fmt, both rustdoc gates, cargo test -p build-xtask, the docs-claims scan, and xtask api --check, which confirms the public API listing didn't change.

The research prompt and admission regression tests failed before their fixes and pass after them. The discovery-id guard passed at once, as expected.

Workshop nextest and clippy, the headless gateway check, cargo deny, and the 1.95 build were not run locally for this series and are left to CI on this pull request. The README and vibelight commits change no Rust code.

The README now tells a new reader what Runvil does, how to get Workshop, and what a prompt looks like. One screenshot, `images/workshop-window.jpg`, replaces the five banner images and the portrait. Six sections follow: Workshop, a sample prompt, how the parts fit together, documentation, contributing, and the license.

- The download link goes to the `nightly` release, because the `workshop-latest` tag has no release with installers. The text tells the reader that nightly builds do not update themselves.
- The documentation link changes from `https://cppalliance.org/runvil/`, which returns 404, to `https://gh.cppalliance.org/runvil/`. The text also links the Language guide and the Gateway guide.
- The Workshop text no longer says that a user can review every recorded run, because no screen reads the run log yet. It now describes agents in the chat panel and a local log that only grows.
- The six images that the old text showed, `images/banner-02.png` to `images/banner-06.png` and `images/runvil-portrait.png`, stay in the repository.
- The change adds no check that keeps the example equal to `prompts/greet.md`.
The documentation site is now at `https://gh.cppalliance.org/runvil/`, and `runvil.org` redirects to it. The root `README.md` shows `runvil.org` as the documentation site. `crates/gateway/app/README.md` links the gateway guide at `https://gh.cppalliance.org/runvil/gateway/` instead of `https://cppalliance.org/runvil/gateway/`.

- The guide links in `README.md` keep their `https://gh.cppalliance.org/runvil/` paths, because `runvil.org` sends every path to the landing page.
vibelight writes a markdown outline and an HTML page for each workspace crate. The config chooses which crates a plain vibelight run outlines, and the output folder ignores its own outlines so they stay out of the history.

- `vibelight.toml` outlines the whole workspace and follows every path dependency, but excludes the six `build-*` tooling crates and `crates/harness-internal/test-plugin`. It writes to `.vibelight`.
- `.vibelight/.gitignore` ignores `*.md` and `*.html`, so the folder tracks only its ignore file.
The shipped research prompt told the model to use its web tools by their ids, but the model only knows tools by their wire names. The prompt now describes searching and fetching by what they do, so the text stays correct however the tools are named to the model. A new test checks that no user message the model reads names an offered tool's id.

- `research_ada_lovelace` holds the scenario both shipped tests run and returns its `Trace`, which `support.rs` now re-exports to the suite. The existing end-to-end test keeps every assertion it had.
- `the_shipped_research_prompt_names_no_tool_by_its_id_in_what_the_model_reads` serializes every user message in every `EffectRecord::Chat` and fails if one contains `web/search` or `web/fetch`. It checks only user messages, the text the prompt itself puts in front of the model, and it requires at least one so an empty run cannot pass.
- `web/search` and `web/fetch` still appear in the section's Lua offer, which addresses tools by id. Only the prose sent to the model changes.

Repairs: user messages name no offered tool id @ prompts/research-person.md - the research prose named the tool ids web/search and web/fetch
Plan: vibe/2026-10-27-1-pr172-debt-removal.md
An admission pass that hit an internal error returned partway through, after it had already taken the queue it was working through and marked earlier admissions in the same pass as holding their slots. Those admissions never reached the scheduler, so a task that never started could read as admitted, and a parked chain waiting to take its slots back could be dropped and never resume. The pass now works on its own copies of both queues, the set of slot holders, and the map of given-back slots, and writes them back only when it succeeds. Which passes fail, their messages, and the order of admission stay the same; only the state left behind after a failure changes.

- `plan` on `Admission` clones `holding` and `released`, builds the new resume and spawn queues locally, and assigns all four to `self` only just before it returns `Ok`. It checks each queued spawn by reference and moves the spawn records out of `self.spawned` only after the last check, so no error return can strand one.
- `slot_holder` and `slots_in_use` move out of `Admission` into free functions that take the given-back map and the holder set as arguments, so the pass reads its working copies rather than `self`.
- `plan` still checks only the entries it would admit instead of validating every queued entry up front, since that would make some passes fail that succeed today.
- `is_queued` still reads the live spawn queue during the resumption checks. That queue stays untouched until the pass succeeds, so those checks see the same queue as before and the pass admits the same entries in the same order.
- `self.holding.clone()` and `self.released.clone()` now run on every pass with something queued. A pass with both queues empty still returns before copying anything.
- `admission-tests.rs` adds two regressions that fail a pass on a queued call chain, one after starting a task and one after admitting a parked call chain's resumption; between them they check that both queues, the holders, and the given-back map come through untouched. The second also checks the resumption is admitted once the stray spawn is forgotten, and the existing bad-resumption test now checks its resumption stays queued.
- `plan` keeps its signature and both error messages, so its caller in the scheduler does not change.

Design: new oversized-unit @ crates/runvil-internal/engine/src/execute/scheduler/admission.rs::Admission::plan
Design: new pure-function @ crates/runvil-internal/engine/src/execute/scheduler/admission.rs::slot_holder deps: ChainIndex,HashMap
Design: new pure-function @ crates/runvil-internal/engine/src/execute/scheduler/admission.rs::slots_in_use deps: ChainTree,HashSet
Repairs: an unqueued task chain has been admitted @ crates/runvil-internal/engine/src/execute/scheduler/admission.rs::Admission::plan - a failed pass left a never-started spawn unqueued and holding its slots
Repairs: a woken chain queued to take its slots back is admitted again @ crates/runvil-internal/engine/src/execute/scheduler/admission.rs::Admission::plan - a failed pass consumed a queued resumption that no later pass admitted
Plan: vibe/2026-10-27-1-pr172-debt-removal.md
The engine kept its own two-variant enum for a section's Static Set and Dynamic Set beside the Lua crate's enum for which set an offer feeds, so two types modeled one concept and had to be kept in step. The module that defines the set errors also imported that enum from the scope module, which in turn imports the set errors, so the two modules depended on each other. The engine now uses the Lua crate's enum everywhere, and that enum renders the same Static Set and Dynamic Set text, so every merge error message reads exactly as before.

- `OfferMode` gains a `Display` impl that writes `Static Set` and `Dynamic Set`, the same text the deleted engine enum wrote, so one enum now serves both offers and the engine's frozen sets.
- `FrozenSet` is deleted with its `Display`, and `set_of`, `set_mut`, `check_set`, `lay_out`, and the set pairing in `refuse_clash` take or match `OfferMode` instead. The engine reaches it through the `crate::lua` import list it already used.
- `SetTool` types its `set` field as `OfferMode`, imported straight from `runvil_lua`, so the set-error module no longer imports `execute::scope` and the import cycle between the two modules is gone.
- `rule_five_fails_a_wire_name_used_twice_within_one_set_or_across_the_sets` now asserts the full message for a wire-name clash across the Static Set and Dynamic Set, which pins the set names the message interpolates. The other scope tests change only which enum names the set; their expected rules and messages stay as they were.
- `OfferMode` keeps its name, variants, and derives; the `Display` impl is the only change to it.

Design: removes parallel-abstraction @ crates/runvil-internal/engine/src/execute/scope.rs::FrozenSet
Design: removes cyclic-dependency @ crates/runvil-internal/engine/src/error/sets.rs
Plan: vibe/2026-10-27-1-pr172-debt-removal.md
Add a test that checks every discovery tool id parses as a tool id whose Pack is reserved. The discovery ids and the reserved Pack list are spelled out separately, so renaming one without the other would let a vendor Pack take the discovery namespace with nothing to catch it. Both ids already sit under a reserved Pack, so the test guards against a future rename rather than fixing a current failure.

- `crates/runvil-internal/lua/src/scope.rs` changes only by a `#[cfg(test)]` module at its end, loaded from the sibling `scope-tests.rs` through `#[path = "scope-tests.rs"]`. The file had no test module before.
- `every_discovery_tool_id_sits_under_a_reserved_pack` walks `DiscoveryTool::ALL`, parses each `tool.id()` with `ToolId::parse`, and asserts `id.pack().is_reserved()`, with a failure message naming the id. An id that stops parsing as a tool id fails the test through `expect` as well.

Plan: vibe/2026-10-27-1-pr172-debt-removal.md
The two shared tool-dispatch functions took an optional call counter, but both engine call sites passed nothing because the engine had already counted the call at dispatch. This change removes the parameter, the increment it guarded, and the lint exemptions its long parameter list needed. The engine's dispatch path behaves as before, and the docs and tests stop describing a count taken here.

- `prepare_dispatch` and `prepare_model_dispatch` no longer take `counts: Option<&ToolCallCounts>`, so `prepare_dispatch` drops its `if let Some(counts) = counts` increment and `prepare_model_dispatch` stops forwarding the argument. Both lose their `clippy::too_many_arguments` expectations, since seven parameters no longer trip that lint and an unfulfilled expectation would fail clippy.
- `accept_tool_call` passed `None` for the counts at both of its dispatch calls, so dropping the argument leaves that path's behavior unchanged. Both functions keep their return types, so its error handling stays as it was.
- `prepare_dispatch` now documents only `Error::Tool` under `# Errors`, dropping the error for a counter id that was never seeded. `prepare_model_dispatch` documents that it propagates every other `prepare_dispatch` error and turns the tool's own failure into the call's result.
- `prepare_dispatch_wraps_a_canned_untrusted_output_and_reports_it` loses the `ToolCallCounts` local and the assertion that the tool's count reached 1, and keeps its other assertions. The other dispatch test calls only drop their `None` argument.
- `ToolCallCounts` itself is untouched; the diff removes only its import and its uses in the dispatch module and its tests.

Plan: vibe/2026-10-27-1-pr172-debt-removal.md
The error-chain renderer's doc gave an Engine error variant as its example of a variant that copies its source's text into its own message, and that variant no longer exists. The example clause is gone and the paragraph is rewrapped. The skip rule it documents and the code are unchanged.

- `display_chain` The doc still says a cause is skipped when its text already appears in the text rendered so far, by a plain substring test. Only the doc paragraph changes, and the function body does not.
- `LuaRuntime` The doc now names no example variant at all, rather than a substitute.

Plan: vibe/2026-10-27-1-pr172-debt-removal.md
Plan: vibe/2026-10-27-1-pr172-debt-removal.md

This branch was successfully deployed

1 active deployment
github-pages — cbfc21f6 Deployed Oct 30, 2026 by vinniefalco via deploy #147
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant