Known external tools
What happens when a user-installed output filter sits between your shell and the agent.
Tools like rtk and Headroom reduce token usage by rewriting command output before the model sees it. Soleur does not adopt, bundle, or endorse them — but users install them independently, and when something then breaks the question becomes "Soleur defect or filter ate the evidence?" This page is the answer key.
The detection marker
At every session boundary (startup|resume|clear|compact) the
ext-filter-detect.sh hook emits one systemMessage line per detected
tool — or SOLEUR_EXT_FILTER tool=none when nothing is found, so a dead hook is
distinguishable from no filter:
SOLEUR_EXT_FILTER tool=<t> version=<v> posture=<p> via=<evidence> measured_at=<date|-> [path=<settings>] ref=soleur.ai/known-external-tools
abbreviated forms: SOLEUR_EXT_FILTER tool=none ref=soleur.ai/known-external-tools
SOLEUR_EXT_FILTER tool=proxy posture=unknown via=proxy-env measured_at=- ref=soleur.ai/known-external-tools
The marker is a stable contract: field names, ordering, and the posture
enum are frozen — bug reports and triage muscle memory parse it literally. When several
detectors fire, the markers are newline-joined inside one
systemMessage object — never N separate objects (the harness's parse must stay
single-document). via= is comma-joined evidence: on-path (PATH hit),
settings-hook (wired into a settings file), proxy-env
(non-default endpoint); a corroborated tool reads
via=on-path,settings-hook.
systemMessage is operator-facing and costs zero model tokens; severity
escalation lives in this page, never in marker adjectives. Any path= field is
tilde-collapsed (~/.claude/settings.json) — it never carries your absolute home
directory. The marker is Claude-Code-only: Devin, Grok, and other harnesses
run no SessionStart hooks, so their sessions have no marker — and that absence does
not mean "no filter" (use the manual check in rung 1 below).
Kill switch: SOLEUR_DISABLE_EXT_FILTER_DETECT=1.
Posture tiers
| posture | meaning |
|---|---|
unmeasured | Soleur's gate-literal divergence harness has not run against this tool. Explicitly not an assurance of compatibility. |
literal-clean | The divergence harness measured zero lost gate-literals on the committed corpus. Asserts literal survival only — not behavioral or instruction-following compatibility. |
measured-incompatible | Gate-literal divergence measured (see Headroom below). Only emitted when the tool's identity is corroborated and its version is at or below the measured version. |
tool=proxy posture=unknown | ANTHROPIC_BASE_URL points at a non-default endpoint. A generic proxy (LiteLLM, Helicone, corporate egress) is never emitted under a vendor name — it is your infrastructure, and its filter posture is simply unknown to us. |
tool=none | No filter detected by any arm. (Not "no filter exists" — four arms can still miss an unusual install.) |
Per-tool posture
| tool | posture | basis |
|---|---|---|
| rtk | unmeasured |
Divergence harness has not yet run against rtk on this host
(plugins/soleur/scripts/ext-filter-divergence.sh reports
SKIPPED reason=rtk-absent here). The posture is a point-in-time
statement: when a measurement lands it is stamped
measured on rtk <version>, <date>, and after any rtk upgrade
re-run the harness — the claim does not follow the tool forward. |
| Headroom | measured-incompatible (≤ v0.37.0) |
Archived eval, 2026-09-07, headroom-ai==0.37.0, library-mode
compress() only: on a corpus of 2,252 tool_result payloads ≥2 kB,
244 of 581 modified payloads dropped at least one gate-relevant literal — including
2,367 file.ext:line citations and 8 SOLEUR_* markers — because the
compressor restructures file:line references into a header plus bare line
numbers Soleur's greps cannot read. Claim scope: gate-literal integrity under that
version and mode. |
| FCC aux optimizations | not applicable | free-claude-code's five local optimizations (quota probes, titles,
suggestions, filepaths, command prefixes) are proxy-layer intercepts: a plugin
cannot observe provider API traffic, so there is no surface for Soleur to judge them on.
They are unreachable from plugin hooks, not a finding about their quality. |
Methodology is symmetric: the same gate-literal divergence harness is the admission test for every tool — the current asymmetry between rtk and Headroom postures is evidence (one measured, one not yet), not favoritism.
rtk command grammar (canonical table)
Soleur's destructive-command guard unwraps rtk on every invocation shape —
a filtered subcommand (rtk git status runs git), an exec subcommand, or the
fallback raw exec of an unrecognized word (rtk rm -rf ~ runs rm). The
canonical set lives in plugins/soleur/scripts/lib/ext-filter-grammar.tsv
(version-pinned; verified 2026-10-13 against src/lib.rs on develop):
exec subcommands: run|err|test|proxy|bypass|summary string payloads: -c | --command | --shell → unparsed-wrapper ask, never silent allow globals: -v/-vv/-vvv/--verbose, --skip-env, -- (either position — clap global=true; -- is end-of-options)
Coverage caveat: rtk's rewrite hook only intercepts Bash tool
calls. Read, Grep, and Glob results are not filtered —
savings claims like "90%" are scoped to terminal-output tokens, not your bill.
Boundary-anchor coverage: since
#10248, boundary-anchored
gates — the gh pr merge review-evidence gate, git commit guards,
git stash, gh issue create, prod-write defers — interpolate the
shared wrapper-prefix fragment (plugins/soleur/scripts/lib/wrapper-boundary.sh),
so rtk gh pr merge, sudo rtk git commit,
X=1 rtk gh pr merge and … -- rtk gh pr merge reach them judged.
Three residual classes remain BY DESIGN: (1) wrappers and wrapper operands outside the
closed atom grammar are not unwrapped — that includes known launchers whose
operand words are unmodeled (sudo -u root …, env -C dir …,
nice -n N …, timeout N …): the prefix stops at the flag word and
the gated command is not reached; (2) opaque string payloads —
rtk run -c '…', rtk summary --shell '…' — join the hooks' existing
declared-lexical-limit class (alongside bash -c '…', subshells and eval): they
are not matchable without parsing a string as shell, which the gates deliberately never do;
and (3) sites whose boundary alternation itself differs (e.g. the with_lock … --
member) — prefix tolerance is shared, boundary shape is per-site, tracked at
#10315.
Three-rung diagnosis
- Check the marker. Scroll to the session-start system messages (or
grep the session log) for
SOLEUR_EXT_FILTER. Agents can re-derive it:bash <plugin>/hooks/ext-filter-detect.sh --self-test— the flag bypasses the harness-env gates.tool=nonemeans no filter detected — not "no filter exists" (an org-policy settings hook, or a PATH the hook process does not inherit, can still hide one). Claude Code sessions only — a Devin or other-harness session has no marker by design; there, checkcommand -v rtkand~/.claude/settings.jsonfor hook entries manually. - Reproduce with the filter off. RTK's disable is
mode = "disabled"in~/.config/rtk/config.toml(or remove its hook entry from settings), and the per-command escape is thertk run/rtk bypasspassthrough — there is noRTK_DISABLEDenv var (it does not exist; verified against RTK's docs 2026-10-13). Headroom's equivalent is removing its plugin/proxy wiring. - Compare verbatim vs filtered. Run the command both ways and diff the
evidence the gate needed.
plugins/soleur/scripts/ext-filter-divergence.shautomates exactly this over the committed corpus — verdictsLITERAL-CLEAN|UNMEASURED|DIVERGENT|INSTRUMENT-DEFECTplusSKIPPED reason=rtk-absentwhen rtk isn't installed (exit codes: 0 clean-or-skipped, 1 divergent, 3 usage/env, 4 instrument-defect).
Footguns
rtk gainandrtk sessionshow what rtk intercepted this session — the fastest way to prove a command was filtered.rtk discoverlists commands it saw but did not filter.- Telemetry defaults (verified against
RTK's README, 2026-10-13): rtk's
telemetry is anonymous and off by default — opt-IN via
rtk telemetry enable, not opt-out. Headroom's beacon is likewise off by default in current releases (their docs). Both are third-party claims about their own practices — re-check after upgrades. - rtk's hook emits its own permission decisions on some integrations — Soleur's guard judges the original command text either way (the two hook chains compose, not replace).
- Proxy-layer filters see your API key and full prompt stream.
Headroom's proxy mode — and any non-default
ANTHROPIC_BASE_URLendpoint — sits on the credential path, not just the output path: your key and every prompt and tool result transit that infrastructure before reaching Anthropic. Read the vendor's privacy terms before installing a proxy-mode filter or pointingANTHROPIC_BASE_URLat a third-party endpoint; atool=proxy posture=unknownmarker is Soleur telling you the transit exists, not judging it. - Reporting bugs: when filing, paste the full marker line into the bug
report's marker field and answer the filter dropdown — triage applies the
external-envlabel when it isn'tnone(don't add it yourself).