npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bevia/sdk

v0.5.0

Published

Bevia SDK — a thin client for a running Bevia Local engine, over its public doors only: submit evidence, read the governed current World (Matter, standing, condition history, relations, receipts by exact identity), subscribe to what happened, propose, and

Readme

@bevia/sdk

Bevia turns perception into governed operational understanding.

This SDK talks to a running Bevia Local engine — the governed intelligence runtime the owner runs. Send evidence in, read the governed current World out (Matter, standing, condition history, relations, receipts by exact identity), subscribe to what happened, propose, and report what you did. Zero dependencies. Node 18+, Deno, Bun, browsers.

your program ──intake.submit──▶ ┌──────────────────┐ ──world.* / attention──▶ your program
                                │   Bevia Local    │ ──events.on───────────▶ your program
your program ──propose────────▶ │  governed World  │
your program ──proposals.report │   (an HTTP URL)  │
                                └──────────────────┘

The engine never calls you; you connect to it. Nothing here reaches the engine's database; every call is a door the owner granted a capability for, and a refusal names the capability you lack.

What this SDK is — and is not

It is a thin client over the public doors of a running Bevia Local engine: the HTTP protocol every Bevia client uses (v1.3, shipped as PROTOCOL.md in every developer workspace and documented at bevia.co/build). MIT, public, zero dependencies, so anyone can read every line of what it sends.

It is not a way into the engine. The SDK carries:

  • no engine code — nothing that admits, forms or publishes meaning;
  • no database driver — it cannot open the engine's data, and no door returns a database handle;
  • no owner-only route — revoking sensors, resolving proposals, clearing or importing a World, materializing the vault are the owner's, through the app, and are not in this package;
  • no credentials — the model the owner configured is invoked by the engine on ask; your program never sees the owner's key.

