7 releases (4 breaking)
Uses new Rust 2024
| new 0.5.2 | Mar 7, 2026 |
|---|---|
| 0.5.1 | Mar 7, 2026 |
| 0.4.0 | Mar 7, 2026 |
| 0.3.0 | Mar 6, 2026 |
| 0.1.0 | Mar 5, 2026 |
#1067 in Development tools
3MB
68K
SLoC
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 |
Append-only JSONL sync with UUID dedup — no merge conflicts, no duplicates |
| 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
cargo install chronis
Quick Start
cn init # Create .chronis/ workspace
cn task create "Design auth module" -p p0 # Create a task
cn task create "Write tests" --type=bug # Create a bug
cn list # List all tasks (human-readable)
cn list --toon # List all tasks (agent-optimized)
cn ready --toon # Show unblocked open tasks
cn claim <id> --toon # Claim a task
cn done <id> --toon # Complete a task
cn archive --all-done # Clean up finished work
cn sync # Sync via git (pull/push)
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
cn list --toon
[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)
export CN_AGENT_ID=agent-1
cn ready --toon # → [id|type|title|pri|status|...]\nt-abc1|task|...
cn claim t-abc1 --toon # → ok:claimed:t-abc1
# ... agent does work ...
cn done t-abc1 --toon # → ok:done:t-abc1
cn list --toon # → verify state
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
cn task create "Title" \
-p p1 \ # Priority: p0 (critical), p1, p2 (default), p3
--type=epic \ # Type: task (default), epic, bug, feature
--parent=<id> \ # Parent task ID (for hierarchy under epics)
--blocked-by=<id1>,<id2> \ # Tasks that block this one
-d "Description text" # Description
Bulk Actions (Cascade)
Cascade operations apply to a task and all its children — useful for closing out an entire epic:
cn claim <epic-id> --cascade # Claim epic + all children
cn done <epic-id> --cascade # Mark epic + all children as done
cn done <epic-id> --cascade --reason="Sprint complete"
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.
cn archive t-abc1 t-abc2 # Archive specific tasks
cn archive --all-done # Archive all completed tasks
cn archive --done-before 30 # Archive tasks done 30+ days ago
cn unarchive t-abc1 # Restore an archived task
cn list # Excludes archived (default)
cn list --archived # Show only archived tasks
cn list --all # Show everything including archived
Dependencies
cn dep add <task-id> <blocker-id> # Add a blocker
cn dep remove <task-id> <blocker-id> # Remove a blocker
Git Sync
Sync chronis state across machines via git. Events are exported to an append-only JSONL file that git can merge naturally.
cn sync # Pull remote events, export local events, commit, push
How it works:
git pull --rebase— fetch remote changes- Import new events from
.chronis/sync/events.jsonlinto local Core - Append new local events to the JSONL file
git commit+git push
Deduplication is handled via UUID tracking — each event is written once by its creating machine and never duplicated.
Multi-machine workflow:
# Machine A # Machine B
cn task create "Auth" -p p0 cn task create "Docs" -p p2
cn sync cn sync # pulls A's tasks, pushes B's
cn sync # pulls B's task
Visualization
cn tui # Interactive ratatui TUI (j/k nav, Tab view, c/d/a actions)
cn serve [--port=3905] # Embedded web viewer (Axum + HTMX)
cn serve --open # Auto-open browser
Migration from beads_rust
One command migrates your existing beads issues to chronis — preserving IDs, status, priorities, dependencies, and parent-child relationships:
cn migrate-beads # Import issues from .beads/ directory
cn migrate-beads --beads-dir=/path/to/.beads
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 |
| Git sync | git commit && push |
Append-only JSONL with UUID dedup |
| 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 -> sync. 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:
export CN_AGENT_ID=agent-1
cn claim <id> # Records "agent-1" as the claimer
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
EmbeddedCore (allsource-core)
|
+--> WAL (CRC32, fsync) --> Parquet (Snappy)
+--> DashMap (in-memory reads)
+--> TaskProjection (event folding)
Data lives in .chronis/ at the project root:
.chronis/
wal/ # Write-ahead log segments
storage/ # Columnar event storage (Parquet)
sync/ # Git sync exchange (events.jsonl)
config.toml # Workspace config
.gitignore # Excludes binary data, tracks sync files
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
cargo fmt --check # Formatting
cargo clippy -- -D warnings # Lints (zero warnings)
cargo test # Integration tests
Acknowledgments
Chronis exists because beads_rust proved that agents can self-manage tasks through a simple CLI. The ready -> claim -> done -> sync workflow that beads_rust pioneered is the foundation chronis builds on. We're grateful to @Dicklesworthstone and the beads community for blazing that trail.
Dependencies
~57–82MB
~1.5M SLoC