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
//! Request + response shapes for the consolidated `admin` domain tool.
//!
//! [`AdminParams`] is what crosses the wire: one flat parameter object with a required
//! [`AdminMode`] selecting the operation and every per-mode field an optional sibling. The
//! per-operation structs below (`CacheStatsParams`, `CacheClearParams`, …) stay as the helpers'
//! internal shapes, so the bodies keep taking exactly the arguments they always did.
//!
//! Split out of `types.rs` to keep that file under the 1000-line cap. The response structs mirror
//! the `store_gc` layer's `Serialize`-only structs and add the `JsonSchema` derive the MCP surface
//! requires.
use rmcp::schemars;
use serde::{Deserialize, Serialize};
use super::mode::AdminMode;
use crate::path::RelPath;
/// Wire parameters for the `admin` tool.
///
/// Only `mode` is required. Every other field belongs to one or two modes and is rejected — not
/// ignored — when passed to a mode that has no use for it (see [`super::mode::reject_unsupported`]);
/// a mode that needs one names the missing field.
#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
pub struct AdminParams {
/// Which operation to run.
pub mode: AdminMode,
/// `rescan` only. Repo-relative paths to re-index incrementally. Omit to walk the whole
/// working tree. Forward-slash, no leading `/`.
#[serde(default)]
pub paths: Option<Vec<String>>,
/// `rescan` only. Force a complete working-tree re-index even when `paths` is supplied
/// (full wins). Use when the index is stale or reports "no indexed files".
#[serde(default)]
pub full: Option<bool>,
/// `telemetry` only. Aggregation window: `today` (default), `1h`, `24h`, `all`.
#[serde(default)]
pub window: Option<String>,
/// `telemetry` only. Exact tool-name filter, e.g. `"code"` or `"admin:rescan"`.
#[serde(default)]
pub tool: Option<String>,
/// `cache_clear` only. Component to clear: `blobs|views|lance|git-cache|telemetry|all`, or
/// `views:<name>` for a single non-live view. Required by that mode.
#[serde(default)]
pub component: Option<String>,
/// `cache_clear` only. Required gate for the destructive components that back the live code
/// map. Ignored for the non-live caches.
#[serde(default)]
pub confirm: Option<bool>,
/// `compress` (prose input) and `checkpoint` (session transcript). Required by `checkpoint`;
/// `compress` takes exactly one of `text` or `path`.
#[serde(default)]
pub text: Option<String>,
/// `compress` only. Repo-relative path of an indexed source file to compress structurally.
/// Mutually exclusive with `text`.
#[serde(default)]
pub path: Option<RelPath>,
/// `compress` only. Reduction intensity: `off|light|moderate|aggressive|maximum`.
#[serde(default)]
pub level: Option<String>,
/// `compress` only. When true (the default), code blocks inside prose are left intact.
#[serde(default)]
pub preserve_code: Option<bool>,
/// `compress` only. Soft token budget hint, echoed back; it does not hard-cap the output.
#[serde(default)]
pub target_tokens: Option<u32>,
/// `delta` only. Previously seen content. Required by that mode.
#[serde(default)]
pub old: Option<String>,
/// `delta` only. Current content to diff against `old`. Required by that mode.
#[serde(default)]
pub new: Option<String>,
/// `waste` only. JSON-Lines tool-call log, one `{"tool","target","bytes"}` record per line.
/// Required by that mode.
#[serde(default)]
pub log: Option<String>,
}
impl AdminParams {
/// A call carrying only `mode`. Callers set the fields their mode uses and leave the rest
/// `None`: the helper rejects a field belonging to another mode, so populating them blindly
/// would fail the call.
pub fn new(mode: AdminMode) -> Self {
Self {
mode,
paths: None,
full: None,
window: None,
tool: None,
component: None,
confirm: None,
text: None,
path: None,
level: None,
preserve_code: None,
target_tokens: None,
old: None,
new: None,
log: None,
}
}
}
#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
pub struct CacheStatsParams {}
/// MCP-facing mirror of [`crate::store_gc::CacheStats`]. The store-layer struct
/// derives `Serialize` but not `JsonSchema`; this clone adds the schema derive the
/// MCP surface needs and converts via [`From`].
#[derive(Debug, Serialize, schemars::JsonSchema)]
pub(super) struct CacheStatsResponse {
/// Recursive byte size of `blobs/`.
pub blobs_bytes: u64,
/// Recursive byte size of `views/`.
pub views_bytes: u64,
/// Recursive byte size of `lance/`.
pub lance_bytes: u64,
/// Recursive byte size of `git-cache/`.
pub git_cache_bytes: u64,
/// Byte size of `telemetry.jsonl`.
pub telemetry_bytes: u64,
/// Recursive byte size of the git-history index (`git-history.fjall/`).
pub git_history_bytes: u64,
/// Recursive byte size of the entire `.basemind/` tree — the ground-truth footprint (matches
/// `du`). The component fields break this down; unattributed bytes are in `other_bytes`.
pub total_bytes: u64,
/// Bytes under `.basemind/` not attributed to a named component (`total_bytes` minus the
/// component sum): legacy `index.msgpack`, lock/id/config sidecars, `.gitignore`, etc.
pub other_bytes: u64,
/// Total blob files on disk (every suffix counts as one file).
pub blob_count: usize,
/// Blob files whose hex stem is referenced by no view — reclaimable by `cache_gc`. Meaningful
/// only when `blob_accounting_ok` is `true`.
pub orphan_blob_count: usize,
/// Whether orphan accounting ran. `false` = a view index was unreadable (stale schema /
/// corruption), so `orphan_blob_count` is `0` because it was skipped, not because there are
/// none; the size fields remain accurate. Re-scan to restore accounting.
pub blob_accounting_ok: bool,
/// Per-view indexed file count, `(view_name, file_count)`.
pub per_view_file_count: Vec<(String, usize)>,
/// Current resident set size (physical RAM) of the process serving this call, in bytes;
/// `null` when unreadable. Inside `basemind serve` this is the live MCP server process.
pub rss_bytes: Option<u64>,
/// Peak resident set size of the serving process over its lifetime, in bytes; `null` when
/// unreadable.
pub peak_rss_bytes: Option<u64>,
/// Outcome and health of the most recent destructive GC attempt, or `null` when none has run.
pub last_gc: Option<LastGcResponse>,
}
/// MCP-facing mirror of [`crate::store_gc_budget::GcState`] — the persisted outcome of the most
/// recent destructive GC sweep.
#[derive(Debug, Serialize, schemars::JsonSchema)]
pub(super) struct LastGcResponse {
/// When the most recent sweep finished; `0` if no sweep has completed.
pub at_epoch_secs: u64,
/// When the most recent attempt finished, including failed and starved attempts.
pub last_attempt_epoch_secs: u64,
/// Operational outcome of the most recent attempt.
pub status: crate::store_gc::GcStatus,
/// Actionable diagnosis for a degraded attempt.
pub detail: Option<String>,
/// Consecutive attempts that starved, failed, or ended over budget.
pub consecutive_degraded_cycles: u32,
/// Blob files inspected.
pub scanned: usize,
/// Orphan blob files removed.
pub removed: usize,
/// Blob bytes reclaimed.
pub bytes_freed: u64,
/// Orphaned workspace dirs reaped.
pub workspaces_reaped: usize,
/// Bytes reclaimed by reaping those dirs.
pub workspace_bytes_freed: u64,
/// Cold workspace dirs evicted by cache-budget enforcement.
pub workspaces_evicted: usize,
/// Bytes reclaimed by those evictions.
pub evicted_bytes_freed: u64,
/// Cache budget applied by the most recent completed sweep.
pub cache_budget_bytes: Option<u64>,
/// Measured cache footprint after the most recent completed budget pass.
pub cache_bytes_after: Option<u64>,
/// Hot workspaces evicted after cold candidates were exhausted.
pub hot_workspaces_evicted: usize,
/// Workspaces skipped because they were locked.
pub locked_workspaces_skipped: usize,
}
impl From<crate::store_gc_budget::GcState> for LastGcResponse {
fn from(s: crate::store_gc_budget::GcState) -> Self {
Self {
at_epoch_secs: s.at_epoch_secs,
last_attempt_epoch_secs: s.last_attempt_epoch_secs,
status: s.status,
detail: s.detail,
consecutive_degraded_cycles: s.consecutive_degraded_cycles,
scanned: s.scanned,
removed: s.removed,
bytes_freed: s.bytes_freed,
workspaces_reaped: s.workspaces_reaped,
workspace_bytes_freed: s.workspace_bytes_freed,
workspaces_evicted: s.workspaces_evicted,
evicted_bytes_freed: s.evicted_bytes_freed,
cache_budget_bytes: s.cache_budget_bytes,
cache_bytes_after: s.cache_bytes_after,
hot_workspaces_evicted: s.hot_workspaces_evicted,
locked_workspaces_skipped: s.locked_workspaces_skipped,
}
}
}
impl From<crate::store_gc::CacheStats> for CacheStatsResponse {
fn from(s: crate::store_gc::CacheStats) -> Self {
Self {
blobs_bytes: s.blobs_bytes,
views_bytes: s.views_bytes,
lance_bytes: s.lance_bytes,
git_cache_bytes: s.git_cache_bytes,
telemetry_bytes: s.telemetry_bytes,
git_history_bytes: s.git_history_bytes,
total_bytes: s.total_bytes,
other_bytes: s.other_bytes,
blob_count: s.blob_count,
orphan_blob_count: s.orphan_blob_count,
blob_accounting_ok: s.blob_accounting_ok,
per_view_file_count: s.per_view_file_count,
rss_bytes: s.rss_bytes,
peak_rss_bytes: s.peak_rss_bytes,
last_gc: s.last_gc.map(LastGcResponse::from),
}
}
}
#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
pub struct CacheGcParams {}
/// MCP-facing mirror of [`crate::store_gc::GcReport`] — see [`CacheStatsResponse`]
/// for why the store struct's `JsonSchema` is re-derived here.
#[derive(Debug, Serialize, schemars::JsonSchema)]
pub(super) struct CacheGcResponse {
/// Total blob files inspected.
pub scanned: usize,
/// Orphan blob files removed.
pub removed: usize,
/// Bytes reclaimed by the removals.
pub bytes_freed: u64,
}
impl From<crate::store_gc::GcReport> for CacheGcResponse {
fn from(r: crate::store_gc::GcReport) -> Self {
Self {
scanned: r.scanned,
removed: r.removed,
bytes_freed: r.bytes_freed,
}
}
}
#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
pub struct CacheClearParams {
/// Component to clear: `blobs|views|lance|git-cache|telemetry|all`.
pub component: String,
/// Required gate for the destructive components (`blobs`, `views`) that back
/// the live code map. Ignored for the non-live caches.
#[serde(default)]
pub confirm: bool,
}
#[derive(Debug, Serialize, schemars::JsonSchema)]
pub(super) struct CacheClearResponse {
/// Canonical token of the component that was targeted.
pub component: String,
/// True when the component was actually cleared.
pub cleared: bool,
}