1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
//! Which tools the model is offered — the workspace's own knob over what used
//! to be two constants and no host input (decision D3).
//!
//! [`ToolRoster::default`] is byte-identical to what every workspace has
//! always done: `spawn`'s replaced doors and the intrinsics basis has never
//! surfaced, hidden, and everything else mentra registers, offered. The two
//! constructors map straight onto mentra's own `ToolProfile`:
//! [`hide`](ToolRoster::hide) extends the denylist, [`only`](ToolRoster::only)
//! replaces it with an allow-list. Neither touches what is *registered* on the
//! runtime — hidden or un-allowed is a roster fact, never a capability fact
//! (see [`crate::workspace::builder`]'s module docs for why that distinction
//! matters to `spawn`).
//!
//! # What a roster is not
//!
//! A roster is the *base* `ToolProfile` an opened workspace carries in its
//! `AgentConfig`, and two things sit outside it entirely:
//!
//! - **A sibling workspace's tools.** On a shared runtime one registry serves
//! every open repository, and a roster says nothing about whose tool a name
//! belongs to. It does not have to: a workspace's bridged `mcp__*` tools and
//! its declared tools are registered for that workspace's own
//! [`ToolAudience`](mentra::tool::ToolAudience), and mentra resolves a name
//! held only by a foreign audience as hidden rather than visible. So a
//! sibling's tool is out of reach whether or not a roster mentions it, and
//! whether the model was offered the name or guessed it. A roster is about
//! what *this* workspace offers of what it can reach. The two `mcp__*` cases
//! an audience cannot express — a second live open of the *same* directory,
//! and a host global under an `mcp__`-shaped name — are hidden by name at
//! every mint instead, and that hiding composes with whatever roster is set
//! here rather than being part of it
//! ([`Workspace::minted_agent`](super::Workspace)).
//! - **The rendered prompt.** Whatever a workspace's `AGENTS.md`, `CLAUDE.md`
//! or memory files ([`crate::memory`]) put in the system prompt is a
//! property of [`WorkspaceContext`](crate::context::WorkspaceContext) and
//! [`crate::memory::index_block`], assembled entirely independently of the
//! roster and rendered by `agent_config` regardless of what it decided. A
//! roster that hides every file tool still ships a prompt — and a memory
//! index — that may tell the model to go read one; the prompt does not know,
//! and does not ask, what the roster allows.
use BTreeSet;
use ToolProfile;
/// Which tools the model is offered, for
/// [`WorkspaceBuilder::with_tool_roster`](super::WorkspaceBuilder::with_tool_roster).
///
/// A thin wrapper over mentra's `ToolProfile` rather than a re-export of it:
/// [`ToolRoster::default`] is the one construction this crate has an opinion
/// about (today's exact hidden set), and wrapping it is what lets that default
/// live beside the constant it is built from instead of at every call site
/// that wants "basis's usual roster, plus...".
;
/// Every name basis takes off the model's roster by default: what `spawn`
/// replaced, and what basis has never surfaced.
///
/// Two constants rather than one list because the two carry different
/// arguments and a reader deserves to know which applies to a given name.
/// The tools `spawn` replaces, by the names mentra registers them under.
const REPLACED_TOOLS: = ;
/// What mentra registers that basis has never deliberately offered.
///
/// Registration is mentra's default posture — `register_tools` walks every
/// intrinsic variant it has — so a name reaching the model here is the absence
/// of a decision rather than one. Each of these fails a different way, and none
/// of the failures is visible to the person running the agent:
///
/// - **`team_spawn` and its six siblings are delegation by another name.** A
/// second door for *hand work to something else, read back a summary* is
/// exactly what ADR-0016 removed `task` for: two names arriving at one
/// approval gate, and two namespaces of remembered rules, for a question an
/// operator asks once. Nothing in basis mints a team, reads a teammate inbox,
/// or renders a `team_request`, so the door does not even lead where its
/// description says. `docs/REDESIGN.md` has recorded these as awaiting a
/// concrete use case since Phase D; reachable-by-accident is not the
/// deliberate surfacing that row is waiting for.
/// - **`idle` is that surface's exit.** Its whole effect is
/// `Agent::request_idle`, which mentra's orchestrator reads as
/// `should_end_turn` — a yield *back to the teammate loop* basis never
/// starts. On a basis run the model calling it ends its own turn mid-task
/// and the caller reads a short answer with no error in it.
/// - **`task_create` and the other four write a board nothing reads.** basis
/// surfaces no task board — not on the event stream, not over ACP, not in
/// the CLI — so a model that files, claims and updates work items gets
/// plausible success back from every call and nothing observable happens.
/// Confident bookkeeping into a void is worse than no bookkeeping, because
/// it reads to the model as coordination.
/// - **`check_background` reports on a tool that is hidden.** The only thing it
/// can report on is `background_run`, which left the roster with ADR-0016's
/// two other doors, so it can answer nothing but "no such task".
/// - **`memory_pin`, `memory_forget` and `memory_search` reach a store basis
/// has decided against (D2, wave 1).** basis's memory is a file convention
/// (`crate::memory`); mentra's engine — recall injection included, switched
/// off in `agent_config` beside this list — is not it. A model pinning facts
/// into a store nothing surfaces is the task-board failure again: plausible
/// success, and nothing the person running the agent can see.
///
/// Deliberately still offered, and each for a reason: `load_skill`, because
/// on-demand skills are basis's own convention and that tool is how a skill is
/// loaded; and `compact`, because a model that can see its context filling
/// should be able to act on it (that the *user* has no matching control is a
/// separate gap, and hiding this would not close it).
const UNSURFACED_TOOLS: = ;