Stelladocs
Telemetry & Usage Analytics

Files touched

Stella records every file it reads, creates, updates, or deletes — as a per-session CRUD ledger with reasons and line-delta counts, surfaced live in the TUI, in JSON output, and in the local store.

Every time Stella touches a file through one of its file tools, it records the touch in a per-session file-touch ledger: which file, what happened (Create / Read / Update / Delete), why, and how many lines it added and removed. The ledger is surfaced live in the TUI, included in --output-format json output on raw (--no-pipeline) runs, and persisted to the local store on every run — so you always have an auditable record of exactly what a run changed.

What gets recorded

Only Stella's dedicated file tools are attributable. Shell commands are opaque — Stella cannot see what a bash command did to the filesystem, which is one reason the file tools exist and the agent is steered toward them.

ToolCRUD eventLine counts
read_fileRalways 0 added, 0 removed
write_file (new path)Clines_added = the full new file's line count
write_file (existing path)Uthe line diff of pre- vs post-write content
edit_fileUthe line diff of the replaced region
delete_fileDlines_removed = the full pre-deletion line count

A failed operation records nothing — a blocked write, a missing file, or an out-of-workspace path never produces a ledger entry.

The reason field

Each of read_file, write_file, edit_file, and delete_file accepts an optional reason string. It is stored (trimmed of surrounding whitespace) in the audit log so the ledger reads like a changelog rather than a list of paths. When omitted, Stella fills in a per-operation default ("file updated (no reason given)", etc.).

// the model calls edit_file like this
{
  "path": "src/router.rs",
  "old_string": "…",
  "new_string": "…",
  "reason": "route /health to the new readiness probe"
}

The record shape

Touches aggregate by normalized, workspace-relative POSIX path, so equivalent spellings (src/main.rs, src/./main.rs, ./src/main.rs) collapse into one record. Each record keeps:

  • crud_events — the unique CRUD letters in first-seen order (e.g. ["R","U"]).
  • lines_added / lines_removed — the aggregate totals across every touch.
  • events — the complete, ordered audit log, one entry per touch, never deduplicated.
{
  "files_touched": [
    {
      "path": "src/router.rs",
      "crud_events": ["R", "U"],
      "lines_added": 18,
      "lines_removed": 4,
      "events": [
        { "event": "R", "reason": "inspect before editing", "lines_added": 0, "lines_removed": 0 },
        { "event": "U", "reason": "route /health to the readiness probe", "lines_added": 18, "lines_removed": 4 }
      ]
    }
  ]
}

crud_events is deduplicated (three reads show one R); events is not (three reads show three entries). The first is the compact "what kinds of change happened here"; the second is the full receipt.

How the line counts are defined

  • A line is a str::lines() item: "" is 0 lines, and a trailing newline does not add one.
  • Update deltas come from a minimal line diff (longest-common-subsequence) of the pre-write versus post-write content — a moved line counts as one removal plus one addition, not zero.
  • All counts are non-negative integers, and a record's totals always equal the sum of its events' deltas.

Where you see it

Live in the TUI

Interactive sessions render a Files touched panel that updates as the agent works, showing each path with its compact CRUD string (e.g. src/router.rs RU).

In JSON output

Which keys the final JSON object carries depends on which execution path ran:

  • Default runs go through the staged pipeline (triage, plan, execute, verify, judge). The pipeline's summary object has status, text, cost_usd, reason, task_class, verdict (passed / deterministic / summary), revisions, candidates_run, model, and events — there is no top-level files_touched.
  • Raw runs (--no-pipeline) fall back to the direct step-loop, whose summary has status, text, cost_usd, reason, model, and events — plus the full telemetry payload under files_touched.

So a script that asserts on exactly what a run changed must pass --no-pipeline:

stella --output-format json run --no-pipeline "add a --health flag and wire the readiness probe" \
  | jq '.files_touched[] | {path, crud_events, lines_added, lines_removed}'
{ "path": "src/router.rs", "crud_events": ["R","U"], "lines_added": 18, "lines_removed": 4 }
{ "path": "src/health.rs",  "crud_events": ["C"],     "lines_added": 22, "lines_removed": 0 }

On a default (pipeline) run, .files_touched is absent from the JSON — jq '.files_touched[]' errors out, and a CI gate built on it fails every run. Add --no-pipeline, or read the ledger back from the store: it is persisted on both paths.

Persisted to the local store

Every execution's file-touch ledger is written to Stella's local session store at <workspace>/.stella/store.db (one row per touched path, with the CRUD letters, line-delta totals, and the JSON audit log). Like the rest of Stella's telemetry, it stays entirely on your machine.

Common tasks

Fail CI when a run edits files outside a directory.

touched=$(stella --output-format json run --no-pipeline "$TASK" | jq -r '.files_touched[].path')
echo "$touched" | grep -qv '^src/' && { echo "run touched files outside src/"; exit 1; }

Get a one-line change summary of a run.

stella --output-format json run --no-pipeline "$TASK" \
  | jq -r '.files_touched[] | "\(.crud_events|join("")) \(.path) (+\(.lines_added) -\(.lines_removed))"'
# CU src/router.rs (+18 -4)
# C  src/health.rs (+22 -0)

Want a program to react to touches as they happen rather than reading the summary at the end? The extension hooks event bus emits file.read, file.created, file.updated, file.deleted, and files_touched.updated events with the same per-touch data — reads are first-class touches and emit a file.read fact too.

Stella is totally free and open source, dual-licensed MIT or Apache 2.0— your choice: MIT's three-paragraph simplicity, or Apache 2.0's explicit patent grant that enterprises prefer. Use it, fork it, embed it, ship it — no account, no phone-home, no strings. What that means →

On this page