reasonix

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package reasonix implements an EVENT-LEVEL adapter for Reasonix (esengine/DeepSeek-Reasonix, MIT).

SURFACE. Reasonix ships a purpose-built usage ledger and this adapter reads that and nothing else:

${REASONIX_STATE_HOME:-${REASONIX_HOME:-~/.reasonix}}/stats/YYYY-MM-DD.jsonl

One append-only JSONL file per writer-local day, one record per completed request. The session transcripts under <root>/projects/**/sessions/ are deliberately NOT read: they carry prompts, reasoning and tool arguments, and they lack the exact counters these records carry. Reasonix's own registry entry says the same thing in the other direction — the transcript overlaps these records and is excluded.

CRITICAL: strictly observational. Files are opened O_RDONLY, never written, never locked, never rotated. Reasonix guards its own appends with <root>/stats/.append.lock; this adapter never touches it.

Records are EVENTS, not cumulative counters

This is the bug class that eats OTEL-shaped and session-state surfaces, so it is settled by evidence rather than by shape. Two proofs, both from this machine:

  1. Reasonix indexes its own stats files into a SQLite catalog whose record table is keyed PRIMARY KEY(file_path, byte_offset) with columns source/model_ref/provider/prompt/completion/reasoning/cache_hit/ cache_miss/total/requests/turns, and whose day rollup is built by SUMMING those columns. A cumulative re-export summed that way would be nonsense, so the vendor's own reader treats every line as a delta.
  2. Two consecutive live records on this machine read prompt=6207 then prompt=5718. A running total does not go down.

So the collector adds these up; it never diffs them and never takes a max.

Identity is the LINE's CONTENT; the byte offset is only a gate

The dedup key is a hash of the record's own bytes. It carries no path and no read position, so a re-read from zero — a lost checkpoint, a moved REASONIX_HOME, a symlinked root re-pointed — re-derives exactly the same keys and collapses in the store. A key minted from a byte offset would recount every line after any rewrite, and a position is not an identity.

Collision safety comes from the record itself: `ts` is RFC3339 with NANOSECOND precision and sits beside the full counter tuple, so two distinct requests cannot produce identical bytes. The only line that can collide with an already-stored line is a genuine duplicate write, which is exactly what deduplication is for. The trade is deliberate and in the conservative direction the ledger always takes: this can only ever UNDER-count, and an append-only ledger can never take an over-count back.

The byte offset survives as the incremental CHECKPOINT, where being wrong costs a re-read rather than a fact. Offsets are safe on THIS surface — daily files are append-only and every record is newline-terminated (the writer guards record boundaries) — unlike the re-read-in-full transcript surfaces, where a poll re-walks the whole tree and a position-derived key would recount on every pass. The checkpoint is trusted only on pure growth; a shrink or a same-size rewrite re-reads from zero.

Cost: the captured rows are unpriced

The captured Ollama rows carry cost_complete=false and incomplete_reason="no_price", with no amount. This adapter stamps no cost: CostMicroUSD remains nil, and the pricing ladder can value the counters. The exact v1.25.3 writer can also emit optional quoted cost amounts and currencies. They are not mapped here; unknown currencies must never be interpreted as USD. See testdata/live-1.25.3.provenance.md for the versioned writer evidence and the captured surface's limits.

Names and counters ONLY

The record shape is content-free by construction — no prompt, no result, no tool input, no cwd, not even a session id — and the decode is an ALLOW-LIST of the seventeen fields below, so a content field added upstream contributes nothing until this package is taught about it on purpose. Every string field read here is an enum or an identifier: a model ref, a surface name ("cli"), a status ("unavailable"), a reason ("no_price"). privacy.no_raw is satisfied by construction, not by a switch.

Index

Constants

View Source
const (
	StateHomeEnv = "REASONIX_STATE_HOME"
	HomeEnv      = "REASONIX_HOME"
)

StateHomeEnv and HomeEnv name the environment variables that move the Reasonix state root, and with it every stats file this adapter reads. StateHomeEnv is checked FIRST; HomeEnv is the fallback; ~/.reasonix is the default. They are exported because a caller that copies this process's discovery into another one — the systemd units internal/service writes — has to know that the environment, not the defaults, decided what gets collected.

Variables

This section is empty.

Functions

func New

func New() adapter.Adapter

New returns a Reasonix adapter.

func StatsDir

func StatsDir(cfg adapter.DiscoverConfig) string

StatsDir resolves the stats directory for one discovery config, applying the full three-step root resolution. Exported for the CLI's `sources`/`doctor` surfaces, which report where an adapter is looking without collecting.

Types

type Adapter

type Adapter struct{}

Adapter reads Reasonix daily usage ledgers. Read-only.

func (Adapter) Capabilities

func (Adapter) Capabilities() model.ToolCapability

Capabilities declares what this project can say about Reasonix.

Cost is COMPUTED: nothing here calls SetCost, so a row is valued from the public rate card or left unpriced. There is NO activity at all — this adapter references model.ActivityEvent nowhere.

func (Adapter) Collect

func (a Adapter) Collect(ctx context.Context, src adapter.Source) (adapter.Observation, error)

Collect reads one daily ledger in full.

func (Adapter) CollectIncremental

func (a Adapter) CollectIncremental(ctx context.Context, src adapter.Source, cp *model.SourceCheckpoint) (adapter.Observation, error)

CollectIncremental tail-reads what is new since cp: an unchanged size+mtime opens nothing; pure growth seeks to the stored offset; a shrink or a same-size rewrite re-reads from zero, which is free of consequence because the keys are content hashes and re-derive identically.

func (Adapter) Discover

func (a Adapter) Discover(ctx context.Context, cfg adapter.DiscoverConfig) ([]adapter.Source, error)

Discover lists the daily ledger files under the resolved stats directory. A missing root is not an error: Reasonix simply is not installed here.

The root is NOT symlink-resolved, and does not need to be: dedup keys are hashes of a record's own bytes, so a re-pointed root cannot mint a new identity for a line already stored.

func (Adapter) DisplayName

func (Adapter) DisplayName() string

DisplayName returns the human-friendly name.

func (Adapter) ID

func (Adapter) ID() string

ID returns the stable tool identifier.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL