#event-sourcing #task #productivity #cli-task #cli

bin+lib chronis

Event-sourced task CLI powered by the AllSource embedded database (all-source.xyz)

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

MIT license

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 Archivingcn 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:

  1. git pull --rebase — fetch remote changes
  2. Import new events from .chronis/sync/events.jsonl into local Core
  3. Append new local events to the JSONL file
  4. 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