Chronis
The agent-native task CLI. Event-sourced, TOON-optimized, built for AI agents and humans alike.
Inspired by beads_rust — the pioneering local-first issue tracker that proved agents can self-manage tasks via CLI. Chronis takes that idea further: every action is an immutable event, state is derived from projections, and output is optimized for LLM token consumption.
Binary: cn | Crate: chronis | Storage: .chronis/ | Powered by AllSource
Why Chronis?
We built hundreds of tasks with beads_rust — 62+ issues across 280+ agent iterations. It works. But we kept hitting walls:
| Problem with flat task trackers | Chronis solution |
|---|---|
| No event history — "how long was this blocked?" requires grepping git log | Event sourcing — every mutation is an immutable event with timestamps |
| Agent output burns tokens — JSON lists, ASCII tables, verbose prose | TOON output — --toon flag cuts token consumption ~50% on every command |
| No undo — agent closes wrong task, manual re-open is the only fix | Temporal replay — reconstruct state at any point from the event stream |
| Cascade operations require scripting — closing an epic means N separate commands | --cascade — claim or close an epic and all children in one command |
| Completed tasks clutter listings forever | Archiving — cn archive --all-done hides completed work, cn unarchive restores |
Git sync = git commit && push, merge conflicts stall agents |
HTTP sync with remote Core — no git conflicts, works with dirty worktrees |
| No approval gates or workflow orchestration | Event-sourced workflows — claim, complete, approve with first-write-wins semantics |
Credit where it's due: beads_rust by @Dicklesworthstone got the core workflow right — ready -> claim -> done -> sync. Chronis preserves that simplicity while adding the observability and durability that production agent systems need.
Install
Quick Start
TOON: Agent-Native Output
This is chronis's signature feature. Pass --toon to any command for TOON (Token-Oriented Object Notation) output — the same format used by AllSource's MCP server. No braces, no quotes, no wasted tokens.
The problem
Traditional CLI tools output for humans. When an LLM agent runs cn list, it gets:
╭──────────┬──────┬──────────────────────┬─────┬─────────────┬─────────┬─────────╮
│ ID │ Type │ Title │ Pri │ Status │ Claimed │ Blocked │
├──────────┼──────┼──────────────────────┼─────┼─────────────┼─────────┼─────────┤
│ t-abc1 │ task │ Design auth module │ p0 │ open │ - │ - │
│ t-abc2 │ bug │ Fix login redirect │ p1 │ in-progress │ agent-1 │ - │
│ t-abc3 │ task │ Write integration... │ p2 │ done │ agent-2 │ - │
╰──────────┴──────┴──────────────────────┴─────┴─────────────┴─────────┴─────────╯
That's ~180 tokens of box-drawing characters, padding, and repeated dashes that carry zero information for the model.
The solution
[id|type|title|pri|status|claimed|blocked_by|parent|archived]
t-abc1|task|Design auth module|p0|open||||false
t-abc2|bug|Fix login redirect|p1|in-progress|agent-1|||false
t-abc3|task|Write integration tests|p2|done|agent-2|||false
~60 tokens. Same information, ~50% fewer tokens. Every field is positional, pipe-delimited, with a header row. LLMs parse this natively.
TOON across all commands
| Command | Human output | TOON output |
|---|---|---|
cn list --toon |
ASCII table | [header]\nrow\nrow |
cn ready --toon |
ASCII table | [header]\nrow\nrow |
cn show <id> --toon |
Multi-line prose | key:value lines + child/timeline tables |
cn claim <id> --toon |
"Claimed task t-abc1 (agent: agent-1)" | ok:claimed:t-abc1 |
cn done <id> --toon |
"Completed task t-abc1" | ok:done:t-abc1 |
cn task create --toon |
"Created task t-abc1: Title" | created:task:t-abc1:Title |
cn archive --toon |
"Archived task t-abc1" | ok:archived:t-abc1 |
Agent workflow (4 commands, ~20 tokens of overhead)
# ... agent does work ...
Compare to JSON-based tools where the same loop burns ~200+ tokens on structural overhead alone.
Commands
Task Lifecycle
| Command | Alias | Description |
|---|---|---|
cn init |
Initialize a .chronis/ workspace in the current directory |
|
cn task create <title> |
Create a task (see flags below) | |
cn list [--status=open] |
cn ls |
List tasks, optionally filtered by status |
cn ready |
cn r |
Show tasks that are open and unblocked |
cn show <id> |
cn s |
Task details, children, and event timeline |
cn claim <id> |
cn c |
Claim a task (uses CN_AGENT_ID env var, defaults to "human") |
cn done <id> [--reason=...] |
cn d |
Mark a task as done |
cn approve <id> |
Approve a task | |
cn archive <ids...> |
Archive tasks (hide from default listings) | |
cn archive --all-done |
Archive all completed tasks | |
cn archive --done-before=30 |
Archive tasks done 30+ days ago | |
cn unarchive <ids...> |
Restore archived tasks |
All commands accept --toon for agent-optimized output.
Task Creation Flags
--type=epic \ # Type: task (default), epic, bug, feature
--parent=<id> \ # Parent task ID (for hierarchy under epics)
--blocked-by=<id1>,<id2> \ #
Bulk Actions (Cascade)
Cascade operations apply to a task and all its children — useful for closing out an entire epic:
Cascade walks the parent-child tree depth-first, processing children before the parent. Tasks already in the target state are skipped.
Archiving
Archive tasks to declutter your default listings. Archived tasks are hidden from cn list and cn ready but preserved in the event stream — nothing is deleted.
Dependencies
Sync
Chronis supports two modes of operation, configured in .chronis/config.toml:
| Mode | How it works | cn sync |
|---|---|---|
| Embedded (default) | Local event store per machine | Exchanges events with a remote Core over HTTP |
| Remote | All commands talk directly to a remote Core | No-op (already shared) |
Embedded mode with sync
Each machine has its own embedded Core for zero-latency reads. cn sync pushes local events to a remote Core and pulls new remote events — no git involved, works with dirty worktrees.
How it works:
- Pull — fetch new events from remote Core (skip events that originated here)
- Push — send local events to remote Core (skip events that came from remote)
- Deduplication is idempotent — UUID tracking prevents duplicates even with timestamp overlap, plus
source_idmetadata prevents echo loops
Multi-machine workflow:
# Machine A # Machine B
Remote mode
Point chronis at a shared Core instance. Writes go to the remote Core, reads use a local in-memory projection cache that's bootstrapped on startup by replaying all remote events. No sync step needed.
# .chronis/config.toml
= "remote"
[]
= "http://core.example.com:3900"
Configuration
# .chronis/config.toml
= "embedded" # "embedded" or "remote"
= "a1b2c3d4-..." # Auto-generated, identifies this machine
[]
= "http://core.example.com:3900" # Remote Core for sync target or primary backend
Visualization
Migration from beads_rust
One command migrates your existing beads issues to chronis — preserving IDs, status, priorities, dependencies, and parent-child relationships:
Both .beads/ and .chronis/ can coexist during transition. Nothing is deleted.
Chronis vs beads_rust
| Feature | beads_rust (bd) |
Chronis (cn) |
|---|---|---|
| Storage model | SQLite + JSONL snapshot | Event-sourced (WAL + Parquet) |
| History | Current state only | Full temporal event stream |
| Agent output | JSON / human text | TOON (~50% fewer tokens) |
| Cascade operations | Manual scripting | --cascade flag |
| Archiving | Not available | cn archive --all-done |
| Approval workflows | Not available | cn approve with event trail |
| Sync | git commit && push (fails with dirty worktree) |
HTTP sync via remote Core (works anytime) |
| Undo | Manual re-open | Replay from event stream |
| TUI | Separate binary (beads_viewer) | Built-in (cn tui) |
| Web UI | Separate project | Built-in (cn serve) |
| Queryable timeline | No | cn show <id> with full event history |
| Data durability | SQLite | CRC32 WAL + Snappy Parquet |
Both tools share the same core workflow: ready -> claim -> done. If you're coming from beads_rust, the transition is straightforward — and cn migrate-beads handles the data.
Workflow
cn ready --> cn claim <id> --> (do work) --> cn done <id> --> cn archive --all-done --> cn sync
For agent orchestration, set CN_AGENT_ID to identify which agent claims tasks:
Best Practices
Git Ignore for .chronis/
Sync is now HTTP-based (no git involvement), so the gitignore is simple — ignore everything except config:
# Chronis (all data is local or synced via HTTP — only config needs tracking)
.chronis/wal/
.chronis/storage/
.chronis/sync/
| Path | Contents | Git? | Why |
|---|---|---|---|
.chronis/wal/ |
WAL segments (binary, CRC32) | Ignore | Machine-local binary data |
.chronis/storage/ |
Parquet files (binary, Snappy) | Ignore | Machine-local, rebuilt from WAL |
.chronis/sync/ |
Sync state (timestamps, dedup) | Ignore | Machine-local sync cursor |
.chronis/config.toml |
Workspace config | Track | Shared settings (mode, remote URL) |
When to Use TOON
Use --toon when the consumer is an LLM or a script. Don't use it when a human is reading.
Use --toon |
Don't use --toon |
|---|---|
| AI agents consuming output (MCP, orchestration) | Interactive terminal use by humans |
| CI/CD pipelines parsing task state | Debugging with cn show |
Piping to cut, awk, or scripts |
Demos and screenshots |
| Large task lists (50+) where savings compound | Onboarding new team members |
Repeated polling (cn ready in a loop) |
TUI (cn tui) or web viewer (cn serve) |
For agent environments, set the flag once:
See docs/BEST_PRACTICES.md for the full guide.
Architecture
Chronis wraps AllSource's embedded library. Every mutation (create, claim, done, approve, dependency change) emits an event into the WAL. A TaskProjection folds these events into queryable task state stored in a DashMap (~12us reads).
cn CLI (clap)
|
v
TaskRepository trait
|
v
CoreBackend (Embedded | Remote)
| |
v v
EmbeddedCore HttpCoreClient
| (remote Core API)
+--> WAL (CRC32, fsync) --> Parquet (Snappy)
+--> DashMap (in-memory reads)
+--> TaskProjection (event folding)
In embedded mode, data lives in .chronis/ at the project root. In remote mode, all data lives on the remote Core instance.
.chronis/
wal/ # Write-ahead log segments (embedded mode only)
storage/ # Columnar event storage (embedded mode only)
sync/ # Sync state — timestamps and dedup cursors
config.toml # Workspace config (mode, instance_id, remote_url)
.gitignore # Excludes local data directories
Event Types
All state is derived from these events:
| Event | Emitted by |
|---|---|
task.created |
cn task create |
task.updated |
(future: cn task update) |
task.dependency.added |
cn dep add or --blocked-by flag |
task.dependency.removed |
cn dep remove |
workflow.claimed |
cn claim (first-write-wins) |
workflow.step.completed |
cn done |
workflow.approval.granted |
cn approve |
task.archived |
cn archive |
task.unarchived |
cn unarchive |
Quality Gates
Test suites
| Suite | Tests | What it covers |
|---|---|---|
task_lifecycle |
11 | Create, claim, done, approve, dependencies, epics, archiving, timeline |
config |
9 | Config parsing, defaults, TOML roundtrip, mode handling, file loading |
sync_state |
15 | CoreBackend (embedded + remote), projections, entity filtering, dedup (UUID + metadata), sync state roundtrip, workspace init |
Acknowledgments
Chronis exists because beads_rust proved that agents can self-manage tasks through a simple CLI. The ready -> claim -> done workflow that beads_rust pioneered is the foundation chronis builds on. We're grateful to @Dicklesworthstone and the beads community for blazing that trail.