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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
//! 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 ;
use ;
/// 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...".
///
/// # As data
///
/// A roster serializes as how it was built — `{"hide": [...]}` with the names
/// added to basis's own hidden set, or `{"only": [...]}` — so a record that
/// outlives the binary which wrote it (a durable task's run options, say)
/// follows the reading binary's default hidden set rather than freezing the
/// writer's.
;
/// [`ToolRoster`] as it is written: the constructor that made it, and the
/// names it was given.
/// Which of a workspace's skills the model is told about and may load, for
/// [`RunProfile::with_skill_roster`](super::RunProfile::with_skill_roster).
///
/// The counterpart of [`ToolRoster`], and thinner: basis has no default
/// opinion about skills, so the default is every skill the workspace
/// discovered. [`only`](Self::only) maps straight onto mentra's
/// `SkillProfile::only`: a skill it leaves out is absent from the list in the
/// system prompt and refused by `load_skill` as an unknown one is, and an
/// empty list also takes `load_skill` off the model's tools.
///
/// It narrows what the *model* reaches. A host running a skill itself — a
/// slash command — is not the model, and is not narrowed.
///
/// Serializes as `"all"` or `{"only": [...]}`.
/// 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: = ;