Portability runs outward: a program can read the World, its publications and (with the owner's grant) export the whole substrate. Bringing a World in is the owner's act in the app, never an SDK call.

The package's publish step audits every shipped file — code, declarations, this README, the license and the manifest — for engine names, internal paths and anything but the doors the protocol publishes, and fails closed. If you can do something with this SDK that the protocol document does not describe, that is a bug — report it.

Install

npm install @bevia/sdk

That is the whole install. The package is on the public npm registry; nothing else — no checkout, no tarball, no private registry — is needed or offered. It speaks protocol v1.3.

No engine to hand? The Build on Bevia quick start at bevia.co/build shows how to run a sandbox engine with a small published World and a token that already works.

If you are a coding agent

Read AGENTS.md beside this README in the SDK's source (also at bevia.co/build/AGENTS.md): the rules a program on Bevia follows, what a caller may never declare, and how to validate what you built. The step-by-step build loop is the bevia-app-builder skill in the same source (skills/bevia-app-builder/SKILL.md, also at bevia.co/build/skills/bevia-app-builder/SKILL.md). bevia.co/llms.txt points at all three.

Connect

Open Bevia's menu-bar icon → Engine Controls… → Create a developer workspace, once. It writes ~/Bevia/Developer/bevia-workspace.json with the engine's port and a working token. Then:

import { connectLocal } from "@bevia/sdk";

const bevia = await connectLocal();          // reads the workspace file
console.log(await bevia.whoami());           // { name, kind, capabilities, pending }

Or pair your own program with a code from the app (Settings → Sources) and ask for what it needs:

import { BeviaClient } from "@bevia/sdk";

const res = await fetch("http://127.0.0.1:PORT/pair", {
  method: "POST",
  body: JSON.stringify({ code: "ABCD2345", name: "Thesis Guard", kind: "extension",
                         capabilities: ["capture", "read", "subscribe", "propose"] }),
});
const { token } = await res.json();          // store it; it is shown once
const bevia = new BeviaClient({ url: "http://127.0.0.1:PORT", token });

BEVIA_ENGINE_URL + BEVIA_TOKEN in the environment also work.

Where the engine is

The engine listens on loopback by default, so http://127.0.0.1:PORT is where you will usually find it, and connectLocal() looks there. But the address is just an address: pass any URL the engine is actually reachable at — another machine on the owner's network, a tunnel the owner opened — and everything below works unchanged.

const bevia = new BeviaClient({ url: "https://engine.example.internal", token });

The doors, the capability grants and the refusals do not change with the hostname. Making an engine reachable beyond the machine it runs on is the owner's decision; the SDK neither arranges it nor assumes it away.

Evidence in

await bevia.intake.validate(capture);        // dry run: writes nothing, says exactly what is wrong
await bevia.intake.submit({
  thread_id: "garmin-runs-2026-07-15",       // stable: re-sending upserts
  source_platform: "garmin",
  source_kind: "garmin_activity",
  conversation: [{ speaker: "garmin", text: "6.2 mi in 52:10, avg HR 148.", emitted_at: "2026-07-15T13:58:00Z" }],
});

// Where it happened (posemesh-first) rides on the capture:
await bevia.intake.submit({
  thread_id: "cactus:dom-store-142:coca cola",
  source_platform: "cactus", source_kind: "cactus_search",
  conversation: [{ speaker: "robot", text: "Located Coca-Cola 12oz (SKU 4921) for “coca cola”", emitted_at: at }],
  location: { provider: "posemesh", domain_id: "dom-store-142", label: "Whole Foods — SoMa", pose: { x: 12.4, y: 0, z: -3.1 } },
}, { navmesh: { x: 12.1, z: -3.0, yaw: 1.57 } });   // robot-action data rides the envelope, never the label

Send what happened, verbatim, with the time it happened. The engine decides what it means; that is the whole contract.

What you can never send

evidence_shape (how evidence is shaped is declared engine-side per source_kind) and the authority fields — source_object, source_state, source_attestation, standing, scoring_basis, establishment_basis, role_basis, authority, trusted — at any level: the capture, a turn, a turn's metadata, or the envelope. A remote program carries raw source evidence; the engine's trusted adapters establish source authority. The engine ignores or verifies these anyway (a forged attestation is refused and the capture lands with no source object); the client refuses them before they leave, with the field named, so you learn at the keyboard rather than weeks later as a silent zero in corroboration. TypeScript refuses evidence_shape at compile time; the runtime check covers the rest and callers who are not compiling.

The envelope is read

intake.submit(capture, envelope) carries the envelope to every landed row's metadata verbatim (capture_envelope). It is not inert: the engine reads watched_root and origin_remote from it when it resolves which project a row was filed under. Send only facts the source established. The Local door does not validate the envelope's shape; the Cloud door does. It can never carry source authority.

Structured records

A turn can carry the source's own typed fields beside the rendered text. Send them in record — nested objects and arrays survive as sent, and numbers stay numbers:

await bevia.intake.submit({
  thread_id: "pos:store-142:2026-07-15",
  source_platform: "square", source_kind: "pos_sale",
  session_facts: { started_at: "2026-07-15T08:00:00-07:00", total_turns: 412 },
  conversation: [{
    speaker: "register-3",
    text: "Sale 4412 — 3 items, $27.40, card",
    emitted_at: "2026-07-15T13:58:00-07:00",   // the source's event time
    upstream_id: "sq_txn_4412",                 // the source's own id for the record
    source_appended_at: "2026-07-15T14:02:11-07:00",
    record: {
      id: "4412", total: 27.4, currency: "USD", tender: "card",
      items: [{ sku: "4921", qty: 2, unit: 3.5 }, { sku: "1180", qty: 1, unit: 20.4 }],
    },
  }],
});

Four things to know, all of them deliberate:

  • record is faithful source content, never authority. Putting a field there does not make it true, does not classify the turn, and does not let a payload claim standing it was not granted. Which of its fields mean anything is declared by the engine for the capture's source_kind; this layer reads none of them.
  • JSON only, 32 KiB. A plain JSON object that survives JSON.stringify → JSON.parse unchanged (Date, Map, BigInt, class instances: convert them yourself, so the conversion is yours and visible). Over the cap the record is dropped, never truncated — a cut record is a different record — and the turn still lands without it. It is temporary working material on the same retention floor as the raw text; nothing durable should assume it is still there.
  • upstream_id is the source record's identity; thread_id is not. upstream_id is the citation key — what lets a later reader point at the exact original row, message or event. thread_id names the stream, and the engine derives its own de-duplication key per turn.
  • emitted_at is event time; source_appended_at is not. emitted_at is when the thing happened on the source's clock and is the only event-time candidate the engine accepts. source_appended_at is when the source wrote the row, kept as a disclosed fact and deliberately never read as event time. Sending it does not fill in a missing emitted_at; nothing does. Leave emitted_at absent when the source has no true event time and the engine will say so honestly rather than guess.

session_facts are the source's own facts about the session as a whole. started_at / ended_at time the session, never a turn inside it, and will not stand in for a turn's missing emitted_at.

Validate first

intake.validate runs the same shape check, the same normalizer and the same record sanitizer as a real capture, and writes nothing. It is the only place that tells you about dropped records, because a dropped record does not fail a capture — the turn lands without it:

const dry = await bevia.intake.validate(capture);
// {
//   valid: true, would_create: 3, skipped: 0, errors: [],
//   records: { present: 3, preserved: 2, max_bytes: 32768,
//              dropped: [{ turn_index: 1, reason: "over_cap", bytes: 41231 }] },
//   note: "dry run — nothing was written. …"
// }
if (dry.records?.dropped.length) throw new Error("records would not survive");

valid: false means the payload could not be understood at all. Check records.dropped too — and skipped, which counts turns carrying no usable text.

Building an adapter

An adapter reads a source and hands over what the source actually said. That is the whole job, and its shortness is the point: everything an adapter is responsible for is a fact it can transcribe, and nothing it is responsible for is a judgment it has to make.

import { defineAdapter } from "@bevia/sdk/adapter";

export const acmePos = defineAdapter({
  manifest: {
    schema: "bevia-source-contract/v1",
    source: { kind: "acme.pos_sale", version: "1.0.0" },
    adapter: { name: "acme-pos", version: "0.1.0" },
    records: {
      identity: "txn_id",                                        // the source's own id
      clock: { field: "closed_at", format: "iso8601" },          // the source's own clock
      appended_at: "written_at",
      sequence: "seq",
    },
    session: { identity_fields: ["shift_id"], close_markers: [{ field: "shift_closed", equals: true }] },
    structure: { grouping_fields: ["register"], numeric_fields: [{ field: "total", fixed_unit: "USD" }] },
    population: { mode: "finite" },
  },
  read: async (since) => ({ records: await readRegister(since), complete: true }),
});

const batch = await acmePos.read(since);
await acmePos.submit(bevia, batch);      // any client, any runtime URL

The manifest declares only where a source puts its own native facts: which field is the record's id, which is its clock, which fields group it, whether the population ends. It is JSON and only JSON — no callbacks, no expressions, no query language, no addresses to fetch. There is nothing in it for a runtime to execute, because a manifest is read as data or it is refused.

What a manifest may not declare is any judgment: how evidence is shaped, what it means, what standing it has, how much it matters, what it relates to, which Territory it belongs in, whether it deserves Attention, whether it is ready to fold. Those are Bevia's, from the evidence, under its own law. The validator names each refusal rather than leaving it to a generic "unknown key", so you learn why.

One distinction that is easy to get backwards: the refusals apply to manifest keys, never to the field paths a manifest points at. A source is perfectly entitled to a column called importance, and you may name it as a grouping field — pointing at a source's own field is not a claim about what Bevia should do with it. Declaring importance itself is what is refused.

Nothing about an adapter is loaded by Bevia. You write it, you distribute it, you run it; what crosses the boundary is JSON through the same door every other capture uses. Until the owner has activated a contract for it (next section), a custom source kind is carried faithfully and prepared by nothing: the evidence lands and is preserved, and no treatment is chosen for it.

Registering a custom source

Before Bevia will accept records from a source it has never heard of, the owner has to accept the structure you declared. You propose; they approve; then you submit.

// 1. You propose. Needs the `source_contract` capability.
const { receipt, next } = await acmePos.register(bevia);
receipt.status;        // "proposed" — nothing is admitted yet
receipt.manifest_digest;
receipt.runtime_id;    // the runtime this is true on

// 2. The owner activates it in the Bevia app. You cannot; the door is
//    not in this SDK, on purpose.

// 3. Then submit against it.
await acmePos.submit(bevia, batch, { contract: receipt });

Re-proposing the identical manifest is idempotent — the digest is the identity, so the same bytes are the same contract. Change the structure and you have a new one: two different structures may not share a source kind and version, because which structure did this row arrive under has to stay answerable. Bump the source version; that is what versions are for.

What registration means

Bevia accepts this as the declared structure of this source.

It is not an acceptance of your interpretation of the source.

That sentence travels in the receipt itself, in means, so a receipt read later and out of context still says what it is. You may declare that trade_id is the row's identity and trading_day is the session. You may not declare what a trade means, what standing it has, what it relates to, or what anyone should notice about it — those stay Bevia's, decided from the evidence, and registering changes nothing about them.

Custom source kinds are namespaced

com.example.trade_feed, com.acme.inventory_sheet, com.builder.robot_events. Built-in kinds carry no dot at all, so requiring at least one makes impersonating one impossible by the shape of the name, before any reserved list is consulted. bevia., local., system. and builtin. are reserved on top of that.

Every refusal, and what it means

A capture under a namespaced kind must reference a contract, and the runtime resolves that reference against its own registry — never trusting the payload. It refuses, by name:

| code | what happened | |---|---| | contract_required | a namespaced kind arrived with no contract reference | | inline_manifest | the capture carried a manifest; register once, reference thereafter | | unknown_contract | no such contract on this runtime | | contract_inactive | proposed, not yet activated by the owner | | contract_revoked | the owner closed this door | | digest_mismatch | the manifest changed, so it is a different structure | | source_kind_mismatch / source_version_mismatch | registered for something else | | sensor_not_authorized | the owner did not authorize this client for it |

Provenance and revocation

Every admitted row is stamped with the contract's id, digest, kind, version, adapter and runtime — built from what the registry resolved, so structurally valid JSON can never mint a provenance fact it was not granted.

That stamp is why revocation is safe: it closes the door going forward and cannot reach backwards. Evidence already observed keeps its own answer to what declared structure did this arrive under, and an export years later still carries it. A later administrative act does not rewrite what was seen.

What is registered is a manifest, never code

Nothing is downloaded, imported, evaluated or fetched. Your adapter runs in your process and talks through the public doors like any other sensor. A manifest is JSON that is read, and the schema refuses everything that is not.

Qualifying an adapter

import { qualifyAdapter } from "@bevia/sdk/qualify";

const report = await qualifyAdapter(acmePos, {
  batches: [{ records: dayOne, complete: true }],
  expected: { record_ids: ["T1", "T2", "T3"] },   // what the source says should be there
  late: [{ records: [arrivedLate] }],
});

report.ready_for_use;   // nothing here proved it broken
report.blocking;        // what to fix
report.undetermined;    // what this sample could not decide — read this too

Offline, on your machine. It checks identity (present, unique, transcribed from the source rather than minted, stable under reordering and re-batching), clocks (parse, zone, event time kept apart from append time, sequence where declared), declared field-path coverage, population accounting, session boundaries, replay stability, rollover on source close markers, late arrivals, oversized records, and records that render nothing.

The rule that governs every verdict: what the sample cannot establish is UNKNOWN, never PASS. A streaming source cannot prove its population complete. A sample stating no expectations cannot prove its grouping right. Reporting those as passes would turn an absence of evidence into a clean bill — the one failure a qualification tool must not have. ready_for_use says only that nothing here proved the adapter broken; read undetermined before believing more.

Where this stops

There are two halves to knowing whether an adapter is any good, and only one belongs in public code.

| | | |---|---| | Structural — identity, clocks, replay and shuffle, grouping boundaries, completeness, late arrivals, rollover, dropped records | here, offline, arithmetic anyone can read | | Cognitive — atomization, smear, evidence coverage, citation quality, narrative sufficiency, useful preparation range | inside Bevia, against the real engine |

The second set are answers Bevia produces by running the evidence through its own preparation and narration. Computing them here would mean shipping that machinery. So they are decided in the app: you load a sample, Bevia evaluates it, and you get the verdict and the diagnostics — not the workings. The report names them under deferred rather than faking a check or leaving them out.

In one line: you can inspect whether your adapter preserves the source correctly; Bevia evaluates whether that source can produce good governed meaning.

source_shape is the same boundary in one field. It stays undetermined unless you pass a contract Bevia already issued, whose digest matches your manifest. This library never infers it — how evidence is shaped decides how it is prepared, and a tool you run on your own machine is not where that is settled.

The World out

const read = await bevia.world.current();                 // the governed CURRENT World, or null before the first publication
read.world.matter                                          // current Matter — never a superseded row
read.world.relations                                       // relations whose endpoints are current
read.world.references                                      // source-established names for identities a current Matter holds
read.durable                                               // the whole durable World, history included
read.transition                                            // set when an older publication's snapshot was served

const why      = await bevia.world.inspect(matterId);     // establishing spans, standing history
const insights = await bevia.attention();                 // what the publisher selected
const tracked  = await bevia.followthrough();             // suggestions and their assessments
const goals    = await bevia.objectives();                // see the note below — null today

History and present, together

The durable World a publication carries is history and present together. When a Matter's basis is superseded, its row stays — with current_standing: "superseded" and the lineage operation that moved it onto successors the World also holds. So durable_world.matter.length is not the size of the World, and a superseded row is not something the owner currently holds. On one measured World: 149 durable rows, 74 current Matter, 75 historical. This has been so since 2026-09-18; a program that counted matter before then has been over-counting since.

world.current() applies the same projection the product applies — exported as currentWorld() if you hold a publication yourself:

  • current Matter: current_standing in established | candidate | open | unknown (unknown is current — the source carried no standing);
  • Matter-scoped carriers (anchors, where, occurred_when, facets, claim_versions) follow their Matter;
  • relations keep only those whose endpoints are current; a namespaced referent in a source's own identity space (pos_txn:T142-0818-0243) is never a Matter and is always current;
  • references keep those whose subject a current Matter holds;
  • history carriers cross verbatim and carry no contract in this SDK.

Reading a Matter

import { isCurrentMatter, isHeldMatter, conditionHistory, currentCondition, referencesOf } from "@bevia/sdk";

for (const m of read.world.matter) {
  m.current_standing;             // read this first — see Standing
  m.claim;                        // null = a recorded unknown, not empty meaning
  m.unknowns;                     // verbatim, never trimmed
  m.provenance.origin;            // lawfully null: the compiler is an instrument, not the origin
  m.support_population;           // corroboration and recurrence, as two numbers — never one score
  if (isHeldMatter(m)) m.succession;   // current, standing on a superseded basis under a typed hold
}

Standing is established | candidate | open | rejected | superseded | withdrawn | unknown. This library never infers one.

Condition history

A Matter's conditions[] holds one row per declaring record, never edited. The current status of a condition identity is its latest declaration by the declaration's own clock:

const rows = conditionHistory(m, "shelf_scan@v1#4");   // [11:30 open, 18:20 open, 23:58 resolved]
const now  = currentCondition(m, "shelf_scan@v1#4");   // { status: "resolved", declared_when, record_id, upstream_id, … }
referencesOf(read.world, "condition:shelf_scan@v1#4"); // the names and ids sources gave this identity

Packets that re-carried the same declaration are provenance (carried_in), never additional history. A declaration with no clock sorts after every clocked one and is never given a time from anywhere else.

Evidence identity

Every receipt (evidence_refs[].ref) is an exact identity: semantic_moment:<id> or raw_moment:<id>, optionally :span:<n>. Follow it by identity or not at all — never by matching prose, never by picking a neighbour.

import { parseEvidenceRef } from "@bevia/sdk";
parseEvidenceRef("semantic_moment:fc3d…");   // { carrier: "semantic_moment", id, span, carrier_basis: "declared" }
parseEvidenceRef("moment:fc3d…");            // null — the bare prefix is retired; pass the store the collection declares
parseEvidenceRef("moment:fc3d…", "semantic_moment"); // { …, carrier_basis: "bound_by_collection" }

A proposal receipt's moment_id is a RAW moment (raw_moment:<id> in this vocabulary).

Relations

for (const r of read.world.relations) {
  r.subject; r.predicate; r.object;     // endpoints: a Matter id, `condition:<id>`, or a namespaced referent
  r.current_standing; r.declarant; r.evidence_refs;
  r.reference;                          // present only when a source's own reference FIELD formed it
  r.succession;                         // present only when an endpoint was superseded
}

Predicates a source's own words may establish: corrects, contradicts, supports. Predicates a source's own structured reference field may establish, under a lane contract: returns_against — a POS return's ref_receipt is the transaction it returns against. Such a relation always carries reference:

r.reference = {
  basis: "source_declared_structured_reference",
  lane: "pos_tlog", reference_field: "ref_receipt", reference_value: "T142-0818-0149",
  source_record_id, subject_identity: "pos_txn:T142-0818-0243", target_identity: "pos_txn:T142-0818-0149",
  target_record_id: "…" | null,
  target_resolution: "exact" | "absent" | "ambiguous",
  contract_predicate: "returns_against",     // always equals r.predicate
};

It is established only on an exact target that names its record. An absent or ambiguous target crosses as a relation of standing unknown with a null target — explicitly unresolved, never bound to a likely candidate (isResolvedReference(r) says which). The predicate is the lane contract's: no caller names one, this SDK never mints one, and REFERENCE_PREDICATES is closed. A namespaced endpoint answers not-established from world.inspect — it is a source identity, not a Matter, and that is the truthful answer.

Succession is not authority. When a relation's endpoint is superseded, the relation continues onto a successor only when exactly one successor carries the receipts the endpoint cited for it (succession: { status: "re_elected", successor, declaring_refs }; the predecessor row stays superseded, pointing forward). No successor carries them → { status: "held", reason: "authority_not_carried" }. Several do → { status: "held", reason: "ambiguous_successor" } — nothing chosen, nothing duplicated. Withdrawn or revoked → no continuation.

Refusals at the read doors

/world-snapshot, /world-inspect, /world-diff and /followthrough refuse at HTTP 200 with { world_snapshot_missing: true, code, reason } (not_published, pointer_unreadable, stale_builder, parse_failure, schema_unknown, builder_mismatch, wrong_principal); isWorldSnapshotMissing() tells them apart. world.snapshot() returns the raw artifact when you want the concept readings beside the durable World; this SDK declares no contract for those.

territories() was removed in 0.4.0: the engine retired the /territories door (HTTP 410) with the legacy geography it read. Regions are a map drawn from the World, not part of it, so world.current() has none; this SDK doesn't read Regions yet.

objectives() returns null today. It reads whatever the engine publishes at objectives/index, structurally, and nothing publishes that document yet — so on every current engine the answer is null. It is capability-dependent like any other read, it computes nothing, and it defines no schema: there is no goal, target, difference or follow-through contract in this SDK, and this method does not promise one is coming or imply what one would look like. Treat a non-null answer as an opaque document from that engine, and read the protocol document before depending on any field inside it.

const history  = await bevia.world.history();             // every publication, newest first
const march    = bevia.world.at(history.publications[7].manifest);
await march.publication();                                // the World as it was published then
await march.document("attention/insights");

const bundle   = await bevia.world.publication();         // one document set — take it away

Reads serve the published World, never a live replay. No publication yet reads { ok: false, code: "not_published" }.

world.diff() serves one window: into a publication (the newest, or { manifest }), from the empty World or from the publication before it. Any other range throws a BeviaError with code published_history_unavailable. attention() and objectives() are conveniences that return null both when nothing is published and when the document is empty; read world.document("attention/insights") for the publication stamp and to tell the two apart.

Ask

const answer = await bevia.ask("what changed in my thinking about pricing this month?");
if (answer.degraded) { /* no AI fuel configured: a deterministic grounded readout */ }

When the answer's posture is "clarify", it carries one question with options. Send the choice back with the same question and the Ask continues:

if (answer.posture === "clarify" && answer.clarification) {
  const c = answer.clarification;
  const next = await bevia.ask(question, {
    clarification_response: { answers: c.id, chosen: { kind: "referent", label: c.options[0] }, attempts_used: 1 },
  });
}

ask spends the owner's fuel and is its own capability.

Reality check (Verify)

One engine, two product modes, one MCP door. A Bevia engine serves mcp.* with its whole tool family. A Verify engine serves the same door with exactly one tool, reality_check, and realityCheck() wraps it, typed:

const r = await bevia.realityCheck({ question: "did the last agent finish this?", project: "my-repo" });
if ("code" in r && r.code === "not_published") {
  // the answer before the first publication that carries the evidence: say so, ask again later
} else {
  console.log(r.answer.word, r.answer.line);          // the plain answer, composed once by Verify
  for (const c of r.chains) console.log(c.subject, c.assessment, c.rungs.map((g) => `${g.rung}:${g.coverage}`));
  console.log(r.publication.basis_seq);               // every result names the publication it read
}

Scope it from what you know, never from guesses: project, since / until, revision, path_prefixes, and session (the asking session's id, to check that session's latest request). Each rung — DECIDED, CLAIMED, LOCAL, COMMITTED, PUSHED, MERGED, CHECKED_AT_REVISION, CURRENT — is observed, unsupported or unobservable; a rung Verify cannot see never counts as a negative, and none of them means "correct". Print answer as given; no surface composes its own. Nothing is written, no model is called, nothing is read live: the result is from Verify's published evidence, and says when it is stale.

On a Verify engine every door this README describes other than pairing, intake.submit, health and mcp.* answers wrong_mode (a 404 that names the mode). On a Bevia engine realityCheck() answers tool_error: that engine does not serve the tool. Declaring a Work — the bounded piece of work Verify reads against — is the owner's act in their app, not a door in this package; what a program knows about the work arrives as evidence through intake.submit.

Events

const sub = bevia.events.on("attention.created", async (ev) => {
  const insight = ev.data;                                // headline, why_now, matter_ids — ids, not evidence text
  const detail = await bevia.world.inspect(String(insight.matter_ids?.[0]));   // why_now on the event is a typed basis token
  // …do whatever you do
});

for await (const ev of bevia.events.subscribe({ kinds: ["world.published"] })) {
  console.log(ev.kind, ev.data.version);
}

Kinds: world.published, attention.created, capture.admitted, proposal.created, proposal.resolved, proposal.withdrawn, proposal.receipt. Every event's id is a cursor — pass it as after to resume with nothing replayed. No cursor means now; read the World for the state rather than replaying how it was built.

Propose, get authorized, report

const p = await bevia.propose({
  kind: "action",
  subject: { text: "the AAPL position", matter_ids: [thesisId] },
  proposal: { text: "Reduce the position by 20%." },
  basis: { evidence_refs: [insightId], reason: "Two contradicting theses in this week's publication." },
});

const decided = await bevia.proposals.awaitResolution(p.proposal_id);   // the OWNER decides, in the app
if (decided.status === "accepted") {
  const order = await broker.sell(0.2);                                    // you act; Bevia never does
  await bevia.proposals.report(p.proposal_id, {
    outcome: "done",
    summary: `Sold 20% at ${order.price}, order ${order.id}.`,
    source_url: order.url,
  });
}

A proposal establishes nothing. An accepted action is an authorization; the receipt lands on the ledger and as one evidence turn on the proposal's own thread, so the World sees what happened. Bevia owns was it proposed, who approved it, what authority existed, what happened. The action belongs to you.

Capabilities

| Capability | Opens | |---|---| | capture | intake.* | | read | world.*, attention, followthrough, objectives, mcp.*, realityCheck — and mcp.* includes ONE write tool, bevia_capture, which the engine admits on that door under read today | | source_contract | contracts.propose, contracts.list/get — propose the declared structure of a custom source; the owner activates it from the app | | evidence | raw moment text behind a territory (a door this SDK does not call) | | ask | ask | | subscribe | events.* | | propose | propose, proposals.* | | substrate_export | exportSubstrate — the WHOLE substrate, owner-grant only. Not to be confused with a Block Share's export, which is a bounded right over one Block and never reaches this door. |

A pairing from the owner's machine gets what it asks for. A pairing from another device gets its kind's default and the rest is recorded as pending for the owner to grant in the app. substrate_export and control are never granted by asking. A BeviaError with status: 401 carries the capability the door needs.

Share-bounded reads

A credential the owner minted against a Block Share is a bounded reader, whatever capabilities it lists. Its contract is Share → Block → Rights → Edition: every read it makes must be answered only from that authorized projection, and a read the engine cannot bound fails closed. Generic read is never sufficient for a Share credential, and a Share credential never reaches the owner's whole World. Today the aperture is served through mcp.* — the tool list narrows to what the Share's rights admit, every answer is formed from and cites only what lies inside the Block's edition, and an aperture the engine cannot evaluate raises BeviaError with code block_aperture_closed. This SDK carries no Block or Share door, so nothing in it can widen a Share; the full-World doors (world.*) are the owner's and their paired sensors'.

Errors

Every failure is a BeviaError with status, code, body and, on a 401, capability. code is the engine's own code when it gives one. Three to know: wrong_mode (this engine's product mode doesn't serve the door; an engine running Verify serves few of them), published_history_unavailable (a world.diff() range outside the window above), and stream_error (the event stream ended with an error; with reconnectMs set, the SDK reconnects instead). The engine rejects what it cannot understand and says exactly why; it never forbids what you are building.

The contract underneath

The Bevia Local extension protocol, v1.3 — PROTOCOL.md in every developer workspace, served as text at bevia.co/build/PROTOCOL.md, and documented at bevia.co/build. The SDK adds no door the protocol does not have; if a language has no SDK, that document is the SDK. A stdlib-only Python reference client of the same doors is among the SDK's examples in its source; it isn't part of this npm package.