Repository navigation
Tool surfaces need policy and provenance, not a hard-coded list (data + a proposal for dsh-superagent) #11765
Replies: 1 comment
|
Source-level follow-up from the same review (no live dsh-superagent run). Environment: DSH 2.0.21-next desktop, profile In Two related notes:
Worker sessions build their allow list from the parent scope's visible names ( |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Tool surfaces need policy and provenance, not a hard-coded list
We build a small DSH plugin that hides unused tool groups (lazy-tools). While studying
dsh-superagent-prototypewe measured a few things about tool surfaces and ran anexperiment. This post shares the measurements, a proposal, and the traps we hit.
Claims are tagged [measured], [source-read] or [inferred]; anything we did
not verify says so.
1. A full profile, measured through our own layer
On one Windows machine (DSH 2.0.21-next desktop, a profile with about 20 bundles),
using our own bench — that is, the plugin being argued for, so the "after" is a layer,
not a neutral baseline:
[measured — one profile, one machine; per-layer attribution of the 74 names not
visible is not shown here];
cua_driver_native__*family (37 + 19) [measured via our probe];agent.ctx.tools.restrict({ deny: cuaToolNames })and defaultscommandModetotrue [source-read]. We did not run
/computer-use on, so we have notobserved that gate lifting [inferred].
The general lesson: a large registry is normal; what matters is how many schemas go
on the wire, and who decided that.
The painful part is attribution:
tools.schemas(agent)omits a name without sayingwho omitted it. Our own probe gives layer index and deny counts, not an owner, so a
desktop permission gate and a third-party plugin's
restrictstill look alike. Asmall "why is this name missing" probe with per-layer counts (ours is
layersFor) helps; it did not, by itself, name the owner.2. The prototype's role surface is a list ∩ mount
src/app/sessions.tsdefines [source-read]:WORK_TOOLS— 20 names for worker Sessions;cordis.patch.ymldecide what is mounted at all.The allow-list is the only place a role's surface is expressed; the effective
surface is that list ∩ what the preset mounts. Two drift examples from reading
the source:
web_fetchis inWORK_TOOLS, but the worker preset mountstool-webwithfetch: false, so the list expects a tool the mount does not provide[source-read; the mount set is our model — we have not run a live instance];
bash, while on Windows the preset mountspwshanddisables
bash. The allow-list is applied; if that is all that happens, the mainSession loses its shell on Windows [source-read allow-list; inferred mount
behaviour; not verified on a live Windows instance].
The structural issue is not the 20 names. It is that a role's surface is expressed
as names in code, so any new tool (MCP server, browser, PDF reader, document
converter) needs a source change, and every fork repeats it.
3. Proposal: role policy, same defaults
Add a settings object, not a new subsystem:
read,web_*,mcp__github__*). The policy isstatic: it is evaluated when a Session is created (and again when its preset
changes), not per turn.
(
unknown: hide).allowGroupsare settings-only. Group bindings must beadmin-owned too — a model-callable group editor would let a tool move itself into an
already-allowed group.
diff reporter (policy vs the current lists), not settings history or an apply path.
4. What we verified (logic level, not a live instance)
We wrote a logic simulation (no DSH, no live superagent) that resolves the proposed
policy over a modelled worker mount set. It runs six checks; two of them were
rewritten after an independent review because they restated their own premises
(
web_fetchwas in no mount list; the router check compared two hardcoded arrays).All six now pass:
WORK_TOOLS ∩ mountedexactly (14 distractor toolsincluded);
web_fetch) is absent, and thecheck now asserts both sides of that drift;
current lists);
pwsh(the platform drift);acme_*) makes third-party tools visible with two adminedits — a match rule plus the role authorising that group — and no code change.
Not verified: a real
dsh-superagentinstance, the real mounted set, token/cachenumbers, and model behaviour.
5. Traps for anyone building this
a role preset before the first turn [source-read in
sessions.ts]. Resolvingthe role once at
agent/createdcan therefore see the wrong preset. We added anagent-preset/selectedlistener to our plugin and a test that reproduces thetiming (smoke
[62]); the listener lives inlib/index.js, not in the policymodule.
token [inferred from the DSH adapter's documented behaviour; not measured here].
A policy resolved once per Session is safe; a mid-session release is not. Per-ticket
Sessions already have different prompts, so cross-ticket reuse is limited
[inferred].
visible, a newly installed MCP server silently gains access. Default hidden, and
report it.
restrictdecides what the model sees; guardsand approvals decide what may run. Keep them separate.
6. Question
Is this direction useful for the prototype? Would a PR in this shape (a settings
schema + a resolver + an equivalence test that pins the default to today's lists) be
considered — or is the intended answer that the tool surface stays a constant until
the project is out of research? We can share the test and the 4-role prototype; they
are small and dependency-free (165 and 139 lines, no imports).
All reactions