Graph-native application state chronicle — zero-effort observability through PluresDB state diffs.
"The best log is one no developer had to write."
- Installation
- Quick Start
- The Problem
- The Insight
- How It Works
- The Graph Model
- Querying
- Integration with Unum
- Architecture
- Design Principles
- Development
- Roadmap
- Contributing
- Security
- Part of the Plures Ecosystem
- License
npm install @plures/chronosRequires Node.js ≥ 18 or a modern browser. ESM only.
import{createChronicle}from'@plures/chronos/chronicle';// One line. Every state change is now chronicled.constchronicle=createChronicle(db);// Inspect historyconsthistory=chronicle.history('todos.1');// → [{ id, timestamp, path, diff: { before: null, after: { text: 'buy milk' }}, ... }]chronicle.stop();Traditional application logging is broken:
- Manual — developers decide what to log, inevitably missing what matters
- Flat — lines in a file with no relationships or causality
- Lossy — you only see what someone thought to capture
- Noisy — 90% garbage until something breaks, then the 10% you need is missing
If your application state is already reactive (via plures/unum), then logging is just the chain of diffs recorded in time. Every state change is a graph node. Every causal relationship is an edge. No developer effort required.
Application (using unum)
│
│ state diffs (automatic via unum subscriptions)
▼
┌─────────────────────────────┐
│ Chronos │
│ • Captures unum state diffs│
│ • Builds causal chain │
│ • Timestamps each node │
│ • Zero configuration │
└─────────────┬───────────────┘
│
▼
┌─────────────────────────────┐
│ PluresDB │
│ • Graph storage │
│ • Time-series indexing │
│ • Vector search │
│ • Hyperswarm P2P sync │
└─────────────────────────────┘
Instead of flat log lines:
[INFO] 2026-03-18T15:30:00 User clicked submit
[INFO] 2026-03-18T15:30:01 Form validated
[INFO] 2026-03-18T15:30:01 API call POST /submit
[ERROR] 2026-03-18T15:30:02 Request failed: 500
Chronos captures a state graph:
UserAction(click_submit)
├─causes→ StateChange(form.validated = true)
│ ├─causes→ APICall(POST /submit)
│ │ └─causes→ StateChange(request.error = "500")
│ └─causes→ UIUpdate(spinner.visible = true)
└─context→ Session(user_id: 7, page: /checkout)
Replace grep "ERROR" | tail -100 with graph queries:
import{createChronicle}from'@plures/chronos/chronicle';import{traceCausalChain}from'@plures/chronos/trace';import{query}from'@plures/chronos/query';import{createTimeTravelDebugger}from'@plures/chronos/time-travel';constchronicle=createChronicle(db,{contextId: 'session:abc123'});// What caused this error state?constcauses=traceCausalChain(chronicle._nodes,chronicle._edges,errorNodeId,{direction: 'backward'});// What did this user action affect?consteffects=traceCausalChain(chronicle._nodes,chronicle._edges,actionNodeId,{direction: 'forward'});// Everything that changed in this sessionconstsessionNodes=query(chronicle._nodes,chronicle._edges,{contextId: 'session:abc123',});// All changes to 'todos' in the last hourconstrecentTodos=query(chronicle._nodes,[],{pathPrefix: 'todos.',startMs: Date.now()-60*60*1000,});// Step through history interactivelyconstdebugger_=createTimeTravelDebugger(chronicle._nodes);while(debugger_.stepForward()){console.log(debugger_.current().path,debugger_.snapshot());}See docs/api.md for the complete API reference.
Chronos hooks into unum's reactive subscriptions automatically:
import{createChronos}from'@plures/chronos';import{pluresData}from'@plures/unum';// One line. That's it. Every state change is now chronicled.constchronicle=createChronos(db);// Your app code doesn't change at allconsttodos=pluresData('todos');todos.add({text: 'Ship chronos',completed: false});// ^ This state change is automatically captured with full causal contextEach state change becomes a ChronicleNode:
interfaceChronicleNode{id: string;// Unique node ID (format: "chrono:{timestamp}-{counter}")timestamp: number;// Unix mspath: string;// PluresDB path that changed (e.g. "todos.abc123")diff: {before: unknown;// Previous value (null for creates)after: unknown;// New value (null for deletes)minimal?: DiffDescriptor|null;// Minimal structural diff (chronicle nodes only)};cause: string|null;// ID of the node that caused this changecontext: string|null;// Session/request/transaction context ID}| Edge | Meaning |
|---|---|
causes | This state change directly caused another |
context | Belongs to this session/request/transaction |
reverts | Undoes a previous state change |
concurrent | Happened simultaneously (same tick) |
Chronos uses async context (AsyncLocalStorage in Node.js) to automatically track causality:
- User action triggers a state change → root node
- That change triggers a subscriber → child node with
causesedge to root - Subscriber makes an API call → grandchild node
- API response triggers more state changes → great-grandchildren
All connected automatically. No manual instrumentation.
- Zero effort — If you use unum, you get chronos for free
- Complete — Every state change captured, not just what devs remember to log
- Structural — Graph, not text. Relationships, not lines.
- Queryable — Semantic search, graph traversal, time-range queries
- Distributed — PluresDB Hyperswarm sync means multi-node observability with just a topic key
- Minimal overhead — Append-only writes, async batching, configurable retention
- Node.js ≥ 18
- npm ≥ 9
git clone https://github.com/plures/chronos.git
cd chronos
npm install# Run the full test suite (watch mode)
npm test# Single run (CI mode)
npm run test:runAll tests are colocated under tests/ and use Vitest.
- Core: PluresDB subscription → causal graph chronicle
- Causal chain inference via AsyncLocalStorage
- Time-range queries (
@plures/chronos/query) - Graph traversal API — trace forward/backward (
@plures/chronos/trace) - Subgraph extraction by context/session
- Snapshot diff — minimal JSON diff engine (
@plures/chronos/diff) - Retention policies — TTL, quota, archival (
@plures/chronos/rules) - Time-travel debugger — step forward/backward through history
- Praxis rule engine integration
- Semantic search over state changes
- Dashboard UI (design-dojo component)
- PluresDB Hyperswarm sync for distributed observability
Contributions are welcome! Please read CONTRIBUTING.md before opening a pull request.
In short:
- Fork the repo and create a feature branch (
git checkout -b feat/my-feature) - Make your changes and add tests
- Run
npm run test:runand ensure all tests pass - Open a PR against
mainwith a conventional-commit title (e.g.feat: add X)
Please do not report security vulnerabilities through public GitHub issues. See our security policy for the responsible disclosure process.
| Package | Role |
|---|---|
| PluresDB | Graph database with vector search + Hyperswarm |
| Unum | Reactive state bindings (Svelte 5 ↔ PluresDB) |
| Chronos | State chronicle (zero-effort observability) |
| Pares Agens | AI agent framework |
| Design Dojo | UI component library |
| Plures Vault | Encrypted secret storage |
Dual-licensed under BSL-1.1 and MIT. You may choose either license at your option.