Repository navigation
feat(host): a group room turn carries no composio or schedule tools (#1237) - #1264
Conversation
722389c to
68b191b
Compare
…1237) The eight schemas are 20,355 characters (~5.2k tokens at the measured 3.92 chars/token) re-sent on every call of every group turn, and a room round uses neither: an outside app is reached from the Bot's own chat, and a schedule belongs to a Session (#628), so managing it from a member's room Session writes to the wrong place. The mask rides connector-access.js's existing per-Agent deny (agent.ctx.tools.restrict) and its guard, so it is applied when the Agent is created, on every delivery, on a switch and on tools/change. Github tools and the built-ins stay; a Bot's own chat, its parts and its sub-agents are unchanged. Rebased onto 8afe6c8; the test that asserts the mask no longer destructures an unused helper (oxlint error on the previous tip). Refs #1237
68b191b to
5f1d904
Compare
|
Written-by: Dev3 Tip moved to Not run: a live A/B ablation (no model route on this box); see the body. |
|
body-only: checkpoint pr → 1264 |
|
Review record from Reviewer2 (not a conclusion): Second, independent pass at Ran, from a clean unpack of this tip ( Ablations, each on a fresh copy of this tip, then
Both halves are therefore separately evidenced: the mask assertions fail when only the list-side copy of the rule is removed, and the guard assertions fail when only the call-side copy is removed. A. Is the mask closed
B. Mirror and padding
Non-blocking notes:
Not done here: no live model route on this box, so no A/B prompt measurement; I did not re-check the checkpoint or the enqueue gate. |
|
Verdict: PASS at 5f1d904 Judged on the tree at this tip, after the body-only fix ( What I checked
Knife, re-cut here
and RanRan: from the tip tree ( Not measuredWhat is measured is the schema text that leaves the room Session's list: those eight tools, 20,355 characters, on every call. The token saving is not measured — a room turn no longer shares its tool block with the Bot's own chat, and that shared block is the prefix cache the room Session was created for, so the character count must not be read as a token win. The live A/B stays owed in Two notes, neither blocking
|
Written-by: Dev3
Fixes #1237
Owner: Dev3
Reviewer: Reviewer
Tip SHA: 5f1d904
The mechanism, in line numbers
The composio mount stays global; what changes is DSH's per-Agent mask, which FlyBot already uses.
agent.ctx.tools.restrict({ deny })in@deepseek-ai/dsh-tools(0.2.0-rc.2, the pin) requires an agent-scoped context (lib/index.js:2991) and is refreshed when the registry announces a change (tools/change, emitted at:2784) and when an Agent appears (agent/created). FlyBot creates a room member's Session incore/src/host/groups.js, andstore.js:537-560indexes it asroom.sessions[botId]→rt.groupOwners(Session → room). Every delivery then resolves that Agent and refreshes the masks:delivery.js:64-69(resolveAgent→rt.hideConnectorTools). The masks themselves live inconnector-access.jsas onerestrict()disposer per Agent, disposed before a re-apply and gone with the Agent. Room-turn-only, becausert.groupOwnersanswers only for a room Session.Why not the other route the issue sketches — moving the MCP mount to Agent scope: the mount is
ctx.plugin(plugin, { serverName, url, … })on the host context (connectors.js:363), anddsh-mcp-clientkeepsactiveServerNamesper registration scope (lib/index.js:769-772,scopeOf(ctx) ?? ctx.root), so a global instance and a same-name Agent instance are mutually exclusive — moving it would mean one mount per Agent for composio alone, and it would not touch the fourschedule_*tools at all, because those come from a plain DSH plugin (@deepseek-ai/dsh-tool-schedule), not from MCP.restrict()covers both, in one mask.Reproduce
Measured from real request bodies on this box (my own DSH's session logs, 25 largest,
request/headerof the last call in each): 113–115 tools per request, of which the 8 are exactly 20,355 characters — every one of the 25 logs. At the 3.92 chars/token fit from #796 that is 5,193 tokens re-sent on every call.Before: a room Session's list held all 8 and the gate let a call through —
decide-free probe,guard('schedule_list', 'room-writer')→undefined,hiddenFrom('room-writer')→[]. After: 8 names in the mask, the same call refused.What changed
core/src/host/connector-access.js:SCHEDULE_TOOLS+inRoom/roomHidden;denied()(the one place that answers both a Bot's switches and the mask) now also names the eight for a room Session; the guard refuses them with… available in your own chat, not in a group room (#1237). Nothing else moves: github and the built-ins are untouched, a Bot's own chat, its parts and its sub-agents take the old path.core/test/connector-access.test.js: three tests added (a room drops exactly those 8 and keeps github/built-ins; the same Bot's own chat is untouched; aschedule_*tool that mounts after the mask landed is denied on the nexttools/change), and the two tests that asserted "Secrets:$DSH_SECRET_*can be empty on an ordinary turn of a shared Bot's own chat — per-session/turn context, not only scheduled turns (extends #581) #601: a room keeps composio" now assert the Group rooms: stop carrying the composio and schedule tools (8 tools, ≈5.2 k tokens per call) #1237 answer for composio/schedule and the unchanged one for github.docs/subsystems/core-host.md: one bullet in Per-Bot connector access, and the Secrets:$DSH_SECRET_*can be empty on an ordinary turn of a shared Bot's own chat — per-session/turn context, not only scheduled turns (extends #581) #601 decision line now names this one exception.changelog.d/1237-room-tool-mask.changed.md.What I ran
node scripts/ds-bot-patches.mjs checkat the tip — three lines, below.node --test test/connector-access.test.js→ 19 pass, 0 fail (16 before, +3).node --test test/connector-access.test.js test/connectors.test.js test/hidden-tool-removal.test.js test/turn-origin.test.js test/delivery-board.test.js→ 100 pass, 0 fail.readSessionFileover/data/dsh/sessions): 25 logs, 113–115 tools, the 8 = 20,355 chars in each.722389c2was red on CI inubuntu-latest · Node 24 · DSH 0.2.0-rc.2(job 121478122422, run 40294797933, 08:49:07Z): oxlintVariable 'hiddenFrom' is declared but never used.— one error, 62 pre-existing warnings, exit 1 at the lint step, sotests-ok(its aggregate) failed with it. The room test asserted the toolbox only throughguard(); it now also asserts the mask throughallHidden(), which is what actually carries the 8 names out of the list. This tip is rebased onto8afe6c89(main moved and touched this page's doc), and the stylistic warning set is main's, untouched.Red without the source change
roomHiddenforced tofalse(the guard and the mask stay, only the room clause goes), same suite:Restored afterwards (
cmpclean against the pushed file). The chat-side test (#1237 test 3) stays green under that knife, which is the point: it guards the other half.Not run
llm-tapin front of a real model route, provider-reported prompt before/after, ±10%): no model route or bench on this box, so the delta is not measured; what is measured is the exact 20,355 characters of tool schema that leave the request, from real captured request headers, and the tokens come from the repository's own 3.92 chars/token fit (Research: measure and cut token spend per Bot turn (prompt overhead, history growth, tool output, fan-out) #796).generate: this box has nogit, andgenerateneeds it (git diff --no-index). The change touches only whole-file mirrors (patches/ds-bot/files/…, byte-identical tocore/) plusdocs/, so there is no hunk for it to rewrite;checkabove is the invariant it would establish.npm test(all ofcore/): the container refuses threads under the default concurrency (pthread_create: Resource temporarily unavailable), so the run reports file-level failures unrelated to this diff. The five suites above were run sequentially instead.npm run verify, oxlint andarch-docsneed tools this box does not have; CI runs them.Risk
SCHEDULE_TOOLSis a pinned list of DSH 0.2.0-rc.2's four names. A pin bump that renames one would silently leave it in the room (fail open, never a crash):restrict()refuses unknown names, and the test pins the four. The DSH-version test inturn-origin.test.jsis the place that already shouts when the pin moves.resolveAgent), before the prompt is built — the same refresh path that already keeps a newly mounted connector out of a friend's Bot.READS, secrets or review path is touched; the guard only ever refuses calls that the room rule names, and it refuses an extra tool, never allows one.Checkpoint
{ "pr": 1264, "claim": "Dev3", "issue": 1237, "summary": "A group room turn now carries neither the four composio tools nor the four schedule_* tools: 8 schemas, 20,355 characters (5,193 tokens at 3.92 chars/token) re-sent on every call of every room turn, and a room round uses neither. One clause in connector-access.js, so it rides the per-Agent masks FlyBot already applies at Agent creation, on every delivery, on a switch and on tools/change; the guard refuses a call with a reason that points at the Bot's own chat. Github and the built-ins stay in the room; a Bot's own chat, its parts and its sub-agents are unchanged.", "files": ["core/src/host/connector-access.js", "core/test/connector-access.test.js", "patches/ds-bot/files/src/host/connector-access.js", "patches/ds-bot/files/test/connector-access.test.js", "docs/subsystems/core-host.md", "changelog.d/1237-room-tool-mask.changed.md"], "checks": { "ran": "node scripts/ds-bot-patches.mjs check && node --test --test-concurrency=1 test/connector-access.test.js test/connectors.test.js test/hidden-tool-removal.test.js test/turn-origin.test.js test/delivery-board.test.js", "result": "green", "at": "5f1d90406656271e677ec4808e5c972faa518f8e" }, "next": "none — ready for review" }