Skip to content

What the plugin costs

The plugin buys ambient memory by spending latency and context. Until 2026-08-11 nobody had measured either, the README's "~10 ms, never on the agent's hot path" was carried forward as though it described the whole plugin, when it describes the capture hook alone.

Reproduce with:

uv run python scripts/measure_hook_overhead.py          # realistic, dedup on
uv run python scripts/measure_hook_overhead.py --fresh  # worst case, dedup defeated
uv run python scripts/measure_hook_overhead.py --json

Measured

Against a local server, real fleet memory, real agent identity, 7 samples per hook, per-session dedup active, re-measured 2026-10-09:

Hook Fires p50 p95 Tokens (first → then)
SessionStart per session 171 ms 176 ms 88 → 88
UserPromptSubmit · recall per prompt 219 ms 233 ms 69 → 41
PreToolUse · gotcha per tool call 176 ms 453 ms 72 → 0
Stop · capture per turn 18 ms 20 ms 0 → 0

For a session of 20 prompts and 60 tool calls: ≈ 15.4 s of added wall-clock and ≈ 2,000 tokens of context.

The dominant cost is PreToolUse, because it fires on every tool call. At 176 ms it is most of the added wall-clock, and it is not the hook anyone would have guessed. Capture stays cheap because its real work, including the token rollup posted to /usage, happens in a detached drainer whose own cost is not in this table.

Injections are budgeted

The first measurement, on 2026-08-11, came to 17.6 s and 4,200 tokens, and most of the tokens were waste: session start injected the whole last handoff (612 tokens in that run, about 1,250 in a later real session), and recall cut notes at a fixed character count, often mid-word. An injection is a nudge, so each one now has a hard budget. Recall stays silent unless a note is close to the prompt, and then injects the single closest one (measured). Session start is a pointer to the handoff of at most 600 characters, recall and file notes are at most 480, each line is one headline or one sentence cut at a sentence or word boundary, and a line that does not fit is dropped whole. tests/test_hook_injection_budget.py holds every hook to that.

Numbers to treat with care: this is localhost, so a remote instance is strictly worse, and tokens are counted as characters/4.

An inert plugin is not a free plugin

With bad credentials every hook returns nothing, logs nothing, and injects nothing. The same session then costs 6.6 s and 0 tokens, all of the latency, none of the benefit, and no signal anywhere that it is happening.

measure_hook_overhead.py exits non-zero when total injection is zero, for exactly this reason. It is also a hazard for any A/B measurement: a treatment arm in this state is a placebo, and would produce a clean, entirely meaningless null result. Any benchmark comparing Artel on/off must assert non-zero injection while it runs, not merely check that the plugin is installed.