vigiles API reference
    Preparing search index...

    Function onlyTools

    • The agent used only tools drawn from allowed — the white-list, and the missing half of a symmetry the file surface already has:

      "not this one" "nothing but these"
      files assertNoWrite assertWroteOnly
      tools notTool onlyTools (this)

      Why the asymmetry mattered: notTool can only forbid the calls the test author thought of, and the set of undeclared tools is unbounded — so "this skill stayed inside the tools it declares" was not expressible, which is exactly the claim a SKILL.md frontmatter makes in prose. See skillContract, which builds this check from that declaration.

      Fails closed on an empty trace. A Trace records tool calls only when the run captured the stream (transcript: true on the harness tier; always on the eval tier), so an uncaptured run is indistinguishable from a tool-free one — and passing it would assert nothing. Same discipline as assertWroteOnly refusing a result that never recorded writes: "we didn't look" is not "it was clean".

      🔴 A Tool(restriction) DECLARATION IS MATCHED BY ITS BASE NAME. allowed-tools: Bash(git:*) is the ordinary way to write a narrow grant, and skillContract hands that literal string here — while a trace only ever carries the base name Bash, because that is the tool the harness reports. Set membership therefore missed, and every legitimate Bash call was reported as outside the declared surface, against a declaration that literally lists Bash (measured 2026-08-11: agent used tool(s) outside its declared set: Bash (declared: Bash(git:*), Read, Skill)). A check that fires on its own happy path gets deleted, not debugged — and it would fire on exactly the authors who narrowed their grant.

      ⚠️ AND THE RESTRICTION ITSELF IS NOT VERIFIED HERE, deliberately. A trace names the tool, not the grant it was matched against, so Bash(git:*) can be held to "no tool outside the declared set" and no further; whether the command really was a git one is a claim this layer has no evidence for. Narrowing beyond the base name is disallowed-tools: + lethal-trifecta's job (see skill-contract.ts), which reads the grant instead of the run. The same split("(") normalization the rest of the codebase already applies to tool declarations — core/tool-contract.ts, core/lethal-trifecta.ts, core/delegation-trifecta.ts.

      ⚠️ SPANS THE WHOLE TRACE, deliberately. It answers "did this RUN stay inside these tools?", which is the right question for a harness that drives one thing. It is the wrong question when the trace has setup calls before the surface under test activated, drives several skills, or dispatches a subagent whose nested calls the parser folds into the same flat list — and reporting those as a violation is an accusation about the wrong code.

      skillContract therefore does NOT use this directly: it wraps it in a window bounded by its own Skill activation (see duringSkill in src/skill-contract.ts). If you are checking one skill, prefer the contract; reach for this when the claim really is about the whole run.

      Parameters

      • allowed: readonly string[]

      Returns Check<Trace>