Skip to content

Finish the shared tool catalogue, and let a person agree to a draft's test run - #113

Merged
ntatschner merged 8 commits into
mainfrom
tool-catalogue-live
Sep 24, 2026
Merged

ntatschner merged 8 commits into
mainfrom
tool-catalogue-live

Conversation

@ntatschner

Copy link
Copy Markdown
Owner

The rest of the shared tool catalogue from team run 20260923-1216-be60, and the one thing its first live trial found missing: a way for a person to agree to a draft's test run.

What it does

  • The Tool Creator nominates more than scripts. Repeated workflows, prompts and integrations (MCP servers and HTTP hosts used by two or more teams) are nominated as well as remedies and command shapes. The nominator evicts runs that leave the listing from its cache.
  • Every team node can search the catalogue. loadout_tools_search and _show for every member role; _submit and _used for every role except reviewer and verifier.
  • The Refiner measures tool health. loadout tools health: case times, recent failures and workarounds, size and churn, and whether anyone still uses a tool. A crossed threshold brings a tool back to the Refiner, but only when it is new since the last stand-down.
  • loadout tools verify <draft> --agree. Shows the script and every case it would run, with the fingerprint of both, asks, records the agreement in the machine's configuration and runs the cases. It is refused anywhere it cannot ask (piped input or output, --json, --non-interactive), which is what a team node's shell looks like; the creator and refiner roles may run tools verify and must not be able to agree to their own scripts. An answer settles a rule of ask for that run, never stays never, and changing the script or a case afterwards is held again.
  • A draft named relative to the catalogue's drafts is found there, as the help always said, rather than against the current directory. Climbing out is still refused.
  • Investigators record each use of a catalogue tool with loadout_tools_used.

Why --agree was needed

Until now nothing a person could type wrote the agreement a draft's harness run needs (a TrustedRemedy named tool-test:<name>@<version> with its fingerprint): tools trust takes only promoted versions and team remedy trust only remedies in a team's directory. Every verify on a real machine was held. GlobalToolLifecycleTests passed throughout because ToolStoreFixture.Agreed writes that agreement itself.

Evidence

A live trial on this machine, with real agents:

  • The daemon's nomination pass filed seven nominations from finished runs. tool-works run 20260924-1750-3b1c drafted git-tree-clean from one of them (inbox/2026-09-24-53fb0737), stood the other six down with reasons, and was held for a person.
  • With this branch installed, the draft was verified at a terminal with --agree (every case passed) and promoted as git-tree-clean@1.0, owner tool-works.
  • system-watch run 20260924-1853-2a26, on a different project, found the tool by catalogue search without being told its name, ran it with that project's path and met all three of its criteria.
  • Run 20260923-1216-be60 was resumed with both run ids, checked them itself, and ended done, 26 of 26 criteria met.

Tests:

  • Full suite on this branch: 3368 passed, 0 failed, 22 skipped.
  • New: ToolAgreementTests (five, through the registry rather than the fixture), a contract test that --agree over pipes is refused and records nothing, and a safety test for the drafts-relative name.
  • Mutation-checked: dropping the terminal guard, letting an answer overrule never, fingerprinting without the cases, and dropping the drafts-relative lookup each fail a test.

Limits

  • Recording a use: the second team ran the tool but did not record the use, so its usage still reads 0. The investigator rule in this branch addresses that for investigators only.
  • Searching: an earlier system-watch run (20260924-1845-6e12) answered by hand without searching, though every brief points at the catalogue. Searching is not yet a habit unless the goal asks for it.
  • Verify time: a promoted manifest records tests.ranAt as 0001-01-01. Tracked separately.
  • Telling a node from a person: nothing marks a team node's process as a node. --agree relies on the terminal instead; tools trust and team remedy trust still have no such guard, which is the open task on write scope and trust.

Nigel added 8 commits September 24, 2026 19:33
Three new rules for ToolNominator, each filed through IToolRegistry.Nominate
with the existing filed-key dedup, secret screen and active-tool hinting:

