Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,24 @@ relayburn — thin install-wrapper so `npm i -g relayburn` exposes the

### Rust crates (`crates/`)

Cargo workspace mirroring the TS dependency graph. Crate names are prefixed `relayburn-*` because `burn` is taken on crates.io; the binary keeps the `burn` invocation via `[[bin]] name = "burn"` in `relayburn-cli`.
The Rust tree is a **monolith**: only `relayburn-sdk` and `relayburn-cli` are published to crates.io. Crate names are prefixed `relayburn-*` because `burn` is taken on crates.io; the binary keeps the `burn` invocation via `[[bin]] name = "burn"` in `relayburn-cli`.

```
relayburn-reader — parsers + classifier (port of @relayburn/reader; #242)
relayburn-ledger — JSONL append + content sidecar + lock + sqlite archive (#243)
relayburn-analyze — pricing, cost derivation, hotspots, overhead (#244)
relayburn-ingest — session discovery + parse-and-append + pending stamps + watch loop (#245)
relayburn-sdk — PUBLISHED to crates.io; embedding API mirroring @relayburn/sdk (#246)
relayburn-cli — PUBLISHED to crates.io; produces the `burn` binary via [[bin]] rename (#248)
relayburn-sdk-node — napi-rs bindings; built in CI to produce @relayburn/sdk@2.0 .node artifacts (#247)
src/{reader,ledger,analyze,ingest}/ are internal modules
(formerly the four lower crates from #242–#245; absorbed
when keeping them as separate crates would have forced a
public crates.io contract for what is really a single
implementation)
relayburn-cli — PUBLISHED to crates.io; produces the `burn` binary via [[bin]] rename (#248).
Consumes the SDK as an external embedder would.
relayburn-sdk-node — napi-rs bindings; built in CI to produce @relayburn/sdk@2.0 .node artifacts (#247).
Not published to crates.io.
```

Build order matches TS: `relayburn-reader → -ledger → -analyze → -ingest → -sdk → -cli`, with `relayburn-sdk-node` depending on `relayburn-sdk`. Toolchain pinned in `rust-toolchain.toml` at the repo root.
Three crates total. Build order is `relayburn-sdk → relayburn-cli`, with `relayburn-sdk-node` also depending on `relayburn-sdk`. Toolchain pinned in `rust-toolchain.toml` at the repo root.

Inside `relayburn-sdk`, the absorbed modules build in dependency order `reader → ledger → analyze → ingest`; each module's source-of-truth comment still points back to the TS sibling under `packages/<name>`. The verb surface lives in `src/{query_verbs,export_verbs,ingest_verb}.rs` at the SDK crate root and pulls from those modules.

`@relayburn/sdk` owns the canonical query/compute surface — every new read verb should land there first as a pure function. `@relayburn/mcp` and `@relayburn/cli` are presenters: MCP wraps SDK calls in tool definitions, CLI wraps them in flag parsing + table rendering. Don't duplicate query logic across CLI/MCP — extract it into SDK.

Expand Down Expand Up @@ -161,8 +166,8 @@ The codex / opencode adapters share the pending-stamp + watch-loop shape; both a

## When in doubt

