Skip to content

dent8 v0.7.0

Choose a tag to compare

@github-actions github-actions released this 11 Jul 20:55

Install: cargo install dent8-cli --locked · pip install dent8 · npm install dent8 · prebuilt archives below. Docs: https://xyzzylabs.github.io/dent8/

Added

  • OS-keychain-backed identity keys (roadmap: identity productization; macOS in this
    release): keychain:<account> is accepted wherever a signing-key path is —
    DENT8_IDENTITY_KEY, keygen --out, grant-issue --issuer-key / --public-key,
    trust-add — naming a generic-password item under keychain service dent8 instead of a
    0600 file. identity agent-keygen --out keychain:<account> generates straight into the
    keychain (no file ever exists, the public key is printed and derivable from the private
    item, and an existing item is refused, same as files). This narrows the threat model's
    top residual: the key is encrypted at rest, locks with the session, and never lands in a
    dotfile a backup or homedir sync would sweep. The secret passes to /usr/bin/security
    over stdin, never argv. Windows Credential Manager / Linux secret-service are the
    documented follow-up, with a clear error meanwhile.
  • MCP resources/subscribe (roadmap: fact-change push): subscribe to a
    dent8://{kind}/{key}/{predicate} fact stream and the server pushes
    notifications/resources/updated when it gains events — immediately after a write
    through the same connection, and within a ~2s poll tick for writes from any other
    process sharing the store
    (another agent, the CLI, a daemon peer), so long-running
    agents stop re-polling explain. Subscribing to a not-yet-asserted stream is allowed and
    notifies on its first write. Works on both transports: the stdio server (notifier thread;
    responses and pushes interleave under one stdout lock) and the local daemon (per-connection
    notifier + single writer task). dent8 mcp proxy now pumps frames bidirectionally
    (previously strict request/response), so daemon clients receive pushes through it too.
    initialize advertises resources.subscribe: true only where a notifier is actually
    wired.
  • dent8 whatif — policy-counterfactual replay (the rank-2 novelty direction in
    research/novelty.md, now surfaced): re-fold the same immutable log
    under a swapped epistemic trust policy — --distrust <source> (repeatable),
    --authority-floor <level>, --confidence-floor <millis> — and see what would be believed,
    with a per-fact structural diff (appeared / disappeared / lifecycle / value / supersession /
    evidence changes) against the real fold. Read-only, deterministic, zero model invocations;
    freshness is deliberately not a policy knob. Also an MCP tool (whatif, same arguments,
    available to read-only daemon connections), with the diff mirrored in structuredContent and
    an advertised outputSchema. At least one policy knob is required — the identity policy is
    the plain fold (explain).
  • scripts/load-test.sh — the concurrency harness (roadmap: load testing and tuning):
    N parallel writers against one shared store, two phases — distinct facts (throughput +
    event-id uniqueness) and a deliberate same-fact supersession herd (every write eventually
    admitted, exactly one believed value, verify green over the full log). Defaults to a
    temporary SQLite store; point DENT8_STORE_URL at a throwaway Postgres (with PSQL
    overridable for dockerized databases) to run the Postgres leg. The harness found every
    concurrency fix below.
  • Cross-process write lease for sqlite:// and postgres:// stores
    (SqliteEventStore::acquire_write_lease, PostgresEventStore::acquire_write_lease):
    each write attempt now holds a backend lease across the whole decide+commit cycle — a
    BEGIN IMMEDIATE on a <db>-lease sidecar database for SQLite, a session advisory lock
    on a dedicated connection for Postgres. Optimistic retry alone is safe but livelocks
    under sustained same-fact contention — the decide step re-reads a growing log, so a slow
    writer's snapshot is perpetually stale by commit time; the lease turns the herd into a
    fair queue. A crashed holder releases automatically (SQLite file locks and Postgres
    sessions die with the process), waits are bounded, and a timeout is a retryable conflict
    (SQLSTATE 55P03 on Postgres). Before: 16 writers × 50 contended supersessions exhausted
    the retry budget; after: all 800 admitted on both backends, none exhausted. The SQLite
    sidecar holds no data and may be deleted when no writer is running; in-memory stores skip
    the lease (single-process by construction). See the new write-concurrency section in
    docs/storage.md.

Fixed

  • SQLITE_BUSY at connect/migrate is a retryable conflict now: concurrent
    first-connects race the schema DDL for the write lock; that BUSY was classified
    Unavailable (fatal) and crashed parallel writers on a fresh store. connect_backend
    also flattened errors to text, destroying the retryable class — it now returns the typed
    StoreError, and the write path routes Conflict into the retry loop.
  • A stale-snapshot commit is retried, not reported as terminal: when a concurrent
    writer lands between an op's decide snapshot and its durable append, the backend's
    re-arbitration correctly rejects the commit (e.g. cannot mutate terminal fact state Superseded) — but that rejection surfaced as a non-retryable failure and crashed the
    writer. It is now classified as a write conflict: the op re-decides from a fresh snapshot
    and the write lands, or is genuinely rejected against current state.
  • Write-conflict retry widened: 32 attempts (was 16), exponential backoff capped at
    256 ms (was 128 ms), still decorrelated per-process jitter.

Full changelog: https://github.com/xyzzylabs/dent8/blob/main/CHANGELOG.md