Performance and tuning
What grith actually costs at startup and per syscall, and the handful of settings that change it.
grith's cost is per syscall, not per second. An idle agent pays nothing. A busy one pays in proportion to how many interesting syscalls it makes - and most of its syscalls are not interesting.
Startup
The measurements below were taken on 2026-03-10, on a development build from
before the 0.1.0 release, on Linux 6.17 x86_64: each tool spawned in a PTY,
timed from execvp() to its banner appearing, three runs averaged.
| Tool | Direct | Via grith exec | Overhead |
|---|---|---|---|
| Claude Code | 702ms | 901ms | +199ms (+28%) |
| Codex | 6,083ms | 6,275ms | +192ms (+3%) |
A synthetic sh -c 'printf ...' isolates grith's own startup with no tool
startup in the way: 3ms direct, about 122ms under grith exec. That ~119ms is
the floor for the current design - PTY allocation, ptrace attach, seccomp
install, first TUI frame and session registration - and it is largely kernel
work.
These are the only startup numbers grith has published, and they are old enough that you should treat them as an order of magnitude rather than a specification. Measure your own tool if it matters.
Per syscall, in steady state
Costs from the same baseline, per intercepted syscall once a session is running:
| Component | Cost |
|---|---|
| seccomp-BPF filter (kernel) | ~0.5µs |
| SIGCHLD wakeup and waitpid | 1-5µs |
| ptrace register read | ~1µs |
| Syscall classification (average) | 15-40µs |
| Session allowlist check | ~1µs |
| Full proxy evaluation, when it happens | 2-10ms |
grith treats 87 syscall identities as security-relevant; the seccomp filter returns ALLOW for everything else, so the vast majority of syscalls never stop the process at all. Of the ones that do, most are answered by the session allowlist or the noise filters without the proxy being invoked. Typical per-syscall overhead lands under 50µs.
grith proxy test reports the pipeline half of that directly, as an
Eval time: line. On the machine these docs were written on, a full 18-filter
pass took about 2ms for a file read and about 3ms for an HTTPS request, run to
run.
The settings that actually move the needle
Audit completeness. audit.completeness decides how much routine activity
is written to the audit database. spawns is the default. io adds compact
rows for routine file reads and writes, roughly +100 MB/day under typical
development workloads; all adds noise-path events too, roughly +500 MB/day.
Both cost write volume on the supervisor thread as well as disk.
[audit]
completeness = "spawns" # decisions | spawns | io | allNoise reduction. These reduce the number of syscalls that reach the pipeline at all, which is usually a bigger win than shaving latency off each one. Both switches are on by default; turn them off only when you are recording a session for forensics.
[supervisor.noise_reduction]
ignore_read_only = true
batch_rapid_reads = true
batch_window_ms = 50A workspace boundary (--workspace-only, or
[supervisor.trust] restrict_to_workspace) overrides ignore_read_only for
reads outside the workspace. That is a security decision, and it costs a little
throughput on purpose.
Log level. general.log_level defaults to info. debug and trace are
verbose enough to be felt on a busy session; use them while tuning filters, not
in normal running.
The behavioural filter. It records call types from the first syscall but
scores nothing until a session has made min_calls_for_baseline calls, so short
sessions never pay for it. Raise the threshold if it fires on legitimate work,
or set enabled = false.
[proxy.filters.behavioural]
enabled = true
min_calls_for_baseline = 200
mild_deviation_score = 1.0
significant_deviation_score = 3.0The cost you actually feel is prompts
A session that stops eight times to ask about the same scratch directory feels
far slower than one paying 40µs a syscall. Most of that work is already done for
you in v0.3.1: Chromium scratch directories, mktemp scratch paths, ephemeral
port binds, and read-only D-Bus probes no longer prompt, and approving a docker
command covers that command's family for the rest of the session. One measured
Chromium run answered 57 fewer prompts; one docker session answered 14 fewer.
Where a prompt still repeats across sessions, answer it with [l] rather than
[a]. That persists a learned rule for the active profile, so the same call is
allowed next time without asking. Sensitive targets are never persisted this way.
When grith feels slow
- Compare against the tool running unsupervised. Model latency dominates most agent workloads and grith cannot make it worse.
- Check the prompt count, not the syscall count. Sessions that feel slow are usually sessions that keep stopping.
- Check the audit database size. Deleting rows does not shrink the file - run
grith audit compactwith the daemon stopped to reclaim the space. - Drop
audit.completenessback tospawnsif something raised it.