- **Architecture / API surface:** read `README.md` first, then the package's `src/index.ts` for exports.
- **Activity classifier rules:** the rule tables (`TEST_PATTERNS`, `EDIT_TOOLS`, `TOOL_ALIASES`, etc.) live at `packages/reader/src/classifier.ts`. They're the source of truth for what `burn compare` buckets each turn into. Adding a new harness = adding entries to `TOOL_ALIASES`; adding a new category = updating `ActivityCategory` in `packages/reader/src/types.ts` and adding its rule + a test.
- **Architecture / API surface:** read `README.md` first, then the package's `src/index.ts` for exports (TS) or `crates/relayburn-sdk/src/lib.rs` for the Rust public surface.
- **Activity classifier rules:** the rule tables (`TEST_PATTERNS`, `EDIT_TOOLS`, `TOOL_ALIASES`, etc.) live at `packages/reader/src/classifier.ts` (TS) and `crates/relayburn-sdk/src/reader/classifier.rs` (Rust). They're the source of truth for what `burn compare` buckets each turn into. Adding a new harness = adding entries to `TOOL_ALIASES`; adding a new category = updating `ActivityCategory` in `packages/reader/src/types.ts` (and the Rust mirror at `crates/relayburn-sdk/src/reader/types.rs`) and adding its rule + a test.
- **Derived state commands:** status, rebuild targets, and content pruning live under `burn state` in `packages/cli/src/commands/state.ts`. Keep maintenance verbs there rather than adding new top-level CLI dispatch.
- **Ledger schema:** `packages/reader/src/types.ts` (`TurnRecord`, `ContentRecord`) and `packages/ledger/src/schema.ts` (`LedgerLine`, `TurnLine`, `StampLine`). Bump `v` if the on-disk shape changes.
- **Ledger schema:** `packages/reader/src/types.ts` (`TurnRecord`, `ContentRecord`) and `packages/ledger/src/schema.ts` (`LedgerLine`, `TurnLine`, `StampLine`); Rust mirrors at `crates/relayburn-sdk/src/reader/types.rs` and `crates/relayburn-sdk/src/ledger/schema.rs`. Bump `v` if the on-disk shape changes.
- **Concurrency:** any read-modify-write on the ledger MUST hold `withLock('ledger', …)` from `@relayburn/ledger`. Append-only writes use the same lock to avoid racing reclassify-style rewrites.
65 changes: 9 additions & 56 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 0 additions & 24 deletions crates/relayburn-analyze/Cargo.toml

This file was deleted.

12 changes: 12 additions & 0 deletions crates/relayburn-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,15 @@ description = "The `burn` CLI — published to crates.io. Crate name is relaybur
[[bin]]
name = "burn"
path = "src/main.rs"

[dependencies]
# The CLI is the canonical external embedder of the SDK — every read
# verb the binary surfaces will wrap a `relayburn-sdk` call once #248
# fills in the CLI source (the binary is an `eprintln!` stub today).
#
# Version requirement is `0.0` (= `>=0.0.0, <0.1.0`) so the loose
# 0.0.x prerelease line satisfies both the local workspace path
# (currently 0.0.0) AND the published `relayburn-sdk` on crates.io
# (currently 0.0.1) without forcing a lockstep bump on every release.
# Tighten this once the SDK ships a stable 0.x line.
relayburn-sdk = { path = "../relayburn-sdk", version = "0.0" }
24 changes: 0 additions & 24 deletions crates/relayburn-ingest/Cargo.toml

This file was deleted.

20 changes: 0 additions & 20 deletions crates/relayburn-ledger/Cargo.toml

This file was deleted.

19 changes: 0 additions & 19 deletions crates/relayburn-reader/Cargo.toml

This file was deleted.

6 changes: 6 additions & 0 deletions crates/relayburn-sdk-node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ license.workspace = true
repository.workspace = true
description = "napi-rs bindings over relayburn-sdk — built in CI to produce the @relayburn/sdk npm artifacts. Not published to crates.io."
publish = false

[dependencies]
# napi-rs glue lands in #247; this crate's only direct dep is the SDK.
# Not published to crates.io, so the version pin is loose by design — it
# just keeps `cargo metadata` and the workspace resolver happy.
relayburn-sdk = { path = "../relayburn-sdk", version = "0.0" }
40 changes: 29 additions & 11 deletions crates/relayburn-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,38 @@ repository.workspace = true
description = "Embedding API for relayburn — published to crates.io as the supported Rust surface."

[dependencies]
# Path + version on every workspace dep so `cargo publish --dry-run` can
# verify the manifest without erroring on missing version requirements.
# The four lower crates publish in lockstep; the workspace version is the
# source of truth (`workspace = true`).
relayburn-reader = { path = "../relayburn-reader", version = "0.0.0" }
relayburn-ledger = { path = "../relayburn-ledger", version = "0.0.0" }
relayburn-analyze = { path = "../relayburn-analyze", version = "0.0.0" }
relayburn-ingest = { path = "../relayburn-ingest", version = "0.0.0" }
# Deps absorbed from the four former lower crates
# (`relayburn-{reader,ledger,analyze,ingest}`) when the monolith
# restructure collapsed them into modules under
# `src/{reader,ledger,analyze,ingest}/`.

