refactor(skill,agent): stop enforcing a skill's allowed-tools - #3464
Conversation
📝 WalkthroughWalkthroughThe change replaces skill-declared allowed-tool enforcement with active-skill availability gating. It updates runtime state, metadata, prompt output, load-skill responses, public exports, tests, and documentation. ChangesSkill availability model
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Model
participant AgentRuntime
participant SkillPolicyEnforcement
participant SkillTools
Model->>AgentRuntime: Emit tool calls
AgentRuntime->>SkillPolicyEnforcement: Check tool availability
SkillPolicyEnforcement->>SkillTools: Evaluate active-skill references and scripts
SkillTools-->>SkillPolicyEnforcement: Return availability
SkillPolicyEnforcement-->>AgentRuntime: Allow tool or return an error
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cb8cbfa623
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/agent/runtime/tool-exposure-runtime.test.ts (1)
174-174: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy liftRestore the unaffected tool-exposure regression tests.
Restore the tests for deferred stream rejection, custom
tool_search, provider fallback, remote-tool capacity, and exposure-budget restoration. Keep skill-policy tests removed becauseallowed-toolsenforcement no longer applies.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/agent/runtime/tool-exposure-runtime.test.ts` at line 174, Restore the removed regression tests covering deferred stream rejection, custom tool_search, provider fallback, remote-tool capacity, and exposure-budget restoration in the relevant test suite. Leave the skill-policy tests deleted, since allowed-tools enforcement no longer applies.Source: Coding guidelines
🧹 Nitpick comments (1)
src/agent/runtime/skill-policy.test.ts (1)
22-25: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRename the tests that still describe the removed policy concept.
The source no longer has an active skill policy. Line 22 and line 88 name a concept that no longer exists. Line 88 also does not describe the two blocking assertions on lines 90 and 91.
♻️ Proposed rename
- it("should allow any tool when no policy is active", () => { + it("allows an ordinary tool when no skill is active", () => {- it("should always allow load_skill regardless of policy", () => { + it("allows load_skill but blocks skill resource tools when no skill is active", () => {Also applies to: 88-92
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/agent/runtime/skill-policy.test.ts` around lines 22 - 25, Rename the tests around enforceSkillPolicy so they describe the current no-active-policy behavior rather than the removed “active skill policy” concept. Update both the test at line 22 and the test covering the blocking assertions near lines 90–91, ensuring the latter name accurately reflects both blocked-tool cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/architecture/05-agent-runtime.md`:
- Around line 152-162: Remove obsolete allowed-tools runtime-enforcement claims
from docs/architecture/05-agent-runtime.md at lines 129, 134, 142, and 202,
including tool restriction, pattern validation, filtering, and exact/prefix
policy references; keep the description focused on skill-resource gating. In
docs/concepts/skill.md at lines 3-8, rewrite the Characteristics statement so
allowed-tools is presented as metadata rather than a limit on active actions.
In `@docs/guides/agents.md`:
- Around line 295-300: Update the documentation describing load_skill behavior
in the surrounding runtime contract: remove claims that loading a skill
activates an active policy or rejects tools based on allowed-tools, and state
that load_skill only makes the skill reference and scripts available to
subsequent calls while ordinary tools remain available.
In `@src/agent/runtime/skill-metadata.test.ts`:
- Line 3: Update the assertion import in the skill-metadata test to use
`#veryfront/testing/assert.ts` instead of `@std/assert`, while preserving the
existing assertEquals, assertExists, and assertThrows usages.
In `@src/agent/runtime/skill-policy-enforcement.ts`:
- Around line 391-396: Update the error construction in the skill-tool
availability check to distinguish an inactive or undefined skill from an active
skill lacking a matching file. Use skillToolAvailability and its hasActiveSkill
state to tell the model to call load_skill first when no skill is active; retain
the existing unavailable-file message for active skills without a match.
In `@src/agent/runtime/skill-policy-same-step.test.ts`:
- Around line 12-13: Update the assertions in the same-step batching tests
around SAME_STEP_GATE_ERROR, ACTIVE_POLICY_ERROR, and the referenced lines to
verify that no tool error is surfaced, rather than checking for removed literal
messages. Ensure the assertion marker is valid for both generate and stream
transports, splitting the checks by mode if their error representations differ.
In `@src/skill/allowed-tools.ts`:
- Around line 8-10: Replace the em dash in the public module documentation near
the allowed-tools explanation with a colon or comma, preserving the existing
meaning and wording otherwise.
In `@src/skill/parser.test.ts`:
- Around line 401-409: Add a focused regression test for
validateSkillFileMetadata covering the spec-conformant allowed-tools value
"Bash(git:*)", alongside the existing [] and "Read" cases, and assert that
strict-path validation accepts and preserves the pattern before modifying
parseStrictAllowedTools.
In `@src/skill/tools.ts`:
- Around line 485-486: Remove allowedTools from the load_skill return object and
the SkillContent type, then update affected tests and catalog emission so
allowed-tools is no longer exposed or generated; keep the response limited to
instructions, references, and scripts.
In `@src/skill/validation.ts`:
- Around line 201-205: Update the allowedTools normalization in the validation
flow to validate every entry is a string, enforce the strict parser’s existing
count and per-entry length bounds, and then freeze the copied array. Preserve
the removal of restrictive pattern matching and the existing undefined handling;
do not rely on the rawAllowedTools as string[] cast alone.
---
Outside diff comments:
In `@src/agent/runtime/tool-exposure-runtime.test.ts`:
- Line 174: Restore the removed regression tests covering deferred stream
rejection, custom tool_search, provider fallback, remote-tool capacity, and
exposure-budget restoration in the relevant test suite. Leave the skill-policy
tests deleted, since allowed-tools enforcement no longer applies.
---
Nitpick comments:
In `@src/agent/runtime/skill-policy.test.ts`:
- Around line 22-25: Rename the tests around enforceSkillPolicy so they describe
the current no-active-policy behavior rather than the removed “active skill
policy” concept. Update both the test at line 22 and the test covering the
blocking assertions near lines 90–91, ensuring the latter name accurately
reflects both blocked-tool cases.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 174dfc5f-814d-4902-ba46-958b9a83531e
📒 Files selected for processing (37)
docs/api-reference/veryfront/agent.mddocs/api-reference/veryfront/skill.mddocs/architecture/05-agent-runtime.mddocs/concepts/framework-primitives.mddocs/concepts/skill.mddocs/guides/agents.mddocs/guides/choose-a-primitive.mdsrc/agent/conversation/delegation-policy.tssrc/agent/factory-call-context.test.tssrc/agent/hosted/cloud-runtime-system-messages.test.tssrc/agent/index.tssrc/agent/runtime/agent-loop-skill-state.test.tssrc/agent/runtime/agent-loop-skill-state.tssrc/agent/runtime/agent-runtime-step.test.tssrc/agent/runtime/agent-runtime-step.tssrc/agent/runtime/call-context.test.tssrc/agent/runtime/index.tssrc/agent/runtime/load-skill-tool.test.tssrc/agent/runtime/load-skill-tool.tssrc/agent/runtime/project-skill-catalog.test.tssrc/agent/runtime/project-skill-catalog.tssrc/agent/runtime/refresh.test.tssrc/agent/runtime/skill-metadata.test.tssrc/agent/runtime/skill-metadata.tssrc/agent/runtime/skill-policy-enforcement.tssrc/agent/runtime/skill-policy-same-step.test.tssrc/agent/runtime/skill-policy.test.tssrc/agent/runtime/skill-prompt.test.tssrc/agent/runtime/skill-prompt.tssrc/agent/runtime/tool-exposure-runtime.test.tssrc/skill/allowed-tools.test.tssrc/skill/allowed-tools.tssrc/skill/index.tssrc/skill/parser.test.tssrc/skill/parser.tssrc/skill/tools.tssrc/skill/validation.ts
💤 Files with no reviewable changes (1)
- src/agent/index.ts
cb8cbfa to
94446c2
Compare
The Agent Skills specification defines `allowed-tools` as tools pre-approved to run without prompting, not an authorization boundary. Veryfront enforced it as a restrictive allowlist, which is the inverse, and rejected spec-conformant values: our pattern grammar accepted only exact ids and `prefix:*`, so the spec's own `Bash(git:*)` example failed to parse. Remove the enforcement, the pattern grammar, and every prompt sentence that told the model to stay inside the list. The field is still parsed and bounded, per the spec's field list, but nothing reads it. Skill tool availability is unaffected: `load_skill_reference` and `execute_skill_script` stay gated on the active skill advertising a reference or script. `enforceSkillPolicy` keeps its `form_input` rules. Not a security change - any tool a skill "blocked" was already reachable by not loading that skill. It is a behaviour change: a skill whose instructions assume a narrow tool set now runs with the full set. Closes veryfront/veryfront-issue-inbox#406. Claude-Session: https://claude.ai/code/session_01Xo93b6StAu691YV9g8Fm53
94446c2 to
8d01f8e
Compare
Keep structural validation of `allowed-tools` even though the runtime no longer enforces it: entries are parsed from an untrusted skill file, stored and surfaced, so they must still be bounded, printable strings. - validation: re-check entry type, length, count and control characters - tools: stop returning `allowedTools` from load_skill; it would imply a policy the caller does not get - types: drop the now-vestigial field from SkillContent/ActiveSkillContext - enforcement: distinguish "no skill loaded" from "tool not in this skill" - tests: drop two dead literals that made assertions vacuous, count tool errors per scenario, add strict-path coverage for `Bash(git:*)` - docs: describe the gate as it now behaves
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/architecture/21-agent-tool-registration-current-state.md`:
- Around line 194-195: Update the documentation wording around the three-tool
skill surface to state that load_skill remains available independently of
availability gating. Specify that only load_skill_reference and
execute_skill_script are gated by the active skill’s advertised references and
scripts, while preserving the statement that execution occurs through Veryfront.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a3269e14-10e5-4c4f-8a40-a4a6332c3025
📒 Files selected for processing (12)
docs/architecture/05-agent-runtime.mddocs/architecture/21-agent-tool-registration-current-state.mddocs/guides/agents.mdsrc/agent/runtime/skill-metadata.test.tssrc/agent/runtime/skill-policy-enforcement.tssrc/agent/runtime/skill-policy-same-step.test.tssrc/skill/allowed-tools.tssrc/skill/parser.test.tssrc/skill/tools.test.tssrc/skill/tools.tssrc/skill/types.tssrc/skill/validation.ts
💤 Files with no reviewable changes (2)
- src/skill/types.ts
- src/skill/tools.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- docs/architecture/05-agent-runtime.md
- src/agent/runtime/skill-policy-enforcement.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/api-reference/veryfront/skill.md`:
- Around line 80-81: Update the ActiveSkillContext entry in the API reference
table to describe tracking active-skill availability and delegation state,
replacing the outdated “runtime policy tracking” wording. Leave the
AgentCapabilityScope entry unchanged.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 436cad9b-8140-4b4a-a6ab-513fb55122f1
📒 Files selected for processing (1)
docs/api-reference/veryfront/skill.md
Closes veryfront/veryfront-issue-inbox#406.
Veryfront enforced a skill's
allowed-toolsas a restrictive allowlist: while a skill was active, any tool outside its list was refused. The Agent Skills specification defines that field as the opposite thing — tools pre-approved to run without prompting, not an authorization boundary. No other framework enforces it either; Anthropic's SDK docs call skill selection "a context filter, not a sandbox".This deletes the enforcement and keeps only what the spec actually specifies.
The conformance bug this fixes
Our pattern grammar accepted exact ids and
prefix:*wildcards, and rejected everything else fail-closed. The spec's own documented example isallowed-tools: Bash(git:*) Bash(jq:*) Read— which our parser rejected. That was pinned by a test assertingnull:Any skill authored to the portable spec was either rejected outright or silently over-constrained. Dropping the grammar is required for conformance, not just cleanup.
What the model sees now
Rendered from a skill declaring
allowed-tools: Bash(git:*) Read, in a run exposingRead,Write,api:list:Bash(git:*)now parses (["Bash(git:*)","Read"]), and none of it reaches the model.Prompt text removed
skill-prompt.ts— "If a skill specifies allowed tools, you MUST stay within the current-run intersection of those tools."LOAD_SKILL_TOOL_INTERSECTION— "If the current run exposes fewer tools than the loaded skill metadata, use only the tools that are actually available right now." Its referent was the metadata's tool list, which this PR stops emitting, so the sentence no longer had anything to compare against.load_skilltool description — "If the skill specifies allowed-tools, you MUST only use those tools while following this skill."load_skilltool description inskill/tools.ts— advertised that the return value includes an "allowed tools policy".allowedToolsNote,noCurrentRunToolsNote,unavailableCurrentRunToolsDelegationNote.allowedTools/allowedToolsDeclaredfrom every<available_skills>catalog record.Prompt text kept
This one addresses skill bodies — prose we don't control that may name a tool the run lacks. It is unaffected by this change.
What still enforces
Skill tool availability is not the same thing and stays.
load_skill_referenceandexecute_skill_scriptremain gated on the active skill actually advertising a reference or script.src/skill/allowed-tools.tsshrinks from 305 lines to the availability layer only; it does not disappear.enforceSkillPolicykeeps itsform_inputrules unchanged — blocking repeat intake after submission, and blocking skill-body switches once a form is submitted. Those never depended onallowed-tools.allowed-toolsis still parsed from frontmatter and carried on the definition, per the spec's field list, and still bounded (pattern count and length) because it arrives from untrusted frontmatter. It is simply never read by the runtime.src/skill/allowed-tools.ts:89—if (allowedTools === undefined) return true— is gone along with its function, but the property it encoded is preserved: no policy means no restriction.Behaviour change
Skill activations that previously had their tool surface narrowed no longer do. This is not a security change — any tool a skill "blocked" was already reachable by not loading that skill, so nothing becomes newly reachable. It is a real behaviour change: a skill whose instructions assume "you only have these three tools" now runs with the full set. Worth a release note.
Two runtime call sites reflect this and are pinned by updated tests:
refresh.test.ts— after form submission the advertised set goes from["load_skill"]to["load_skill", "load_skill_reference", "read_secret"].read_secretis no longer withheld;load_skill_referenceis still gated on an advertised reference.tool-exposure-runtime.test.ts— a tool that was only "unauthorized" via skill policy is now exposed. Deferred loading still refuses to execute an unexposed tool, which its two surviving tests cover.A regression caught mid-change
Collapsing
getEffectiveAvailableToolNamesinitially passedavailableToolNamesstraight through. The old helper returned[]forundefined, so an unknown tool inventory used to suppress delegation advice; passingundefinedthrough made it emit "call invoke_agent" against an inventory we cannot see. Restored explicitly:createRuntimeLoadSkillTool omits delegation advice when tool inventory is unknowncovers it.Tests
Tests asserting the removed policy are deleted, not weakened — roughly 20 cases across 8 files, including the two
skill-policy-same-step.test.tscases added by #3463 that asserted a batched tool could still be denied by a freshly activated policy.Where a test's real subject survived, it was repointed rather than dropped:
allowed-tools.test.tsrewritten around availability; it now pins that no declaration can deny an ordinary tool.parser.test.ts—should reject invalid allowed-tools patterninverted intoshould accept the spec's own Bash(git:*) example verbatim.load-skill-tool.test.tsaccessor-rejection probe repointed from a removed message field toreferenceNote.tool-exposure-runtime.test.ts— one test deleted because its only source of "unauthorized" was the policy; its siblingdeferred generate rejects a guessed tool that was not exposedcovers the same mechanism independently.Verification:
deno test src/ cli/— 3817 passed, 28201 steps, 0 faileddeno test src/skill/ src/agent/— 1192 passed, 2048 steps, 0 faileddeno task typecheck— cleandeno lint src/— cleandeno fmt --check— cleandeno task docs:api-reference:check— regenerated (veryfront/agent.md,veryfront/skill.md)--no-check(asdeno task testdoes); type coverage comes fromdeno task typecheckplus an explicitdeno checkon every touched test fileDocs
docs/guides/agents.md:318claimedallowed-toolsis "enforced at planning time and execution time (fail-closed)" — the precise opposite of the new behaviour. Rewritten to state it is not enforced and to point at agent tool configuration for narrowing a run.05-agent-runtime.md,concepts/skill.md,framework-primitives.md, andchoose-a-primitive.mdalso described skills as providing "tool policy"; corrected.Deliberately not in scope
The issue also lists collapsing the three accepted spellings (
allowed-tools,allowed_tools,allowedTools) to the spec's one. The frontmatter schema is.passthrough(), so the extra spellings are now inert — nothing reads the parsed value. Removing them is pure churn with a small breakage risk for existing skills, so it is left out; noted on #406.Breaking change: four removed exports
veryfront/skillno longer exportsisToolAllowedBySkill,snapshotAllowedToolPatterns,validateAllowedToolPatternsorvalidateStrictAllowedToolPatterns. This is deliberate, and deprecated shims are not the right call here.Every one of these functions answers the question "may this tool run under the active skill?" — the question this PR establishes has no meaningful answer, because
allowed-toolsis pre-approval metadata, not an authorization boundary. A retainedisToolAllowedBySkillwould keep returningfalsefor tools the run can in fact call. That is a worse outcome than a compile error: the caller keeps a check that silently means nothing, instead of being told to move the decision to the agent's tool set, which is where it actually belongs.Callers should configure the agent's tools to control what a run can do. Structural validation of the field survives in
validateSkillMetadata/validateSkillFileMetadata, so a malformedallowed-toolsis still rejected at parse time.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
load_skillremains available, and batched tool calls follow model-emitted order.isSkillToolAvailablefor checking skill-related tool availability.Bug Fixes
Documentation
allowed-toolsis not an authorization boundary.