feat(audit): emit tool.spillover events when output is spilled (#500 polish)

The existing `tool.result` audit event records that a tool
finished but says nothing about spillover — operators tailing
`~/.deepseek/audit.log` couldn't see when 200 KiB of stdout
landed under `~/.deepseek/tool_outputs/`.

Adds a discrete `tool.spillover` event keyed off
`apply_spillover`'s return value, fired in both the sequential
and parallel tool paths so the log entry exists regardless of
how the tool was scheduled. Each event carries:

  {"event": "tool.spillover", "tool_id": "...",
   "tool_name": "exec_shell", "path": "/.../call-abc.txt"}

This is a pure observability addition. The model still receives
the same truncated head + footer; the UI still renders the
inline `full output: <path>` annotation; the spillover writer
contract is unchanged. No new tests — `apply_spillover` already
has unit-level coverage and the engine paths are exercised by
integration runs.
This commit is contained in:
Hunter Bown
2026-05-03 05:58:02 -05:00
parent 6b0a60883a
commit 0fa042dc99
2 changed files with 32 additions and 5 deletions
+6
View File
@@ -188,6 +188,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
ones. New `skills_directories()` and
`discover_in_workspace()` helpers in
`crates/tui/src/skills/mod.rs`.
- **`tool.spillover` audit event** (#500 polish) — emit a
discrete audit-log entry whenever `apply_spillover` writes a
spillover file, so operators tailing
`~/.deepseek/audit.log` can correlate large-output episodes
with disk-usage growth in `~/.deepseek/tool_outputs/`. Fires
in both the sequential and parallel tool paths.
- **RLM tool family** (#512) — `rlm` tool cards map to
`ToolFamily::Rlm` and render `rlm`, not `swarm`. Stale "swarm"
wording cleaned out of docs / comments / tests.
+26 -5
View File
@@ -1092,9 +1092,19 @@ impl Engine {
.await;
// #500: spill outsized output before fanout (mirror
// of the sequential path below).
if let Ok(tool_result) = result.as_mut() {
crate::tools::truncate::apply_spillover(tool_result, &plan.id);
// of the sequential path below). Emit a
// `tool.spillover` audit event so operators can
// correlate large-output episodes with disk usage.
if let Ok(tool_result) = result.as_mut()
&& let Some(path) =
crate::tools::truncate::apply_spillover(tool_result, &plan.id)
{
emit_tool_audit(json!({
"event": "tool.spillover",
"tool_id": plan.id.clone(),
"tool_name": plan.name.clone(),
"path": path.display().to_string(),
}));
}
let _ = tx_event
@@ -1374,8 +1384,19 @@ impl Engine {
// result fans out to the model context and the UI cell.
// Both consumers see the same truncated content + the
// `spillover_path` metadata pointing at the full file.
if let Ok(tool_result) = result.as_mut() {
crate::tools::truncate::apply_spillover(tool_result, &tool_id);
// Emit a discrete `tool.spillover` audit event so
// operators can correlate large-output episodes with
// disk-usage growth in `~/.deepseek/tool_outputs/`.
if let Ok(tool_result) = result.as_mut()
&& let Some(path) =
crate::tools::truncate::apply_spillover(tool_result, &tool_id)
{
emit_tool_audit(json!({
"event": "tool.spillover",
"tool_id": tool_id.clone(),
"tool_name": tool_name.clone(),
"path": path.display().to_string(),
}));
}
let _ = self