# Common
serde = { workspace = true }
serde_json = { workspace = true }
serde_json = { workspace = true, features = ["preserve_order"] }
anyhow = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
tokio = { workspace = true, features = ["sync"] }

# reader: hashing + classifier rule tables
sha2 = "0.10"
hex = "0.4"
regex = "1"
phf = { version = "0.11", features = ["macros"] }

# ledger: SQLite events + content store
rusqlite = { workspace = true }

# analyze: order-preserving pricing table.
# `IndexMap` preserves JSON insertion order so duplicate model IDs in
# `models.dev.json` (e.g. `claude-sonnet-4-6` under both `anthropic` and
# `nano-gpt`) resolve deterministically — last entry in file order wins,
# matching the TS `Object.values` / `Object.entries` iteration semantics.
indexmap = { version = "2", features = ["serde"] }

[target.'cfg(unix)'.dependencies]
# ingest: pid-liveness probe in the pending-stamp resolver
libc = "0.2"

[dev-dependencies]
tempfile = "3"
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
tokio = { workspace = true, features = ["rt-multi-thread", "macros", "time", "sync", "test-util"] }
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
//! the 1e-9 USD precision contract that `overhead` and `hotspots` gate
//! against.

// The four absorbed module roots carry the lower crates whole, including
// items the SDK does not re-export (dead from the SDK perspective). Silence
// the never-used warnings rather than handpicking re-exports — the next
// agent absorbing more verbs will need them.
#![allow(dead_code, unused_imports)]

pub mod claude_md;
pub mod compare;
pub mod compare_archive;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ use std::path::{Path, PathBuf};

use indexmap::IndexMap;
use regex::Regex;
use relayburn_reader::TurnRecord;
use crate::reader::TurnRecord;
use serde::{Deserialize, Serialize};

use crate::cost::lookup_model_rate;
use crate::pricing::PricingTable;
use crate::analyze::cost::lookup_model_rate;
use crate::analyze::pricing::PricingTable;

const PER_MILLION: f64 = 1_000_000.0;
const CHARS_PER_TOKEN: u64 = 4;
Expand Down Expand Up @@ -511,8 +511,8 @@ fn to_posix_relative(file_path: &str, base_dir: Option<&Path>) -> String {
#[cfg(test)]
mod tests {
use super::*;
use crate::pricing::load_builtin_pricing;
use relayburn_reader::{SourceKind, ToolCall, TurnRecord, Usage};
use crate::analyze::pricing::load_builtin_pricing;
use crate::reader::{SourceKind, ToolCall, TurnRecord, Usage};
use std::fs;
use tempfile::TempDir;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@

use std::collections::BTreeMap;

use relayburn_ledger::EnrichedTurn;
use relayburn_reader::ActivityCategory;
use crate::ledger::EnrichedTurn;
use crate::reader::ActivityCategory;

use crate::cost::cost_for_turn;
use crate::pricing::PricingTable;
use crate::analyze::cost::cost_for_turn;
use crate::analyze::pricing::PricingTable;

/// Activity category label, or `"unclassified"` for turns the classifier
/// couldn't bucket. Mirrors the TS `ActivityCategory | "unclassified"`
Expand Down Expand Up @@ -311,9 +311,9 @@ fn activity_label(activity: Option<ActivityCategory>) -> String {
#[cfg(test)]
mod tests {
use super::*;
use crate::pricing::load_builtin_pricing;
use relayburn_ledger::EnrichedTurn;
use relayburn_reader::{ActivityCategory, SourceKind, ToolCall, TurnRecord, Usage};
use crate::analyze::pricing::load_builtin_pricing;
use crate::ledger::EnrichedTurn;
use crate::reader::{ActivityCategory, SourceKind, ToolCall, TurnRecord, Usage};
use std::collections::BTreeMap;

fn turn(
Expand Down
Loading
Loading