trapline
One binary, one SQLite file and four clients of the same API: REST, web panel, CLI and MCP. Designed so an agent can read the error, patch the code, run the tests and close it for the next release without leaving your machine.
Sentry self-hosted asks for 16 GB of RAM. trapline does the job with 19.7 MB.
Error tracking, tracing, uptime, crons and a status page in an 18 MB binary. The official Sentry SDKs stay as they are: change the DSN and it works.
An error travels from the SDK to the agent without leaving the process
Click through the steps or let it play. Each step lights up the piece it talks about.
The minimal version that works: you do not install a new SDK, you change one variable.
From throw to closed issue
A made-up case with small numbers, to see what happens at each stage before talking about architecture.
TypeError: Cannot read properties of undefined (reading 'id') at checkout.js:42. The official SDK sends it as is.trapline issues bundle -project 1 -issue 17 returns a markdown document: exception, symbolicated stacktrace with context, breadcrumbs, aggregated tags, frequency over 24 h and 14 d, and the suspect commits with the reason for the suspicion.Five times the obvious solution was wrong
Each challenge follows the same pattern: what anyone would do first, why it does not work, what was done, and the number that proves it. The technical detail stays folded.
Every public figure of the product has a script that measures it and a gate that measures it again: footprint, binary size, events per second, compatibility with each SDK, parity across the four clients, and even the code blocks in the documentation, which run against a real server.
One filter: does this add a process, a dependency, or idle memory?
If the answer was yes, an alternative was sought. If there was none, it was measured and published with its gate.
Go deeper
DuckDB or ClickHouse are the right choice if you look only at the query profile. DuckDB is deferred, not discarded: the storage port allows adding a columnar adapter without touching the domain (ADR 01, ADR 04, ADR 10).
Measurement sets the priorities: with a realistic 14.6 KB event, decoding the JSON costs more than the whole SQLite transaction (421.9 µs vs 790.0 µs for four more statements than there were). The single writer turns concurrency into a queue: 8 clients give 667 to 1,090 ev/s against 705 to 747 with one.
curl | sh on amd64 and arm64. With CGO, cross-compilation requires C toolchains per architecture and the binary stops being static. It is slower in microbenchmarks and the design absorbs it: reads hit aggregates, not millions of rows.Go deeper
A C library in the process is also unmanaged memory surface in a service whose ingest endpoint is public. modernc.org/sqlite with CGO_ENABLED=0; the benchmark gate in CI is the judge. Adapters are tested against real SQLite, never against database mocks (ADR 09).
Go deeper
The scheduler offers each job together with the question of whether its subsystem has work, and only the ones that answer yes exist; the question is asked again on config change, without a restart (ADR 05, ADR 14). For ingest, where the SDK initiates the work, the protocol itself is used: a disabled category returns 429 with X-Sentry-Rate-Limits and the SDK stops sending it.
internal/engine imports nothing from the repository and will become a separate library under Apache-2.0, because it is code meant to be reused.Go deeper
That boundary, together with internal/domain (stdlib only) and five more pure packages, is verified by a test in internal/arch on every go test and by depguard in lint. The duplication is intentional: the test runs even if you do not have golangci-lint installed.