CLAUDE.md@packages/core · git:20261012.74bd46e · 2026-10-12 · sha256 8100b6a30435dd08
CLAUDE.md@packages/core git:20261012.74bd46eA
Immutable. This exact content never changes and is served at /api/v1/blob/8100b6a30435dd08.
# core
## Purpose
The vocabulary every other package shares: opaque ids, the write-once
durable rows, the closed register-namespace set, the transaction types, and
the two total codecs (JSON and msgpack) that guard every durability and
wire boundary. WP-A, and the root of the dependency DAG — `core` depends on
`gleam_stdlib` and nothing else, not even `gleam_erlang`.
## Key Types
- `core/ids.{EntryId, UsageId, OpId, SessionId}` — distinct opaque UUIDv7
wrappers, so an `EntryId` can never be passed where an `OpId` belongs.
`Generator` mints them purely from an injected `Clock` plus a seeded
SplitMix64 state; `mint_follower` reuses a parent's 48-bit time prefix
with a fresh random tail. `SessionId` (`protocol-change/008`) names a
whole session rather than a row in one — minted once at session
creation, persisted by `session`, and what the event bus keys by.
- `core/entry.Entry` — the four write-once row shapes (`MessageEntry`,
`CompactionEntry`, `BranchSummaryEntry`, `CustomEntry`), placement fields
and payload together. `UsageRow` is the ledger row.
- `core/message.Origin` distinguishes human attribution from peer-agent
attribution. `Origin(principal, name)` preserves the human identity admitted
under protocol 016. `PeerOrigin(session, strand)` records the host-bound peer
sender under protocol 048. `StrandOrigin(strand)` records a strand of the
same session that sent the message through the Agency (protocol 059; the
Agency writes it from the authenticated caller, in the same release that reads it). All
carry attribution only, without credentials or authority. Historical
unattributed turns remain `None`.
- `core/origin` owns validation, encoding, decoding and presentation of that
field. Human records keep their legacy untagged encoding; peer records use
`{kind: "peer", session, strand}` and strand records
`{kind: "strand", strand}`, validated by `validate_strand` with the bounds
of a peer's strand. A malformed present origin is corruption.
`stable_identity` of a strand is `strand:` plus its id, which no human
principal can equal. `project` adds an explicit human or peer-agent label at
the provider boundary while preserving stored content, and leaves a strand
message's content unchanged so the model sees only the Agency's framing.
`display_label` gives existing client views
an explicit peer label instead of treating a strand name as a human name.
- `core/todo_list.{Board, Phase, Task, Status}` — a strand's todo board,
kept here because three readers on two sides of a wire need one shape:
the `todo` tool writes it, the blackboard cell `agent/{strand}/todo`
stores it, and the TUI decodes it from the tool result's `details` to
draw the pinned panel. `decode` is total and answers a
`CorruptionReport`; `validate` is the writer's check (bounds, blank text,
unique phase names and unique task text across the board), and every
stored board has passed it because the tool is the cell's only writer.
`active`, `focus` and `count` answer the questions a renderer asks.
- `core/glance.Glance` — a strand's operator-facing title and one-line
activity summary, written by the daemon's glance loop under the reserved
`client/glance/{strand}` fact and read by the TUI's agent strip from each
capture. It names the operation it describes, so a reader drops it once
that operation is no longer current. `tokens` is the operation's
current context size from its newest usage row, a replacement value and
never a sum, so a reader may swap in a newer live figure. `decode` is
total; `clip` is the writers' one-line, byte-bounded, grapheme-safe cut
for model text.
- `core/register.{RegisterNs, RegisterValue}` — the closed namespace enum
and the thin tagged JSON wrapper storage persists. The rich payload types
each namespace forces live in `machine`; `core` understands only
`StrandLeaf`'s `Option(EntryId)`.
- `core/tx.{Write, Tx, SeqExpectation, CommitResult, CommitError}` — the
unit of durability: an ordered write list applied all-or-none under
optimistic seq expectations. `CommitError` carries four refusals, and
the fourth is newer than the frozen sketch: `LeaseLost(held_by:)` says
the committer is no longer the session's writer, which is the one
refusal no reload and no retry can get past
(`protocol-change/005`). `tx.describe_lease_loss` is the single place
it is worded for humans, so every layer that has to flatten one into
prose says the same thing.
- `core/json.JsonValue` — a pattern-matchable JSON ADT with a total parser,
defined here rather than borrowed so pure code can inspect the `Json`
named in the frozen contracts.
- `core/msgpack.MsgPackValue` — the canonical msgpack subset the
effect-plane framing protocol uses (ADR-003).
- `core/corruption.CorruptionReport` — the single error type every total
decoder returns.
- `core/clock.Clock` — the injected time capability; reading returns
`#(now_ms, successor_clock)`.
## Relationships
- **Depends on**: `gleam_stdlib` only. No `gleam_erlang`, no `gleam_otp`,
no `simplifile` — the purity of this package is structural, not a
convention.
- **Depended on by**: every other Gleam package (`storage`, `session`,
`machine`, `runtime`, `provider`, `broker`, `tools`, `conformance`).
- **FFI**: none. There is no `internal/ffi_*` module here and there must
not be one; `Clock` and `Generator` take injected functions instead, so
impurity belongs to whoever constructed them. Nor is there an
`@external` of any target, nor `gleam_erlang` or `gleam_otp` in
`gleam.toml` — by rule, not by coincidence.
Two properties rest on that, and one `@external` closes both: purity is
what makes the state space property-testable without spawning processes,
and the same discipline is what keeps this package compiling to the
**JavaScript target**. Lint R6 gates on it at error level and its census
must stay zero. Portable here means *decide but not act* — replay a
conversation tree, validate a transcript with the server's own total
decoders, run `next_action` over fetched state — and never the harness in
a browser: `gleam_otp` has no JavaScript target, Rule Zero is
kernel-enforced (in a browser the harness VM and the untrusted-code VM
would be the same VM), and the two-channel doctrine needs processes on
both sides. `docs/gleam-style.md` Part IV §5 argues it in full.
## Traffic
- **Actor messages**: none. `core` spawns nothing and imports no process
primitives.
- **Commits**: defines the `Write` vocabulary (`InsertEntry`,
`InsertUsage`, `SetRegister`, `DeleteRegister`) and `SeqExpectation`;
performs none.
- **Registers**: defines the closed set — `strand.leaf`, `strand.config`,
`strand.state`, `strand.last_result`, `op.meta`, `op.state`,
`op.tool_args`, `op.preparation`, `pending.entry`, `fact.name`,
`fact.label`, `fact.custom` — and their string forms via
`ns_to_string` / `parse_ns`.
- **Wire**: `core/codec` is the JSON durability codec for every durable
core type, using pi's exact field vocabulary (`parentId`, `cacheRead`,
`stopReason`, `retainedTail`) per ADR-001 so a format-4 import is a
mechanical decode-and-re-mint. Its public `encode_user_block` and
`decode_user_block` functions let ClientGateway carry that same block shape
without reimplementing it. `core/msgpack` is the effect-plane framing codec,
golden-pinned under `protocol/msgpack-fixtures/`.
## Invariants
- **Decoding is total everywhere.** Every decoder returns
`Result(t, CorruptionReport)`; wrong shapes and wrong types are reports,
never crashes. Partial decoding is a bug class, not a style choice
(spec §0.2).
- **Adversarial input is bounded at both codecs.** JSON and msgpack
containers nest at most `max_depth` (256) levels; `CorruptionReport`
truncates its context excerpt to `max_context_length` (256) graphemes;
msgpack rejects non-byte-aligned bit arrays and trailing bytes.
- **Duplicate keys are corruption, in both codecs.** Decoders disagree on
first- versus last-occurrence precedence, so a document or frame
carrying duplicates has no single meaning; rejecting is the only rule
with one interpretation.
- **JSON string runs preserve byte boundaries.** `json.clean_run` and
`json.escape_runs` advance four safe bytes together; each byte is checked
for quote, backslash and C0 controls. An exceptional chunk resumes the
single-byte step, and slices are still UTF-8 validated. Independent
codepoint-oracle and raw-input refusal tests cover every escape/control
byte at varied ASCII and multibyte offsets. String delimiters, adjacent
escapes, raw controls and end of input dispatch before ordinary-run slicing.
Nonempty runs validate their matched slice directly and consume a successful
delimiter in the same cursor update; errors retain their original offsets
and contexts. Reversed chunks and final concatenation retain the previous
output ownership. `scripts/json_decode_perf.sh` measures root-only parse
allocation and untraced timing, and its `equivalence` mode compares complete
results and corruption reports against a built baseline.
- **msgpack encoding is canonical** — smallest encoding that fits — so
equal values always produce identical bytes. The Go helper's strict
decoder and the golden fixtures both depend on this.
- **Non-finite floats are corruption**, in JSON and msgpack alike: the
BEAM cannot represent NaN or infinity. JSON ints are arbitrary
precision; msgpack ints outside `[-2^63, 2^64-1]` are encode errors.
- **A tool call's arguments are always a JSON object, even when the model
did not send one.** Argument text the streaming adapters could not parse
settles as `message.malformed_arguments`, an object under the reserved
`malformed_arguments_field` carrying the raw text and the parser's
complaint. That keeps the durable value replayable — the Anthropic and
Gemini dialects refuse a tool call whose input is not an object — and
gives `machine/planner` something to recognize and refuse in-band
(issue #189). The raw excerpt is bounded at the constructor, as
`corruption.report`'s context is, because it is unparsed model output
that a durable entry replays on every later turn.
- **Minting is pure and reproducible.** The same `Generator` value always
mints the same ids; the runtime seeds it from real entropy, tests from a
constant. Production wiring must supply seeds that never repeat within a
session lifetime or re-minted ids could collide with committed ones
(spec-gaps WP-E item 6).
- **Tool-result ids inherit the assistant id's time prefix**
(`mint_follower`), so a call-and-results group stays time-cohesive under
id order even across a midnight boundary (pi §1.2 rule 2).
- **Entries are write-once.** The types carry no update path; writing under
an existing id is corruption at the storage layer, not an update.
- **A present but malformed origin is corruption, never an anonymous
fallback.** `origin.decode_field` reads an absent field and an explicit
`null` as `Ok(None)`, but a present object that fails `validate` is an
`Error`. Degrading it to `None` instead would erase an author the
transcript does say it had, and would make a truncated or forged record
indistinguishable from a genuinely unattributed turn
(`protocol-change/016`).
## Deep Docs
- [docs/architecture/durability.md](../../docs/architecture/durability.md) —
the durability plane: stores, identity, transactions, expectations.
- [docs/adr/001-agent-message-fidelity.md](../../docs/adr/001-agent-message-fidelity.md)
— why the message family mirrors pi's shapes field for field.
- [docs/adr/003-msgpack.md](../../docs/adr/003-msgpack.md) — why the
msgpack codec is self-contained pure Gleam.
- [docs/spec-gaps.md](../../docs/spec-gaps.md) — "From WP-A (`core`)":
the mint signature, `RegisterValue` representation, numeric edges.
- [Root CLAUDE.md](../../CLAUDE.md) — repo ground rules and the doc graph.
## Code-mode utilities
`core/json_wire` converts the shared JSON and msgpack value types. It refuses
binary data, non-text or duplicate object keys, and excessive nesting on the
msgpack-to-JSON path. The satellite report helpers and host note/orchestration
routers share this pure conversion.
## Request accounting (protocol 081)
`message.Usage.evidence` is `usage_evidence.Evidence`: `NoProvider` (the
local addition identity) or `Remote(billing, tokens)`, where `tokens` is
`Unreported` or `Reported(coverage, cost)` and `cost` is `Unpriced` or
`Priced(coverage, basis)`. Only reported tokens have a cost and only a remote
observation has a billing arrangement, so an unknown observation cannot carry
a price and a missing remote witness cannot read as the identity. `Other`
covers both an unknown arrangement and observations from several. Configured
zero rates establish an estimate; an absent rate card does not. The stored
JSON keeps the earlier shape (`tokens`, `billing`, `cost`), which spells
the local identity as a billing and as a cost; `decode` accepts exactly the
combinations the types express, reads a stored `mixed` billing as `Other`, and
reads usage without evidence as unknown. Present malformed evidence is
corruption. `core/durable_usage_compat_test` pins the text earlier builds wrote.
`accounting.RequestAccounting` is `NoAttempts` or
`Attempts(total, last, count)`: the aggregate, the last attempt and an attempt
count in constant space. `append` adds one attempt, while `combine` folds
disjoint reports. `details(report, extra)` returns the ledger-details object
with the caller's fields plus `loom.request-accounting.v1`; `decode_row`
reads the row's aggregate and validates that bounded metadata. Historical
signed reconciliation rows stay readable; present request metadata rejects
negative consumption. `zero_usage` is the local identity and
`unknown_usage(billing)` is a remote attempt that reported nothing; source
packages call these rather than spelling the zero record again.