sorrel/docs/core

Sorrel Core

The Rust engine underneath everything: a content-addressed object store, snapshots and changes, lanes for parallel work, a real three-way merge with first-class conflicts, and a permission spine that works without any server.

Content-addressed objects

Every piece of history is an immutable object identified by the BLAKE3 hash of its bytes. Objects live in a fanout store on disk (FileObjectStore) or in memory for tests. Writes are atomic (temp file, then rename) and reads verify the digest, so corruption is detected instead of propagated. Identical content always has the identical id — on every machine.

ObjectWhat it captures
BlobRaw file bytes.
TreeA directory: named entries pointing at blobs and subtrees, with modes and sizes.
SnapshotA whole working tree at a point in time, plus parent snapshots — history is a DAG of snapshots.
ChangeAn intent: author, message, base and resulting snapshot, and the path-level diff between them.
Lane / StackIsolated streams of work (for humans or agents) and ordered stacks of changes, with owner, visibility, and policy references.
Conflict / MergeResultThe outcome of a merge: stored, addressable, and inspectable — not an error string.

Snapshots and changes

materialize_snapshot walks a directory into trees and blobs and writes a snapshot. A stat cache (size + mtime) skips re-hashing unchanged files, which keeps status-style operations fast on large trees. snapshot_diff computes the path-level added/modified/deleted set between two snapshots, and create_change records that diff as a Change object and links the snapshots into history.

The merge model

Merging is engine work, not CLI string manipulation. Three layers cooperate:

  • Merge base: merge_base walks the snapshot DAG breadth-first from both sides and returns a deterministic best common ancestor (criss-cross ties resolve to the smallest id, so every machine agrees).
  • Line merge: merge3 is a dependency-free three-way text merge. Regions changed on one side take that side; identical edits merge; overlapping different edits produce structured conflict hunks and Git-style <<<<<<< markers. Non-UTF-8 content is treated as binary.
  • Snapshot merge: merge_snapshots merges two snapshots against their base path by path. A clean merge writes a merged snapshot whose parents are both sides. A conflicted merge writes no snapshot — it stores Conflict objects (content, add/add, modify/delete, binary) and a MergeResult that lists them.

Conflicts being real objects matters for agents: a conflicted merge is data another process can pick up, reason about, and resolve — possibly on a different machine, possibly by a different agent than the one that attempted the merge.

The policy spine

Core owns identity, permissions, grants, policy evaluation, redaction markers, SecretRef semantics, and audit records — headless, with no server required. Decentralized does not mean permissionless: policy and authority changes are signed PolicyChange objects evaluated against the previous effective policy. A principal cannot grant itself power it does not already have delegated authority for; peers, runners, the Hub, and vaults reject forged or locally edited permission state.

The contract is enforced across languages by protocol fixtures: a canonical conformance manifest lives in sorrel-protocol and is vendored into every consumer (engine, CLI, Hub, runners, vault) with drift-guard tests, so all evaluators provably return the same decisions.

Performance stance

  • BLAKE3 ids; stat-cache to avoid re-hashing unchanged files.
  • Engine-level exclusion (e.g. .sorrel/) instead of tree copies.
  • Dependency-free benches with coarse budgets that fail on order-of-magnitude regressions.