Architecture
Exponential is an event-sourced issue tracker that stores its data in a git-tracked append-only log. A single Go binary ships the CLI, a web UI, an HTTP API, and an MCP server.
Event sourcing
All mutations are recorded as events. There is no mutable state on disk:
{"type": "CREATE", "payload": {"title": "...", "status": "BACKLOG", "labels": ["feature"]}}
{"type": "UPDATE", "payload": {"status": "DOING", "assignee": "Alice"}}
{"type": "COMMENT", "payload": {"id": "cmt-...", "text": "markdown body"}}
{"type": "DELETE", "payload": {"reason": "duplicate"}}
{"type": "MERGE", "payload": {"branch": "myapp-a1b2/login", "strategy": "squash"}}
Events are stored as one JSON object per line (JSONL) in .xpo/issues.db, appended with O_APPEND. Concurrent appends are safe on POSIX filesystems; git merge conflicts stay limited to the tail of the file.
Projection
Current state is derived by replaying the event log. Each event type has a deterministic effect — CREATE initializes, UPDATE patches, DELETE soft-deletes, COMMENT appends, MERGE records metadata. Post-processing applies config-driven rules: parent status inference, cycle rollover, estimate aggregation.
Transport abstraction
All operations go through a Transport interface:
- LocalTransport reads and writes
issues.dbdirectly. Resolves IDs flexibly — exact, prefix, or substring match. - RemoteTransport proxies to a server via REST with JWT authentication.
- Client wraps a Transport and adds git-specific operations (branching, merging, review).
This is what makes local and distributed mode use the same code paths.
Storage
| Path | Git-tracked | Purpose |
|---|---|---|
.xpo/issues.db | Yes | Active event log (JSONL) |
.xpo/archive.db | Yes | Archived events |
.xpo/config.yaml | Yes | Project configuration |
.xpo/authorized_keys | Yes | SSH public keys for server auth |
.xpo/server.key | No | JWT signing key (auto-generated) |
.xpo/issues.snapshot.json | No | Projection cache |
The snapshot cache stores projected state up to a known event offset, enabling fast startup without replaying the full log.
Web UI internals
The server maintains a read-through projection cache. The web UI persists mutations automatically. AppendEventCollapsed() merges consecutive edits on the same issue — two UPDATEs become one — and prunes no-ops.
Real-time updates use Server-Sent Events (SSE) with a 30-second keepalive.
Data model versioning
| Version | Change |
|---|---|
| v1 | Initial schema (typed issues: TASK, BUG, EPIC) |
| v2 | Unified Issue type with labels, assignee, estimation |
| v3 | Globally consistent sort order keys |
xpo migrate appends corrective events — existing events are never modified.