- Rule 5, workflow: the longest ordered run of at least three passing
  command shapes (rule 3's shapes) that runs of two different teams share.
  Back-to-back repeats of one command collapse to a step, so a retry is not
  a workflow. Report documents are now read in the order they were written.
- Rule 6, prompt: a paragraph of 30 or more words, folded for whitespace
  and case, found in the briefs or reports of runs of two teams. Filed as a
  candidate with capability 'prompt'. It carries the paragraph as first
  written, not folded, because a lower-cased credential gets past the
  secret screen; the mutation check showed exactly that. Only the run ids
  are recorded as origin, never the task around the paragraph.
- Rule 7, integration: an MCP server (from mcp__<server>__ tool names) or
  HTTP host used by nodes of two teams. RunDocuments lists only briefs,
  reports, policies, questions and answers, so the source is both report
  evidence refs and the node streams (stream-*.jsonl) that sit beside them.
  Loadout's own server and loopback are left out, because every node uses
  them.

tool-creator.md now covers the three kinds. A prompt tool is drafted as a
script that prints the text with its placeholders filled from named inputs,
because the harness runs scripts and checks output; a bare text file could
not be run or given cases.
The review of 3217b25 found four ways the new workflow, prompt and
integration rules nominated the wrong thing or did the work twice.
Each has a test that fails without its fix.

- Workflows: back-to-back repeats of one step (a retried build) made
  two workflows out of one. Steps are now collapsed before they are
  compared. Back_to_back_repeated_steps_collapse_into_one.
- Prompts: the boilerplate the runner writes into every lead's brief
  was counted as a prompt two teams had both written. It is now
  excluded, and a shared paragraph naming a project path is kept out
  of the inbox. Runner_boilerplate_in_two_teams_lead_briefs_is_not_a_prompt,
  A_shared_paragraph_naming_a_project_path_does_not_reach_the_inbox.
- Cost: every pass built a new ToolNominator, so a finished run's
  streams were read again on every schedule. ToolNominationPass now
  keeps one for its lifetime.
  A_second_pass_does_not_read_a_finished_runs_streams_again.
- Integrations: an existing tool covered an MCP nomination if its
  script contained the server's name anywhere, so a script clearing a
  github runner's cache covered "mcp github". Coverage now needs the
  script to call mcp__<name>__, or to name an HTTP host whole.
  An_unrelated_script_mentioning_github_does_not_cover_an_mcp_github_nomination.

Mutation checks: the MCP pattern replaced by a bare \bgithub\b fails
the github test; 'at == 0' changed to 'at >= 0' in Collapsed fails the
collapse test. Full suite: 3344 passed, 0 failed, 22 skipped.
ToolNominator keeps what it read of each finished run, by run id, so a
long-lived pass does not re-read the same streams on every schedule. Nothing
ever removed an entry, so a run that was forgotten, or pushed beyond Depth by
newer runs, stayed in memory for as long as the daemon lived. Finished() now
drops every cached run whose id was not among those _journal.List(Depth)
returned this time.

A_run_that_leaves_the_listing_is_evicted_from_the_cache reads two finished
runs, deletes one folder, finds again, and expects one entry left. It failed
before the change (expected 1, found 2). It observes the cache through a new
internal Cached count.

ToolNominationPass keeps one nominator for its life, and nothing pinned that.
A_second_pass_does_not_read_a_finished_runs_streams_again rewrites a run's
report between passes: a fresh pass loses the nomination, and the same pass
still returns it, because its nominator does not read the run again.

Mutation checks: replacing TryRemove with TryGetValue in the eviction loop
failed the eviction test; building a new ToolNominator on every
ToolNominationPass.BeforeAsync failed the pass test. Both were restored from
copies.

Correction to 78b6c8f: its message says "Steps are now collapsed before
they are compared." They already were: collapsing came in 3217b25, and
78b6c8f added only its test, Back_to_back_repeated_steps_collapse_into_one.

Suite: Passed 3346, Failed 0, Skipped 22, Total 3368.
Run 20260923-1216-be60's lead was refused loadout_tools_search with
"Nothing in the role.project-lead role allows" it, although the brief
every node is given tells it to search the catalogue before building.
No role's front matter named the launcher's own catalogue tools, the
same gap MemberTools closed for progress and task_declare.

TeamRunner.AllowedFor now adds search and show for every role, and
submit and used for every role except reviewer and verifier: those judge
one piece of work and write nothing, so a lesson they find goes in their
report for the lead to submit. The list goes to both the agent's
--allowed-tools and the node policy the permission answerer reads, and a
role's deny list still wins.

Every_member_role_may_search_the_tool_catalogue iterates the real role
files through NodePermissions.Decide.
… the Refiner

The Refiner judged correctness and generality through the gate, but had
nothing to measure usability, maintainability, performance or continued
relevance with. ToolHealth computes all four per active tool from what
the registry already holds:

- performance: each case's time, now recorded at verify in the
  manifest's tests.seconds, with the median and the worst;
- usability: failed and workaround rates over the last 20 uses, and
  distinct teams per use;
- maintainability: non-blank script lines, inputs, dependencies,
  known-good versions and promotions in the last 90 days;
- relevance: days since last use (or since promotion when never used),
  and a newer active tool that overlaps it.

NeedsRefining now returns true when a threshold is crossed, even after
two stand-downs: a rate of 0.25 or more over at least 8 uses, a case
slower than 30 s, or 60 days idle while active. Each value is a named
constant on ToolHealth with its reason.

Exposed as 'loadout tools health [name] --json'; the Refiner role may run
it and is told to read it first and state its gain against those figures.
61f8050 let any crossed health threshold bring a tool back to the
Refiner however many times it had stood down. An idle tool crosses
"idle" on every pass for ever, so it would be looked at on every pass
and the two-stand-down stop rule (criterion 15) never applied to it.

A stand-down now records, by name, the thresholds the tool had crossed
when it was looked at (failed, workaround, slow, idle), in a new
optional `crossed` field on the audit entry. After two stand-downs with
no signal between them, NeedsRefining is true only if a threshold is
crossed now that the last stand-down did not record. ToolHealth carries
the same crossings by name in a new Thresholds list, because the
sentences in Crossed carry numbers (days idle, rates) that move.

Only a new name counts, not a bigger number. The rates and case times
change only through uses and promotions, and the failed or workaround
uses and the promotions start the count again on their own. Idleness
grows every day whether or not anything has changed.

A deliberate change in what a test expects:
NeedsRefining_turns_true_on_a_crossed_threshold_even_after_stand_downs
asserted the uncapped behaviour. It is now
A_crossing_the_stand_downs_already_saw_does_not_override_them, the same
setup asserting false. New: An_unchanged_crossing_after_two_stand_downs_is_not_refined_again
(an idle tool, a day more idle on its third pass) and
A_new_crossing_after_stand_downs_is_refined (it stood down twice while
healthy, then went idle). ToolStoreFixture.Registry takes an optional clock.

Also gives ChurnWindowDays the reason its neighbouring thresholds have.
A draft cannot be promoted until its cases have run, and they run only
when this machine has agreed to that script and those cases: a
TrustedRemedy named tool-test:<name>@<version> carrying their fingerprint.
Nothing a person could type wrote one. tools trust takes only promoted
versions, team remedy trust only remedies in a team's directory, and
without an entry RemedyCeiling.Decide asks whatever team-remediation
says. The first live tool-works run (20260924-1750-3b1c) drafted
git-tree-clean and was held for a person who had no way to answer.
GlobalToolLifecycleTests passed throughout, because ToolStoreFixture.Agreed
writes the agreement itself.

tools verify <draft> --agree shows the script and the cases it would run,
with the fingerprint, and asks. On yes it records the agreement in the
machine's configuration and verifies with ToolHarness.Answered: the
person's answer settles a rule of ask for this run, never stays never,
and the fingerprint still has to match, so changing the script or a case
afterwards is held again. IToolRegistry.Agreement gives the name and
fingerprint from the same read of the draft that VerifyAsync decides on.

--agree is refused wherever the command cannot ask (piped input or
output, --json, --non-interactive). That is a team node's shell: the
creator and refiner roles may run tools verify, and without this they
could agree to their own scripts. Nothing marks a node's process as a
node today, so the terminal is the only evidence of a person this
command has.

A draft named relative to the catalogue's drafts is now found there
rather than against the current directory, as the help always said;
a path from the current directory is still taken when nothing under
drafts has that name, and climbing out of drafts is still refused.
A held verify also says how a person can answer.

Tests: ToolAgreementTests (five, through the registry rather than the
fixture), a contract test that --agree over pipes is refused and
records nothing, and a safety test for the drafts-relative name.
Mutation-checked: dropping the terminal guard, letting an answer
overrule never, fingerprinting without the cases, and dropping the
drafts-relative lookup each fail a test.
In system-watch run 20260924-1853-2a26 an investigator found
git-tree-clean in the shared catalogue, ran it, and met every criterion,
but never called loadout_tools_used, although the goal said to record the
use and every brief carries the catalogue pointer that says so. The tool
still read "Used 0 times" afterwards. A pointer in the brief was not
enough; the role now says it as a rule.

Unrecorded use is not harmless: the Refiner's health measure and
retirement both read the usage records, so a tool that is used but never
recorded looks idle.
@ntatschner
ntatschner merged commit ef338ec into main Sep 24, 2026
13 of 14 checks passed
@ntatschner
ntatschner deleted the tool-catalogue-live branch September 24, 2026 19:42
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