An observability stack for a single machine, built as separate programs that ship on their own.
| Component | Directory | What it is |
|---|---|---|
| signy | signy/ |
The storage and query engine. Takes collecty's batches on one route and answers a first-party HTTP API. Single machine, single writer, S3-compatible object storage as the source of truth, local disk as an evictable cache, and one declared memory budget divided into arenas. |
| collecty | collecty/ |
The collector. Takes OTLP/HTTP, writes it zstd-compressed to an append-only disk queue, and ships it to signy in batches. It never decodes a payload: a segment is one zstd stream over the exports as they arrived, and the file is the request body byte for byte. |
obsy is the product and the name of this repository. It is deliberately not a
binary, a crate, an env prefix, a metric family or an image — nothing at build
or run time is called obsy. Each component owns its name everywhere it is
visible: signy's knobs are SIGNY_*, its metric families are signy_*, its
routes are /signy/..., its headers are X-Signy-* and its image is
ghcr.io/namse/signy. Image tags do not derive from the repository name, so
signy's image and collecty's stay distinct.
app ──OTLP/HTTP──▶ collecty ──▶ signy
That is the only way in, and it is one way on purpose. signy has a single
write route, POST /signy/api/v1/collect; its OTLP push endpoints and its OTLP
gRPC listener were removed. An engine an application can push to directly has no
queue in front of it, so a refusal it gives — draining for a restart, flush
behind, disk low — is telemetry lost unless that application happens to hold it.
collecty's append-only disk queue is the thing that holds it, and a queue only
helps when nothing can go around it.
So the supported wire, for the whole stack, is OTLP over HTTP/1.1, protobuf,
uncompressed, on POST /v1/logs, /v1/traces and /v1/metrics at collecty.
Two things follow, and they are exporter configuration rather than
limitations to work around:
- No OTLP JSON. collecty refuses it with
415— it never decodes a payload, so it cannot re-encode one — and nothing downstream accepts it either. - No OTLP gRPC. Point the exporter at collecty over HTTP.
obsy/
├── .github/workflows/ one workflow per component
├── CLAUDE.md repository conventions
├── collecty/ the collector: Cargo.toml, src, docs, benches
└── signy/ the engine: Cargo.toml, src, docs, benches, compare, deploy
There is no Cargo workspace at the root. Each component is self-contained under
its own directory, carries its own Cargo.lock and rust-toolchain.toml, and
builds on its own.
That was decided rather than inherited. A root workspace would share one
Cargo.lock, which means a dependency bumped for collecty changes what signy
builds — the opposite of components that ship on their own. It would also move
the lock out of signy/, breaking an image build that copies it from there.
Neither cost buys anything: the two crates share no code.
cd signy
cargo testThe engine documents itself under signy/docs/:
ARCHITECTURE.md for what it is,
VISION.md for what it is for and what would falsify
the claim, QUERY_API.md for the API contract,
CONFIGURATION.md for every knob, and
DEPLOYMENT.md with
RUNBOOK.md for running it.
cd collecty
cargo testThe collector documents itself under collecty/docs/:
ARCHITECTURE.md for what it is and why each
decision was made, and
CONFIGURATION.md for every knob and what
raising it costs.
collecty carries no comments in its source. Every "why" that would have been one
is in docs/ARCHITECTURE.md instead.