# Thinkn — Belief State Infrastructure for AI Agents > Full doc bundle for AI ingestion. For per-page markdown, fetch https://thinkn.ai/dev/.md. > Live HTML docs: https://thinkn.ai/dev > Index with section links: https://thinkn.ai/llms.txt --- # Quick Reference (curated) # beliefs SDK — LLM Reference > Belief state infrastructure for AI agents. npm package: `beliefs` > Docs: https://thinkn.ai/dev > Hack Guide: https://thinkn.ai/dev/tutorial/hack-guide > Manifesto: https://thinkn.ai/dev/why/index This file is a single-read reference for coding agents. It explains why belief state exists, how it differs from memory/RAG, the mental model you need to use the SDK well, the full API surface, and how to wire it into common agent frameworks. ## Why beliefs matter ### Every breakthrough begins as a belief Progress does not begin with certainty. It begins with a view of reality that is incomplete, testable, and worth pursuing. But the current AI stack is not built for that. It stores documents, retrieves memories, and generates language. It does not maintain a living model of what is currently believed to be true, why it is believed, how strongly, what evidence supports it, where it conflicts, and what should change next. The `beliefs` SDK is that missing layer. It gives your agent a structured model of its current understanding: claims with confidence scores, conflict detection, gap awareness, a clarity score (0-1 readiness to act), and ranked next actions by expected information gain. Every transition is recorded with provenance. ### Scaling the past vs. discovering the unknown ``` ┌──────────────────────────────────────────────────────────────┐ │ SCALING THE PAST │ │ │ │ Memory ──────▶ "What happened before?" │ │ Retrieval ───▶ "What text is similar?" │ │ Generation ──▶ "What can I produce from this?" │ │ │ │ These scale what is already known. │ │ They do not model what is currently believed. │ │ They cannot surface what has never been seen. │ │ │ ├──────────────────────────────────────────────────────────────┤ │ DISCOVERING THE UNKNOWN │ │ │ │ Beliefs ─────▶ "What is true? How strongly? Why?" │ │ Evidence ────▶ "What supports or contradicts it?" │ │ Gaps ────────▶ "What do we not know?" │ │ Clarity ─────▶ "Are we ready to act or must we look │ │ deeper?" │ │ │ │ These model the present understanding of reality. │ │ They evolve as evidence changes. │ │ They surface what was previously invisible. │ └──────────────────────────────────────────────────────────────┘ ``` Without an explicit way to model and update beliefs, AI does not scale truth. It scales whatever assumptions it happened to start with. It scales inherited frames. It scales contradiction. It scales drift. As intelligence becomes abundant, coherence becomes scarce — and some beliefs are load-bearing. If the pricing model, the fundraising plan, and the diagnostic recommendation all rest on one unexamined assumption, the cost of that assumption being wrong is everything built on top of it. ### The five symptoms of drift These are what drift looks like in practice — the visible failures of systems that accumulate information without modeling what they believe: 1. **Agents contradict themselves.** Turn 3: "The market is $4.2B." Turn 12: "SEC filings suggest $3.8B." Turn 18: the agent cites $4.2B because it appeared first. No detection, no resolution, no awareness. 2. **Confidence is invisible.** The agent stated a number. Is it from one source or ten? Is it corroborated or contested? The context window does not encode this. Every piece of text looks equally valid. 3. **Guesses and facts are indistinguishable.** A user's intuition and a peer-reviewed study carry identical weight. There is no distinction between assumption and evidence. 4. **Agents do not know what they do not know.** No concept of "gap." No awareness that critical data is missing. No mechanism to prioritize what would reduce the most uncertainty. 5. **Bigger context makes it worse.** A 200K context window does not fix these problems. It carries stale assumptions further, with more fluency. More context is more surface area for drift. Belief state infrastructure is how we fix this. A shared layer where assumptions, evidence, confidence, contradictions, and decisions stay in sync, so humans and AI can think more clearly, adapt more honestly, and push together toward what has not yet been seen. ## Mental model ### The core loop Every agent turn follows three steps: read state, act, observe. ``` ┌──────────────────┐ user input ───▶│ beliefs.before() │─── returns current state, └────────┬─────────┘ clarity, gaps, moves, │ and a prompt to inject ▼ ┌──────────────────┐ │ your agent │─── runs with belief context └────────┬─────────┘ in its system prompt │ ▼ ┌──────────────────┐ │ beliefs.after() │─── extracts, fuses, └──────────────────┘ returns delta + new state ``` The SDK wraps your loop. It does not own it. It does not replace your agent framework, does not decide what your agent does, does not require a specific LLM provider, and does not sit in the critical path of your LLM calls. ### Belief types A belief is a structured assertion your agent holds about the world. Every belief has a type: | Type | Use case | |------|----------| | `claim` | An assertion supported or refuted by evidence | | `assumption` | Something taken as true without direct evidence | | `risk` | A potential negative outcome | | `evidence` | A data point or source that supports/refutes other beliefs | | `gap` | Something the agent has not investigated yet | | `goal` | What the agent is pursuing | Types are assigned automatically during extraction. You can also specify a type when adding beliefs manually via `beliefs.add(text, { type: 'assumption' })`. ### Evidence hierarchy Different evidence types carry different weight. A single verified measurement shifts confidence more than several inferences. | Type | Weight | Description | |------|--------|-------------| | `measurement` | highest | Audited metric, verified data point | | `citation` | high | Research report, external source with provenance | | `user-assertion` | medium-high | User explicitly stated this | | `expert-judgment` | medium | Expert opinion with rationale | | `inference` | low-medium | Agent-derived inference from available data | | `assumption` | lowest | Explicit assumption, no supporting evidence | Every piece of evidence has a direction: **supports** (increases confidence), **refutes** (decreases confidence), or **neutral** (adds information weight without shifting direction). Refuting evidence is captured, not discarded — nothing is silently dropped. ### Two-channel clarity Clarity is a 0-1 score that answers one question: does the agent understand enough to move forward? It decomposes into four channels, exposed on `BeliefContext.channels` and `BeliefDelta.channels`: ``` ┌──────────────────────────────────────────────────────────────┐ │ THE TWO QUESTIONS │ │ │ │ 1. DECISION RESOLUTION: "Can we make a call?" │ │ ───────────────────────────────────────── │ │ 80% → Yes, lean toward it │ │ 50% → No, it is ambiguous │ │ 99% → Strong signal │ │ │ │ 2. KNOWLEDGE CERTAINTY: "Have we done the work?" │ │ ───────────────────────────────────────── │ │ Just stated → No evidence yet │ │ 10 data points → Some certainty │ │ 100 data points → High certainty in our assessment │ │ │ └──────────────────────────────────────────────────────────────┘ ``` Two claims at 50% confidence are not the same. One has zero evidence (research it). The other has 40 data points that genuinely split both ways (decide, don't research). The two-channel model separates them. **Knowing you do not know is categorically different from not knowing.** The four quadrants: ``` Knowledge Certainty Low High ┌────────────┬────────────────┐ High │ │ │ Decision │ Belief │ Validated │ Resolution │ without │ belief. │ │ evidence. │ Ready to act. │ │ ▶ Invest- │ │ │ igate. │ │ ├────────────┼────────────────┤ Low │ │ │ Decision │ No idea. │ Genuinely │ Resolution │ Start │ uncertain. │ │ from │ Surface │ │ scratch. │ trade-offs. │ │ │ ▶ Decide, │ │ │ don't │ │ │ research. │ └────────────┴────────────────┘ ``` The other two channels: **coherence** (do the beliefs hang together, or are there unresolved contradictions?) and **coverage** (are important areas addressed, or are there large gaps?). Open gaps reduce clarity. Gaps with more downstream dependencies reduce it more. ### Fusion, not averaging When multiple agents share a namespace (`new Beliefs({ agent, namespace })`), their deltas merge into one world state. Conflicts are detected, resolved by trust weight, and kept visible in the trace. A measurement from an SEC filing outweighs an inference from an agent — but the contradiction is never silently dropped. Last-write-wins is not fusion. Averaging confidences is not fusion. Fusion is trust-weighted Bayesian merging with a visible conflict log. ### How knowledge certainty accumulates When you seed beliefs with `add()`, knowledge certainty starts at zero. `add('Market is $4.2B', { confidence: 0.8 })` sets decision resolution to 0.8, but the system has not seen evidence yet. Knowledge certainty tracks *earned evidence* — data accumulated since the belief was created. It grows when `after()` processes real agent output that references the claim, when multiple observations reinforce it, or when tool results provide independent confirmation. To build KC quickly, prefer `after()` on real output over seeding with `add()`. ## When to use beliefs Use the SDK when: - Your agent runs more than a few turns on the same topic. - Conflicting information from different sources matters to the outcome. - You need to trace why the agent believes something (compliance, debugging, audit). - Multiple agents share state and you need trust-weighted merging. - Your agent's readiness to act is decision-relevant (research more, or proceed?). Do not use it when: - The task is a single-turn chatbot reply with no persistent state. - You just need retrieval over documents — use a vector store. - You want to micromanage the fusion math — the SDK deliberately hides it. ### Anti-patterns - **Do not call `after()` per stream chunk.** Call it once per turn, after the model finishes. `after()` runs extraction and fusion; per-chunk calls are wasteful and produce inconsistent deltas. - **Do not bypass fusion.** Do not write to belief state through any path other than `after()` / `add()` / `resolve()` / `retract()` / `remove()`. The fusion engine owns conflict resolution and trust weighting. - **Do not read or depend on internal distributions, scoring models, or fusion weights.** The SDK exposes developer-facing contracts: `text`, `confidence`, `clarity`, `channels`, `readiness`, `moves`. The underlying math (Beta/Gaussian/Dirichlet distributions, entropy tracking, Bayesian updates) is intentionally hidden and may change. - **Do not treat confidence as truth.** A belief at 0.9 confidence with zero knowledge certainty is a stated guess, not a validated fact. Check `channels.knowledgeCertainty` before acting on high-confidence claims. - **Do not share an API key across untrusted tenants.** Use `namespace` for multi-tenant isolation. ## Documentation map Full docs live at https://thinkn.ai/dev. The sections below mirror the in-app navigation. ### Start — get running fast - [start/index](https://thinkn.ai/dev/start/index) — World models, the core loop, the integration strip, the "I want to..." matrix. - [start/install](https://thinkn.ai/dev/start/install) — `npm i beliefs`, get an API key, scopes at a glance, run a verification snippet. - [start/quickstart](https://thinkn.ai/dev/start/quickstart) — The 3-step loop and a 30-line runnable example. - [start/faq](https://thinkn.ai/dev/start/faq) — RAG vs. beliefs, vector store vs. beliefs, when not to use beliefs. ### Why — the positioning and the rationale - [why/index](https://thinkn.ai/dev/why/index) — The bug, the five symptoms, memory vs. beliefs, a worked example. ### Core — the vocabulary and the model - [core/beliefs](https://thinkn.ai/dev/core/beliefs) — Belief structure, the six types, evidence hierarchy, extraction vs. manual assertion. - [core/intent](https://thinkn.ai/dev/core/intent) — Goals, gaps, and the is/ought firewall. - [core/clarity](https://thinkn.ai/dev/core/clarity) — Two-channel clarity, the four quadrants, load-bearing beliefs. - [core/moves](https://thinkn.ai/dev/core/moves) — How thinking moves are ranked by expected information gain. - [core/world](https://thinkn.ai/dev/core/world) — The fused world state: beliefs, edges, goals, gaps, contradictions. ### Tutorial - [tutorial/research-agent](https://thinkn.ai/dev/tutorial/research-agent) — A 30-minute guided build, one concept per section. - [tutorial/hack-guide](https://thinkn.ai/dev/tutorial/hack-guide) — Framework recipes (Vercel AI, Anthropic, OpenAI, fetch) and project ideas. ### Reference — the SDK surface - [sdk/core-api](https://thinkn.ai/dev/sdk/core-api) — Full reference for every method, every option, every return field. - [sdk/patterns](https://thinkn.ai/dev/sdk/patterns) — Loop patterns (single/multi-turn, streaming, tool-aware, multi-agent) and smaller integration patterns. - [sdk/reads](https://thinkn.ai/dev/sdk/reads) — Eight scope-read methods (gaps, decisions, goals, risks, insights, evidence, intents, contradictions). - [sdk/moves](https://thinkn.ai/dev/sdk/moves) — Move recommender (list, generate, act, rank), `moves.forecast`, `moves.cascade`, free-form `forecast.predict`. - [sdk/trust](https://thinkn.ai/dev/sdk/trust) — Trust overrides for agents and sources, plus tool reliability priors. - [sdk/streaming](https://thinkn.ai/dev/sdk/streaming) — Subscribe / events / streamExtraction / drift. - [sdk/scoping](https://thinkn.ai/dev/sdk/scoping) — `namespace`, `writeScope`, `thread`, `agent`, `contextLayers` — how to isolate or share belief state. - [sdk/auth](https://thinkn.ai/dev/sdk/auth) — `apiKey` (server) vs. `scopeToken` (browser, edge, untrusted runtimes). ### Adapters — framework integrations - [adapters/claude-agent-sdk](https://thinkn.ai/dev/adapters/claude-agent-sdk) — `beliefs/claude-agent-sdk` hooks for `@anthropic-ai/claude-agent-sdk`. - [adapters/vercel-ai](https://thinkn.ai/dev/adapters/vercel-ai) — `beliefs/vercel-ai` middleware for `generateText` / `streamText`. - [adapters/react](https://thinkn.ai/dev/adapters/react) — React hooks for belief state (coming soon). - [adapters/devtools](https://thinkn.ai/dev/adapters/devtools) — Debug UI for inspecting belief state in development (coming soon). ### Use cases - [cases/finance](https://thinkn.ai/dev/cases/finance) — Investment theses, contradictions across sources, temporal decay on risk. - [cases/health](https://thinkn.ai/dev/cases/health) — Differential diagnosis, drug interactions, the is/ought firewall in clinical context. - [cases/engineering](https://thinkn.ai/dev/cases/engineering) — Security posture, cross-boundary assumption detection, decay on dependency claims. - [cases/science](https://thinkn.ai/dev/cases/science) — Hypothesis tracking, contradiction detection across experiments, swarm coherence. ### Internals — how it works under the hood - [internals/how-it-works](https://thinkn.ai/dev/internals/how-it-works) — The lifecycle: fusion, decay, evidence (with the is/ought firewall), and the ledger. - [internals/contracts](https://thinkn.ai/dev/internals/contracts) — Eight behavioral guarantees the engine commits to. ## SDK reference ### Install ```bash npm i beliefs ``` ### Authentication Get an API key at https://thinkn.ai/profile/api-keys. Set it as `BELIEFS_KEY` in your environment. ### Constructor ```ts import Beliefs from 'beliefs' // or: import { beliefs } from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, // required — get at thinkn.ai/profile/api-keys agent: 'research-agent', // optional, default 'agent' — identifies this contributor namespace: 'project-alpha', // optional, default 'default' — top-level isolation boundary writeScope: 'space', // 'thread' (default) | 'agent' | 'space' — authoritative layer thread: 'conversation-42', // required when writeScope === 'thread' (or use withThread()) contextLayers: ['self', 'agent', 'space'], // which layers before()/read() merge; defaults vary by writeScope baseUrl: 'https://www.thinkn.ai', // optional — override for local or self-hosted environments debug: true, // optional — log requests to console timeout: 120000, // optional, default 120000ms maxRetries: 2, // optional, default 2 (auto-retries 429/5xx with backoff) }) ``` The simplest runnable setup uses `writeScope: 'space'` so you don't need a thread: ```ts const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'project-alpha', writeScope: 'space', }) ``` **Scope rules.** `namespace` is the top-level isolation boundary — different namespaces never interact. `writeScope` decides which layer is authoritative on `add()` / `after()` / `resolve()`: `'thread'` (per-conversation, default), `'agent'` (durable per-agent scratchpad), or `'space'` (one shared store for the whole namespace). `contextLayers` decides what `before()` and `read()` merge back into the prompt context — thread defaults to `['self', 'agent', 'space']`, agent to `['self', 'space']`, space to `['self']`. Within a `space` write scope, beliefs from different `agent` values are fused (trust-weighted). #### withThread(threadId: string): Beliefs Clone the client with a bound thread, preserving all other config. Use this for chat-style session memory when you don't have the conversation ID at construction time. ```ts const baseBeliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'support', writeScope: 'thread', }) const beliefs = baseBeliefs.withThread(conversationId) ``` ### Methods #### before(input?: string): Promise Read current belief state before the agent acts. Inject `context.prompt` into your agent's system prompt. ```ts const context = await beliefs.before(userMessage) // context.prompt — string to inject as system prompt // context.beliefs — Belief[] with confidence scores // context.goals — string[] // context.gaps — string[] (what the agent doesn't know) // context.clarity — number 0-1 (readiness to act) // context.channels — { decisionResolution, knowledgeCertainty, coherence, coverage } // context.moves — Move[] (ranked next actions by info gain) ``` #### after(text: string, options?: AfterOptions): Promise Feed agent output after it acts. Extracts beliefs, detects conflicts, fuses into world state. **Call once per turn, not per stream chunk.** ```ts const delta = await beliefs.after(result.text) // delta.changes — DeltaChange[] (created / updated / removed / resolved) // delta.clarity — number 0-1 // delta.channels — ClarityChannels // delta.readiness — 'low' | 'medium' | 'high' // delta.moves — Move[] // delta.state — WorldState (full state after this turn) // For tool results, tag the tool name: await beliefs.after(toolResult, { tool: 'web_search' }) // To label provenance on every extracted belief (appears in trace): await beliefs.after(transcript, { source: 'quarterly-earnings-call' }) ``` AfterOptions: `tool?: string`, `source?: string` (free-form provenance label stored on each extracted belief). #### add(text: string, options?: AddOptions): Promise Assert a single belief, goal, or gap. ```ts await beliefs.add('Market is $4.2B', { confidence: 0.85, source: 'IDC Q4 2025 Tracker' }) await beliefs.add('Missing APAC data', { type: 'gap' }) await beliefs.add('Determine TAM', { type: 'goal' }) await beliefs.add('Market is $6.8B', { confidence: 0.95, evidence: 'IDC Q4 2025 report', supersedes: 'Market is $4.2B', }) // Deterministic hot path — no LLM extraction, faster when you already know the shape: await beliefs.add('Market is $4.2B', { mode: 'claims', confidence: 0.85 }) // Run LLM-gated extraction over raw text (equivalent to after(), exposed on add() for symmetry): await beliefs.add(longTranscript, { mode: 'output', source: 'agent-trace' }) ``` AddOptions: `confidence?: number`, `type?: 'claim'|'assumption'|'evidence'|'risk'|'gap'|'goal'`, `evidence?: string`, `supersedes?: string`, `source?: string` (provenance label, e.g. document name, URL, tool), `mode?: 'claims' | 'output'`. **`mode` semantics:** | `mode` | Engine route | When to use | |--------|-------------|-------------| | omitted | `/ingest` | Default. Engine dispatches based on body shape. | | `'claims'` | `/ingest/claims` | Deterministic hot path. No LLM call. | | `'output'` | `/ingest/output` | LLM-gated extraction from raw text. Equivalent to `after(text)`. | `mode: 'output'` only works on the single-text overload. Passing it to `add(items[], { mode: 'output' })` throws `TypeError`. For batch ingestion of structured items, use `mode: 'claims'` or omit `mode`. #### add(items: AddManyItem[]): Promise Assert multiple beliefs, goals, or gaps in one request. ```ts await beliefs.add([ { text: 'Market is $4.2B', confidence: 0.8 }, { text: 'Missing APAC data', type: 'gap' }, { text: 'Determine TAM', type: 'goal' }, ]) ``` #### observe(envelope: ObserveEnvelope): Promise Structured-input primitive for non-agent surfaces: UI events, document edits, manual ingest, hooks, integration webhooks. Where `before()` / `after()` model an agent loop, `observe()` carries the engine's full provenance vocabulary (`surface`, `kind`, `tags`, `agentId`, `actor`) directly so non-agent consumers don't smuggle metadata through a `source` string. ```ts await beliefs.observe({ content: 'User dragged "Pricing" onto the Decisions frame.', surface: 'canvas', kind: 'block_moved', actor: 'user', tags: [`block:${blockId}`, `frame:${frameId}`], }) ``` Envelope fields: `content` (required, non-empty), `actor` (default `'assistant'`), `surface` (e.g. `'chat'`, `'canvas'`, `'document'`, `'tool'`, `'integration'`), `kind` (sub-event tag like `'block_created'`, `'doc_written'`), `tags` (free-form provenance tags), `agentId`, `depth`, `signalFocused`. Returns `{ success, applied, extractionStatus: 'ok' | 'empty' | 'error', extractionError?, beliefsExtracted, edgesCreated, contradictionsDetected, gapsResolved }`. `applied` is `true` only when a non-empty delta hit the world state. #### stateAt(options?: StateAtOptions): Promise<{ state: BeliefSnapshot; appliedDeltas: number }> Replay belief state at a specific point in time. Use one of `step`, `traceId`, or `asOf` to select the replay window; pass `beliefId` and/or `agentId` to narrow the deltas considered. ```ts const yesterday = new Date(Date.now() - 24 * 3600_000).toISOString() const { state, appliedDeltas } = await beliefs.stateAt({ asOf: yesterday }) await beliefs.stateAt({ traceId: 'trace-abc-123' }) await beliefs.stateAt({ beliefId: 'b-market-size' }) ``` StateAtOptions: `beliefId?: string`, `agentId?: string`, `step?: number` (replay every delta up to this seq), `traceId?: string` (replay this trace plus everything before), `asOf?: string` (ISO timestamp). A workspace with no archive activity returns an empty state with `appliedDeltas: 0` instead of erroring. #### removeWhere(filter: { source: string }): Promise<{ success: true; removed: number; source: string }> Bulk-remove beliefs sharing a `':'` provenance ref. Used when an upstream block, document, or message is deleted and its derived beliefs should go with it. ```ts const { removed } = await beliefs.removeWhere({ source: `block:${blockId}` }) console.log(`Retracted ${removed} beliefs`) ``` **Currently only `'block:'` is supported.** Any other kind throws `BeliefsError('remove_where/unsupported_source')`. Engine support for `agent:`, `thread:`, and `source:` is in flight; until then, retract individual beliefs with `retract()` or `remove()`. #### resolve(text: string): Promise Mark a gap as resolved. The gap is removed from `context.gaps` and its resolution is recorded in the trace. ```ts await beliefs.resolve('Missing APAC data') ``` #### read(): Promise Full world state: beliefs, goals, gaps, edges, contradictions, clarity, channels, moves, prompt. Use when you need everything in one call. ```ts const world = await beliefs.read() ``` #### snapshot(): Promise Lightweight read — beliefs, goals, gaps, edges, contradictions. No clarity, moves, or prompt computation. Faster than `read()` when you only need raw state. ```ts const snap = await beliefs.snapshot() ``` #### list(options?: ListOptions): Promise<{ beliefs: Belief[]; nextCursor?: string }> Paged read with full-text query and filters. Canonical app-builder read; supersedes the older `search()`. ```ts const page = await beliefs.list({ query: 'market size', filter: { type: ['claim', 'goal'] }, limit: 25, }) for (const b of page.beliefs) render(b) if (page.nextCursor) { /* fetch next page with { cursor: page.nextCursor } */ } ``` ListOptions: `query?: string`, `filter?: { type?: string|string[]; source?: string|string[]; lifecycle?: string|string[] }`, `limit?: number`, `cursor?: string`. #### get(beliefId: string): Promise Detail-page payload for one belief: the belief, supporting/contradicting relations, cross-belief links, history timeline, recommended next move, and an optional clarity sidebar. One method, one round-trip from the consumer's POV; SDK fans out to three engine endpoints in parallel. ```ts const detail = await beliefs.get('belief-abc123') renderHeader(detail.belief.text, detail.belief.clarity) for (const n of detail.relations.supporting) renderSupport(n) for (const n of detail.relations.contradicting) renderContradiction(n) if (detail.thinkingMove) renderRecommendedMove(detail.thinkingMove) ``` Returns `{ success, belief, relations: { supporting, contradicting }, links, history, thinkingMove, clarity?, durationMs }`. #### graph(options?: GraphOptions): Promise Render-focused projection of the belief graph: nodes, edges, contradictions, and aggregate stats. Use when drawing a graph in a UI or analyzing its shape. ```ts const projection = await beliefs.graph({ filter: { kinds: ['claim', 'goal'], minConfidence: 0.4, limit: 200 }, }) for (const node of projection.nodes) renderNode(node) for (const edge of projection.edges) renderEdge(edge) ``` GraphOptions: `filter?: { limit?, kinds?, minConfidence?, maxContradictions? }`, `scope?: { spaceId?, studioId?, sessionId? }`. Returns `{ nodes, edges, contradictions, stats: { nodeCount, edgeCount, contradictionCount, edgesByLayer? } }`. The `edgesByLayer` breakdown labels each edge as `explicit` (user-asserted), `ledger` (causal-history derived), `contradiction`, `similarity`, or `domain`. #### search(query: string): Promise **Deprecated.** Alias for `list({ query }).then(r => r.beliefs)`. Will be removed in a future minor. Migrate to `list()`. ```ts const results = await beliefs.search('market size') ``` #### trace(beliefId?: string): Promise Audit trail of belief transitions. Pass a `beliefId` to trace one belief; omit for the full ledger. ```ts const history = await beliefs.trace() const single = await beliefs.trace('belief-abc123') ``` #### retract(beliefId: string, reason?: string): Promise Mark a belief as retracted. The belief stays in the ledger but is excluded from the active world state. Use when you learn a claim was wrong and want to preserve the trace. ```ts await beliefs.retract('belief-abc123', 'Source was misquoted') ``` #### remove(beliefId: string): Promise Delete a belief entirely. Stronger than `retract()` — the belief is removed from the world state. Prefer `retract()` when you need the audit trail. ```ts await beliefs.remove('belief-abc123') ``` #### reset(): Promise<{ removed: number }> Clear all beliefs, goals, and gaps in the current scope. Returns the number of items removed. Destructive — primarily for tests and development. ```ts const { removed } = await beliefs.reset() ``` ### Streaming Two SSE-based streams over one transport: a state stream (`subscribe` / `events`) and a per-request extraction stream (`streamExtraction`). Retries, abort propagation, and frame validation live inside the SDK. #### subscribe(handler, options?): Subscription Push state changes into a callback. Returns `{ done, unsubscribe }`. ```ts const sub = beliefs.subscribe( (event) => { if (event.type === 'belief_records_updated') renderRecords(event.beliefRecords) else if (event.type === 'belief_records_stale') requestFullRefresh(event.reason) }, { onError: console.error, signal: ac.signal }, ) sub.unsubscribe() await sub.done ``` Options: `onError?` (default `console.error`), `onClose?`, `dropHeartbeats?` (default `true`), `signal?: AbortSignal`. Frame types: `belief_records_updated`, `belief_records_stale`, `heartbeat`. `belief_records_stale` is a hint to refresh from `read()` or `snapshot()`. #### events(options?): AsyncIterable Same stream, async-iterable face. Use when you prefer `for await` over the callback shape. ```ts const ac = new AbortController() for await (const event of beliefs.events({ signal: ac.signal })) { if (event.type === 'belief_records_updated') renderRecords(event.beliefRecords) } ``` #### streamExtraction(request, handler?, options?): AsyncIterable & { cancel(): void } Per-request extraction of beliefs from a content payload. Yields `belief_event` frames as the engine extracts; ends with exactly one `complete` or `error` chunk. Optional `handler` callback runs alongside iteration. ```ts const stream = beliefs.streamExtraction({ content: longTranscript, surface: 'voice' }) for await (const chunk of stream) { if (chunk.type === 'belief_event') appendIncrementalBelief(chunk.event) else if (chunk.type === 'complete') finalize(chunk.eventCount) else if (chunk.type === 'error') showError(chunk.message) } stream.cancel() // mid-extraction abort ``` #### drift.watch(handler, options?) / drift.events(options?) SSE stream of per-agent reliability drift events. Engine snapshots a baseline at stream start, then emits a `DriftEvent` per `(agent, evidenceType)` on each polling tick with a `driftDetected` boolean. ```ts const sub = beliefs.drift.watch( (event) => { if (event.type === 'drift' && event.driftDetected) { console.warn(`${event.agentId} drift on ${event.evidenceType}: shift=${event.meanShift}`) } }, { targetAgentId: 'researcher', pollIntervalMs: 30_000 }, ) ``` Options: `targetAgentId?`, `pollIntervalMs?` (default 10000, min 1000, max 300000), `zThreshold?` (default 1.645), `dropHeartbeats?`, `onError?`, `onClose?`, `signal?`. ### Moves namespace Engine-recommended next actions, ranked by expected information gain. Requires `apiKey` or `scopeToken` auth (`serviceToken` cannot invoke `moves.*`). ```ts const moves = await beliefs.moves.list({ topN: 3 }) const result = await beliefs.moves.generate({ beliefId: 'b-abc', includeJustification: true }) await beliefs.moves.act(move.id, 'accept') // 'accept' | 'snooze' | 'dismiss' const ranked = await beliefs.moves.rank({ topN: 5, budget: 0.05 }) const fcast = await beliefs.moves.forecast('gather_evidence', { depth: 3, rollouts: 50 }) const cascade = await beliefs.moves.cascade('gather_evidence', { targetBeliefId: 'b-abc', magnitude: 0.3 }) ``` | Method | Returns | What it does | |--------|---------|--------------| | `moves.list(opts?)` | `ThinkingMove[]` | Currently-ranked moves in scope. Option: `topN`. | | `moves.generate(opts)` | `{ success, move \| null, target, reason?, durationMs }` | Fresh move targeting one belief. Required `beliefId`; optional `includeJustification`, `sessionId`. `move: null` with `reason: 'belief_complete'` = nothing to recommend. | | `moves.act(moveId, action, opts?)` | `{ success, move, durationMs }` | Record `'accept' \| 'snooze' \| 'dismiss'`. Throws `TypeError` on any other action. | | `moves.rank(opts?)` | `MoveRankingSummary[]` | Composite ranking with `qValue`, `expectedInfoGain`, `cost`, `valueOfInformation`. Options: `topN` (default 5, max 50), `budget`, `agentId`, `signal`. | | `moves.forecast(action, opts?)` | `ForecastSummary` | Project the action's value on the **current agent's** belief state. Options: `depth` (max 5), `rollouts` (max 200), `maxTopics`, `agentId`, `signal`. | | `moves.cascade(action, opts?)` | `CascadeSummary` | Project the action across **other agents'** beliefs via the influence matrix. Options: `targetBeliefId` (defaults to most-uncertain belief), `magnitude` (0–1, default 0.2), `maxAgents`, `agentId`, `signal`. | #### forecast.predict(actions, options?): Promise Free-form action forecasting. Same predictive model as `moves.forecast`, but accepts an arbitrary list of caller-supplied action strings and returns one summary per input action, in input order. ```ts const forecasts = await beliefs.forecast.predict( ['gather_evidence_apac', 'design_test_market_size', 'reframe_question'], { horizon: 3, rollouts: 50 }, ) const ranked = [...forecasts].sort((a, b) => b.score - a.score) ``` Options: `horizon?` (default 1, max 5), `rollouts?` (default 30, max 200), `maxTopics?`, `agentId?`, `signal?`. Each `ForecastSummary` has `{ id, summary, score, willAnswer, confidence, why, suggestion?, relatedBeliefs? }`. Cold-start workspaces return `confidence: 'low'` and low scores honestly — that's the engine telling you it has no track record yet. ### Trust namespace Per-scope overrides for how much weight an agent or evidence source carries during fusion. Every override is a stated `(confidence, strength)` rating the engine applies at fusion time. Requires `apiKey` or `scopeToken` auth (`serviceToken` cannot mutate user-scoped trust). ```ts // Disable an unreliable source (locked overrides never drift): await beliefs.trust.set( { kind: 'source', id: 'rumor-mill' }, { confidence: 0.0, strength: 100, lock: true }, ) // Trust a domain expert agent above the default: await beliefs.trust.set( { kind: 'agent', id: 'risk-bot' }, { confidence: 0.95, strength: 50 }, ) const all = await beliefs.trust.list() // TrustOverride[] const justAgents = await beliefs.trust.list({ kind: 'agent' }) // filter by kind const one = await beliefs.trust.get({ kind: 'agent', id: 'risk-bot' }) // TrustOverride | null const { removed } = await beliefs.trust.unset({ kind: 'agent', id: 'risk-bot' }) ``` `set()` validates synchronously: `confidence` must be in `[0, 1]`, `strength` must be ≥ 0, `target.kind` must be `'agent' | 'source'`. Strength rule of thumb: **10** for a weak preference (still adjustable by learning), **100** for a confident rating, **500** for an immovable position. #### tools.observe(envelope) / tools.priors(options?) Distinct from the top-level `observe()`. Records and reads running estimates of *tool* reliability (success rate per `(tool, contextClass)`). Lightweight — single success/failure outcome per call. ```ts const prior = await beliefs.tools.observe({ tool: 'web_search', success: true, contextClass: 'market-research', }) const all = await beliefs.tools.priors({ tool: 'web_search' }) ``` Returns `ToolPriorSummary` with `{ tool, contextClass, rate, confidence, credibleInterval, observations }`. `confidence` is `'low'` below 5 observations, `'medium'` 5–20, `'high'` 20+. ### Admin namespace (serviceToken only) Service-token-only inspection and regeneration of persisted thinking moves for a space. Not callable with `apiKey` or `scopeToken`. ```ts const audit = await beliefs.admin.auditMoves(spaceId) await beliefs.admin.regenerateMoves(spaceId) ``` ### Types ```ts interface Belief { id: string; text: string; confidence: number; type: string label?: string; createdAt: string; updatedAt?: string source?: string // provenance label (document, URL, tool, agent output) lifecycle?: 'active' | 'retracted' | 'invalidated' | 'expired' | 'resolved' } interface BeliefContext { prompt: string; beliefs: Belief[]; goals: string[]; gaps: string[] clarity: number; channels?: ClarityChannels; moves: Move[] } interface BeliefDelta { changes: DeltaChange[]; clarity: number; channels?: ClarityChannels readiness: 'low' | 'medium' | 'high'; moves: Move[]; state: WorldState } interface WorldState { beliefs: Belief[]; goals: string[]; gaps: string[]; edges: Edge[] contradictions: string[]; clarity: number; channels?: ClarityChannels moves: Move[]; prompt: string } interface BeliefSnapshot { beliefs: Belief[]; goals: string[]; gaps: string[]; edges: Edge[] contradictions: string[] } interface ClarityChannels { decisionResolution: number; knowledgeCertainty: number coherence: number; coverage: number } interface Move { action: string; target: string; reason: string value: number; executor?: 'agent' | 'user' | 'both' } interface Edge { type: string; source: string; target: string; confidence: number } interface DeltaChange { action: 'created' | 'updated' | 'removed' | 'resolved' beliefId: string; text: string confidence?: { before?: number; after?: number } reason?: string; source?: string // origin of this change } interface TraceEntry { action: 'created' | 'updated' | 'removed' | 'resolved' beliefId?: string; confidence?: { before?: number; after?: number } agent?: string; timestamp: string; reason?: string; source?: string } // --- Stubs for newer surfaces; see /dev/sdk/* for full shapes --- interface GraphNode { id: string; kind: string; text: string; confidence: number; /* see /dev/sdk/core-api */ } interface GraphEdge { id: string; source: string; target: string; type: string; layer: 'explicit' | 'ledger' | 'contradiction' | 'similarity' | 'domain'; confidence: number } interface BeliefDetail { success: boolean belief: { id; title?; text; category; kind; clarity; source; provenanceType?; updatedAt?; sourceId?; childEvidence } relations: { supporting: GraphNeighbor[]; contradicting: GraphNeighbor[] } links: BeliefDetailLink[] history: BeliefDetailHistoryItem[] thinkingMove: ThinkingMove | null clarity?: BeliefDetailClarity durationMs: number } interface Subscription { done: Promise; unsubscribe(): void } type BeliefStreamEvent = | { type: 'belief_records_updated'; sessionId: string; spaceId: string; viewId: string; beliefRecords: BeliefRecord[]; groupIndex: number; totalGroups: number; timestamp: string } | { type: 'belief_records_stale'; sessionId: string; spaceId: string; reason: string; timestamp: string } | { type: 'heartbeat'; timestamp: string } type BeliefExtractionStreamChunk = | { type: 'belief_event'; index: number; event: { id: string; baseEvent: string; semanticLabel: string; text: string; actor: string; sourceMessageIds: string[] } } | { type: 'complete'; lastProcessedMessageId: string; eventCount: number } | { type: 'error'; message: string } interface TrustEntity { kind: 'agent' | 'source'; id: string } interface TrustOverride { entity: TrustEntity; confidence: number; strength: number; locked: boolean; updatedAt: string } interface ThinkingMove { id: string; targetId: string; targetEntityType?: string; targetEntityId?: string action: 'clarify' | 'gather_evidence' | 'resolve_uncertainty' | 'compare_paths' | string rationale: string; expectedDeltaH: number status: 'suggested' | 'accepted' | 'snoozed' | 'dismissed' | string suggestedModality?: string; qValue?: number; executor?: 'agent' | 'user' | 'both' createdAt: string; updatedAt?: string; resolvedAt?: string } interface ForecastSummary { id: string; summary: string; score: number // 0–1 expected value willAnswer: string[]; confidence: 'low' | 'medium' | 'high' why: string; suggestion?: string; relatedBeliefs?: string[] } interface MoveRankingSummary { id: string; summary: string; targetId: string targetKind: 'claim' | 'goal' | 'gap' action: string; subType: string qValue: number; expectedInfoGain: number; cost: number; valueOfInformation: number executor: 'agent' | 'user' | 'both'; confidence: 'low' | 'medium' | 'high' } ``` ### Error handling ```ts import Beliefs, { BetaAccessError, BeliefsError } from 'beliefs' try { const delta = await beliefs.after(text) } catch (err) { if (err instanceof BetaAccessError) { // Missing or invalid API key (401/403) } if (err instanceof BeliefsError) { // err.code — e.g. 'rate_limit/exceeded', 'validation/invalid_params' // err.retryable — boolean // err.retryAfterMs — suggested wait (ms) } } ``` Rate limit: 60 requests/minute per key. ## SDK capabilities The hosted API does real work behind each call. Understanding which capability is triggered by which method helps you pick the right entry point. | Capability | What it does | Triggered by | |------------|--------------|--------------| | **Extraction** | LLM-powered belief extraction from agent output and tool results. Finds claims, assumptions, risks, evidence, and gaps. You do not parse outputs yourself. | `after(text)` | | **Linking** | Automatic detection of contradictions, support, derivation, and supersession relationships between beliefs. Populates `edges` and `contradictions`. | `after()` → surfaced via `read().edges` | | **Deduplication** | Embedding-based similarity matching prevents duplicate beliefs when agents restate the same claim in different words. Runs transparently inside `after()`. | `after()` (invisible) | | **Fusion** | Trust-weighted merging across multiple agents sharing a namespace. Conflicts stay visible in the ledger, never silently dropped. | `Beliefs({ agent, namespace })` + `after()` | | **Clarity scoring** | 0-1 readiness assessment combining decision resolution, knowledge certainty, coherence, and coverage into one number plus four sub-channels. | `before().clarity` / `delta.clarity` / `world.channels` | | **Thinking moves** | Ranked next actions by expected information gain. Tells the agent where to look next to reduce uncertainty where it matters most. | `before().moves` / `delta.moves` | | **Provenance and trace** | Full audit trail of every transition: who stated it, what evidence, how confidence evolved, entropy before/after. | `trace()` | | **Gap tracking** | Explicit modeling of what the agent has not investigated. Gaps are first-class, penalize clarity, and drive move ranking. | `before().gaps` / `add(text, { type: 'gap' })` / `resolve(text)` | | **Retraction without loss** | Mark beliefs as wrong while preserving the ledger. Supports compliance and debugging. | `retract(id, reason)` | ## Framework integrations ### The core loop (any framework) ```ts const context = await beliefs.before(userMessage) const result = await yourAgent(context.prompt, userMessage) const delta = await beliefs.after(result) if (delta.readiness === 'high') { /* act */ } else { /* keep investigating — follow delta.moves[0] */ } ``` ### Vercel AI SDK ```ts import { generateText } from 'ai' import { anthropic } from '@ai-sdk/anthropic' const context = await beliefs.before(question) const { text } = await generateText({ model: anthropic('claude-sonnet-4-20250514'), system: context.prompt, prompt: question, }) const delta = await beliefs.after(text) ``` ### Anthropic SDK ```ts import Anthropic from '@anthropic-ai/sdk' const context = await beliefs.before(question) const message = await new Anthropic().messages.create({ model: 'claude-sonnet-4-20250514', max_tokens: 4096, system: context.prompt, messages: [{ role: 'user', content: question }], }) const text = message.content.filter(b => b.type === 'text').map(b => b.text).join('') const delta = await beliefs.after(text) ``` ### OpenAI SDK ```ts import OpenAI from 'openai' const context = await beliefs.before(question) const completion = await new OpenAI().chat.completions.create({ model: 'gpt-4o', messages: [ { role: 'system', content: context.prompt }, { role: 'user', content: question }, ], }) const text = completion.choices[0]?.message?.content ?? '' const delta = await beliefs.after(text) ``` Note: for o-series models (o3, o4-mini), use `role: 'developer'` instead of `role: 'system'`. ### Clarity-driven routing ```ts const context = await beliefs.before(input) if (context.clarity < 0.3) await runResearch(context.gaps) else if (context.clarity > 0.7) await draftRecommendations(context.beliefs) else await investigateGaps(context.gaps) ``` ### Multi-agent shared state ```ts const researcher = new Beliefs({ apiKey, agent: 'researcher', namespace: 'project' }) const analyst = new Beliefs({ apiKey, agent: 'analyst', namespace: 'project' }) await researcher.after(researchOutput) const context = await analyst.before('Interpret the findings') // analyst sees researcher's beliefs, fused by trust weight ``` ### Gap-driven research ```ts const context = await beliefs.before(input) for (const gap of context.gaps) { const result = await searchTool.run(gap) await beliefs.after(result, { tool: 'search' }) } ``` ## Built-in adapters Adapters are subpath imports from the same `beliefs` package. No extra install. ### Vercel AI SDK middleware ```ts import { generateText, wrapLanguageModel } from 'ai' import { anthropic } from '@ai-sdk/anthropic' import Beliefs from 'beliefs' import { beliefsMiddleware } from 'beliefs/vercel-ai' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY }) const { text } = await generateText({ model: wrapLanguageModel({ model: anthropic('claude-sonnet-4-20250514'), middleware: beliefsMiddleware(beliefs), }), prompt: 'Research the AI tools market', }) ``` Options: `capture?: 'response' | 'tools' | 'all'` (default: `'response'`), `includeContext?: boolean` (default: `true`) ### Claude Agent SDK hooks ```ts import { query } from '@anthropic-ai/claude-agent-sdk' import Beliefs from 'beliefs' import { beliefsHooks } from 'beliefs/claude-agent-sdk' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY }) const result = await query({ prompt: 'Research AI tools market', options: { hooks: beliefsHooks(beliefs) }, }) ``` Options: `capture?: 'tools' | 'all'` (default: `'tools'`), `includeContext?: boolean` (default: `true`), `toolFilter?: string` (regex) ## Public HTTP API If you cannot use the npm package, hit the HTTP API directly. All endpoints require `Authorization: Bearer `. Base URL: `https://thinkn.ai/api/sdk/v1/beliefs`. ### Read & write | Endpoint | Purpose | SDK equivalent | |----------|---------|----------------| | `POST /context` | Get belief context before the agent acts | `before()` | | `POST /ingest` | Submit observation for extraction and fusion | `after(text)` | | `POST /ingest/claims` | Deterministic structured-claims path (no LLM) | `add(text, { mode: 'claims' })` | | `POST /ingest/output` | LLM-gated extraction from raw text | `add(text, { mode: 'output' })` | | `POST /ingest-tool-result` | Submit tool result with source tagging | `after(text, { tool })` | | `POST /observe` | Structured envelope for non-agent surfaces | `observe(envelope)` | | `POST /apply` | Apply a structured delta to world state | `add()` / `resolve()` | | `POST /reset` | Clear all beliefs in scope | `reset()` | | `GET /snapshot` | Get lightweight state snapshot | `snapshot()` | | `GET /world-state` | Full world state (beliefs + clarity + moves + prompt) | `read()` | | `POST /state/at` | Replay state at step / traceId / asOf | `stateAt(opts?)` | | `GET /ledger` | Query the audit trail | `trace()` | ### Browse, detail, graph | Endpoint | Purpose | SDK equivalent | |----------|---------|----------------| | `GET /records` | Paged read with query/filter/cursor | `list(opts?)` | | `GET /search` | Legacy search-by-text (deprecated) | `search(query)` | | `GET /detail/[id]` | Detail-page payload (belief + relations + history) | `get(beliefId)` | | `GET /detail/[id]/evidence` | Just the supporting evidence neighborhood | (subcall of `get()`) | | `GET /detail/[id]/contradictions` | Just the contradicting neighborhood | (subcall of `get()`) | | `POST /by-block/[blockId]` | Bulk-remove beliefs from a block source | `removeWhere({ source: 'block:' })` | | `GET /graph` | Render-focused projection (nodes/edges/contradictions/stats) | `graph(opts?)` | | `POST /clarity` | Compute clarity for the current scope | (component of `before()`/`read()`) | | `GET /gaps` / `/risks` / `/decisions` / `/goals` / `/insights` / `/evidence` / `/intents` / `/contradictions` | Plain-English faceted reads | `reads.*` (see `/dev/sdk/reads`) | ### Moves & forecasting | Endpoint | Purpose | SDK equivalent | |----------|---------|----------------| | `GET /thinking-moves` | Currently-ranked moves in scope | `moves.list(opts?)` | | `POST /thinking-moves/generate` | Fresh move targeting one belief | `moves.generate(opts)` | | `POST /thinking-moves/[moveId]/action` | Record accept/snooze/dismiss | `moves.act(moveId, action)` | | `POST /moves/rank` | Composite ranking with q/cost/voi | `moves.rank(opts?)` | | `POST /moves/forecast` | Project one action on this agent's beliefs | `moves.forecast(action)` | | `POST /moves/forecast-cascade` | Project one action across other agents | `moves.cascade(action)` | | `POST /forecast/predict` | Free-form forecast over a list of actions | `forecast.predict(actions)` | ### Trust & tools | Endpoint | Purpose | SDK equivalent | |----------|---------|----------------| | `POST /trust-overrides` | Upsert a trust override | `trust.set(target, opts)` | | `GET /trust-overrides` | List overrides in scope (optional `kind` filter) | `trust.list(opts?)` | | `GET /trust-overrides/[type]/[id]` | Read one override (or `null` when none) | `trust.get(target)` | | `DELETE /trust-overrides/[type]/[id]` | Remove an override | `trust.unset(target)` | | `POST /tools/observe` | Record one tool success/failure outcome | `tools.observe(envelope)` | | `GET /tools/priors` | Read learned tool priors in scope | `tools.priors(opts?)` | | `GET /agents/calibration` | Per-agent calibration stats | (internals) | | `GET /agents/reliability` | Per-agent reliability summary | (internals) | ### Streaming (SSE) | Endpoint | Purpose | SDK equivalent | |----------|---------|----------------| | `GET /events` | State stream (records updated/stale + heartbeats) | `subscribe(handler)` / `events()` | | `POST /extraction-stream` | Per-request extraction frames | `streamExtraction(req)` | | `GET /drift/watch` | Per-agent reliability drift events | `drift.watch(handler)` / `drift.events()` | ### Conventions Preferred HTTP scope fields are `namespace` and `thread`. When an endpoint accepts contributor identity, the field name is `agentId` (not `agent`). - `POST /context`: body `{ namespace, thread?, input? }`. - `POST /ingest`, `POST /ingest/claims`, `POST /ingest/output`: body `{ namespace, thread?, agentId?, ...payload }`. - `POST /ingest-tool-result`: body `{ namespace, thread?, agentId?, toolName, toolResult, source? }`. - `POST /observe`: body `{ namespace, thread?, agentId?, content, surface?, kind?, actor?, tags?, depth?, signalFocused? }`. - `POST /apply`: body `{ namespace, thread?, agentId?, delta, source? }`. - `POST /reset`: body `{ namespace, thread? }`. - `POST /state/at`: body `{ namespace, thread?, beliefId?, agentId?, step?, traceId?, asOf? }`. - `POST /by-block/[blockId]`: body `{ namespace, thread? }` (block id is in the path; `removeWhere` only supports `block:` sources today). - `GET /records`: query `?namespace=&thread=&query=&type=&source=&lifecycle=&limit=&cursor=`. - `GET /search`: query `?namespace=&thread=&query=&limit=` (deprecated). - `GET /snapshot` / `GET /world-state`: query `?namespace=&thread=`. - `GET /detail/[id]` and the two sub-routes: query `?namespace=&thread=`. - `GET /graph`: query `?namespace=&thread=&kinds=&minConfidence=&limit=&maxContradictions=` (or use `scope.{spaceId,studioId,sessionId}` overrides on POST bodies where supported). - `GET /ledger`: query `?namespace=&beliefId=&agentId=&since=&until=&limit=`. - `GET /thinking-moves`: query `?namespace=&thread=&topN=`. - `POST /thinking-moves/generate`: body `{ namespace, thread?, beliefId, includeJustification?, sessionId? }`. - `POST /thinking-moves/[moveId]/action`: body `{ namespace, thread?, action: 'accept' | 'snooze' | 'dismiss' }`. - `POST /moves/rank`: body `{ namespace, thread?, topN?, budget?, agentId? }`. - `POST /moves/forecast`: body `{ namespace, thread?, action, depth?, rollouts?, maxTopics?, agentId? }`. - `POST /moves/forecast-cascade`: body `{ namespace, thread?, action, targetBeliefId?, magnitude?, maxAgents?, agentId? }`. - `POST /forecast/predict`: body `{ namespace, thread?, actions: string[], horizon?, rollouts?, maxTopics?, agentId? }`. - `POST /trust-overrides`: body `{ namespace, target: { kind, id }, confidence, strength, lock? }`. - `GET /trust-overrides`: query `?namespace=&kind=`. - `GET|DELETE /trust-overrides/[type]/[id]`: type is `'agent' | 'source'`. - `POST /tools/observe`: body `{ namespace, thread?, tool, success, contextClass?, weight?, agentId? }`. - `GET /tools/priors`: query `?namespace=&tool=&contextClass=&limit=&agentId=`. - `GET /events`, `GET /drift/watch`: SSE; query `?namespace=&thread=&...stream-specific options`. - `POST /extraction-stream`: SSE; body `{ namespace, thread?, content, surface? }`. Legacy aliases remain accepted for compatibility: `workspaceId`, `threadId`, and nested `scope.thread` / `scope.threadId` on POST bodies. Prefer `namespace` and `thread` for new integrations. --- More patterns: https://thinkn.ai/dev/sdk/patterns Full API reference: https://thinkn.ai/dev/sdk/core-api Integrations: https://thinkn.ai/dev/adapters/claude-agent-sdk, https://thinkn.ai/dev/adapters/vercel-ai --- # Start ## Start Here Source: https://thinkn.ai/dev/start/index Summary: thinkⁿ is world model infrastructure for agents: a durable, auditable belief state that lives outside the model. You tell your agent a fact. Eight turns later it contradicts you. A bigger context window makes this worse, not better, because now there's more text for the model to lose the thread in. The model has no place to keep what's settled, what's contested, and how sure it is. **thinkⁿ is world model infrastructure for agents: a durable, auditable belief state that lives outside the model.** An agent's world model is the frame it acts from. That frame has six parts: the **environment** it works on (a codebase, an account, a production line, a portfolio, a patient), the **observations** streaming in, the **belief state** of what's currently true and how sure it is, the **intent** the agent is after (its goals and what success looks like), the **policy** that governs the world, and the **actions** the agent can take in it. You declare the environment, intent, policy, and actions; the engine compiles observations into the belief state and projects the worldview. The belief state is the live core. It holds what's been observed (past), what's held true (present), and what move would bring the picture closer to reality (future). Beliefs are how you build that state. A belief is a claim with an explicit posterior, its evidence, and a lifecycle, and it's the unit that keeps the world model honest as reality changes. See [World](/dev/core/world) for the full frame, or the [FAQ](/dev/start/faq) for how it differs from memory, RAG, and a markdown file. ```bash npm i beliefs ``` ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'my-project', writeScope: 'space', }) // Before the agent acts: read the bounded worldview for this turn. // The argument is a relevance query (the task/turn/question at hand) // that biases which beliefs and moves surface. const context = await beliefs.before(userMessage) // Run your agent with belief context injected. // The top-ranked next move rides back as context.moves[0]. const result = await myAgent.run({ system: context.prompt }) // Feed the output back: beliefs extracted, conflicts detected, state updated. // delta.moves carries the re-ranked moves after the turn. const delta = await beliefs.after(result.text) ``` That's the loop: two calls per turn, regardless of which framework you ship on. **The read surface, once.** `before()` and `after()` are the turn loop. Each returns the worldview plus its ranked `.moves` (`context.moves`, `delta.moves`). `read()` is the full snapshot, out of band. `moves.rank({ topN })` is the standalone ranked-move accessor when you want moves without running a turn. Inside the loop, prefer `context.moves[0]` over a redundant `moves.rank()`. ## Works with your stack ```ts import { beliefsHooks } from 'beliefs/claude-agent-sdk' // Anthropic Claude Agent SDK import { beliefsMiddleware } from 'beliefs/vercel-ai' // Vercel AI SDK // React hooks + browser DevTools (coming soon) ``` Or call `beliefs.before()` / `beliefs.after()` manually around any LLM (OpenAI, plain fetch, your own agent loop). See the [Hack Guide](/dev/tutorial/hack-guide) for working recipes across frameworks. ## I want to... | I want to... | Start here | |---|---| | **Ship in 10 minutes.** Hackathon, prototype, exploration. | [Hack Guide](/dev/tutorial/hack-guide): install + framework recipes + project ideas | | **See it run end-to-end** before committing. | [Quickstart](/dev/start/quickstart): 30 lines that print clarity rising | | **Learn the model first**, then build. | [World model](/dev/core/world) → [Concepts](/dev/core/beliefs) → [Tutorial](/dev/tutorial/research-agent) | | **Build chat memory** that's separate per conversation. | [Install](/dev/start/install) → use `writeScope: 'thread'` and bind `thread: 'id'` | | **Run multi-agent shared state** (debate, supervisor/worker, swarm). | [Patterns → Multi-Agent](/dev/sdk/patterns): same namespace, `writeScope: 'space'` | | **Audit why an agent believes something.** | [How it works → Ledger](/dev/internals/how-it-works) and [`beliefs.trace()`](/dev/sdk/core-api) | | **Evaluate fit** before integrating. | [FAQ](/dev/start/faq): when beliefs help, when they don't | | **Add beliefs to a Claude Agent SDK app.** | [Adapter: Claude Agent SDK](/dev/adapters/claude-agent-sdk) | | **Add beliefs to a Vercel AI SDK app.** | [Adapter: Vercel AI](/dev/adapters/vercel-ai) | | **See it across domains** (the whole business, then engineering, research, investing, legal, support, operations, finance, and health). | [Use cases](/dev/cases/enterprise) | ## Why coding agents first A codebase is already a compact world. It has laws (types, invariants), assumptions (architecture decisions, dependencies), history (commits, PRs), ownership, and contradictions (stale docs, drifted assumptions). Coding agents are already operating inside this world, but with short-lived context and weak memory. The first world model thinkⁿ targets is the one your coding agent already lives in. Concrete beliefs the engine can hold for a repo: ``` belief: Authentication is enforced at the API middleware layer confidence: 0.82 evidence: middleware.ts, auth.test.ts, architecture.md contradicts: /api/internal/export bypasses middleware next move: inspect route-level auth coverage before modifying export flow ``` The same machinery carries across the whole business and the functions inside it: engineering, research, investing, legal, support, operations, finance, and health. Any system that has to hold a coherent picture of reality across many turns and many sources runs on the same belief state. See it worked end-to-end in [Use cases](/dev/cases/enterprise). As agents take on real work, a company needs a shared operating state for what its agents understand and decide: what's true, what changed, what's uncertain, and what action is safe. That state is the world model, owned by the company, shared across every agent it runs, and auditable when a decision has to be explained. With it, a company can scale work without scaling management. Point your agent at the SDK reference: [llms.txt](https://thinkn.ai/llms.txt). It gives the agent enough to wire up the loop correctly without guessing at the surface. ## Install Source: https://thinkn.ai/dev/start/install Summary: Configure your stack and grab the install command + boilerplate. Pick package manager, framework, and memory scope. ## Configure your install Pick your package manager, framework, and memory scope. The install command and starter snippet update as you choose. The `^0.7.0` range pulls in patches and new features without breaking changes. Pin tighter if you want. ## Get your API key 1. Log in at [thinkn.ai](https://thinkn.ai) 2. Go to **Profile > API Keys** (`/profile/api-keys`) 3. Click **Create Key**, give it a name, and copy the `bel_live_...` value 4. Add it to your environment: ```bash # .env (do not commit this file) BELIEFS_KEY=bel_live_... ``` Copy the key immediately. It is only shown once. If lost, revoke it and create a new one. ## Memory scope, in detail `writeScope` decides where new beliefs are stored, and therefore who else can read them. | Scope | What it does | Use when | |---|---|---| | `'space'` | One shared belief state per `namespace`. Every agent and conversation reads and writes the same world. | Prototypes, multi-agent collaboration, anything where shared context is the point. | | `'thread'` | Separate belief state per conversation. Bind with `thread: 'id'` or `beliefs.withThread(id)`. | Chat apps, per-user memory, sessions that shouldn't leak into each other. | | `'agent'` | Durable scratchpad per agent, isolated from other agents in the same namespace. | Background workers and tool-running agents that keep private notes. | The SDK defaults to `writeScope: 'thread'` (and requires a bound thread ID). For copy-paste verification, the configurator above starts on `'space'`, the simplest runnable setup. See [Scoping](/dev/sdk/scoping) for the full breakdown including `contextLayers`. ## Verify it's working After setting `BELIEFS_KEY` in your environment, run the snippet from the configurator (or any of the [framework recipes](/dev/tutorial/hack-guide)). A successful first call returns a `BeliefDelta` with `clarity` set, no auth errors. If you see `BetaAccessError`, your key isn't on the allowlist. Request access via the [waitlist](https://thinkn.ai/waitlist). Give your agent the SDK reference so it can write correct code on the first try: [`thinkn.ai/llms.txt`](https://thinkn.ai/llms.txt) ## Requirements - Node.js 18+ - TypeScript 5+ (recommended) The SDK is in private beta. Request access at [thinkn.ai/waitlist](https://thinkn.ai/waitlist). ## Quickstart Source: https://thinkn.ai/dev/start/quickstart Summary: The 3-step loop, plus a runnable example that prints clarity rising in under a minute. The hosted API is in private beta. [Request access](https://thinkn.ai/waitlist) to get an API key. ## The Loop Every agent using beliefs follows the same cycle: ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'quickstart', writeScope: 'space', }) ``` ```ts const context = await beliefs.before(userMessage) ``` `context.prompt` is a serialized belief brief. Drop it straight into your agent's system prompt. `context.clarity` is a 0–1 score: how much does the agent know vs. how much is still uncertain? `context.moves` are ranked next actions. ```ts const result = await myAgent.run({ context: context.prompt }) const delta = await beliefs.after(result.text) ``` The infrastructure extracts beliefs from the output, detects conflicts, updates confidence, and records provenance, automatically. `delta.clarity` tells you whether to keep investigating or act. Three steps, repeated every turn. ## Run It A 30-line script that exercises the whole loop without a real LLM. Save as `quickstart.ts` and run with `BELIEFS_KEY=bel_live_xxx npx tsx quickstart.ts`. ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY!, namespace: `quickstart-${Date.now()}`, writeScope: 'space', }) // 1. Seed a goal and a few priors. await beliefs.add('Determine the size of the AI dev tools market', { type: 'goal' }) await beliefs.add('Market is around $4B', { confidence: 0.6, type: 'assumption' }) // 2. Feed agent output. Beliefs are extracted automatically. const research = `Per Gartner 2024, the AI developer tools market is $4.2B, growing 25% YoY. GitHub Copilot, Cursor, and Tabnine account for about 65% of the market.` const first = await beliefs.after(research) console.log(`turn 1: clarity ${first.clarity.toFixed(2)}, ${first.changes.length} changes`) // 3. Feed contradicting evidence. Conflict is detected. const idc = `IDC Q4 2025 reports the market at $6.8B, not $4.2B. The earlier figure excluded embedded AI in mainstream IDEs.` const second = await beliefs.after(idc, { tool: 'market_research' }) console.log(`turn 2: clarity ${second.clarity.toFixed(2)}, contradictions ${second.state.contradictions.length}`) // 4. Read the fused world state. const world = await beliefs.read() console.log(`final: ${world.beliefs.length} beliefs, clarity ${world.clarity.toFixed(2)}`) ``` You'll see clarity move turn-over-turn, a contradiction surface when sources disagree, and the fused world state at the end. Exact numbers vary across runs (extraction is LLM-driven) but the pattern (clarity rising, conflicts detected) is stable. ## Where to next ## FAQ Source: https://thinkn.ai/dev/start/faq Summary: Straight answers on what a belief state is, how it differs from memory and RAG, and when to skip it. ## Why not just keep a markdown file the agent reads? A `CLAUDE.md`, a notes doc, a pinned system prompt: this is the real baseline, and for a single short task it is often enough. It breaks down the moment the work runs long or spans more than one agent. A markdown file is a flat blob of text. It has no confidence, so a line someone wrote once reads with the same authority as a fact verified ten times. It has no provenance, so you cannot tell who claimed something or what evidence stands behind it. It has no contradiction detection, so when two lines disagree the agent acts on whichever it read last. Nothing decays, so a note that was true six months ago still reads as current. And it is single-writer by nature: two agents editing the same file clobber each other instead of merging. A belief state is the same instinct built to be acted on. Every claim carries a posterior and its evidence, conflicting claims are surfaced and resolved by source reliability, stale evidence loses weight on a schedule, and many agents fuse into one shared, auditable picture. You still write to it in plain language through `observe()` and `after()`; the engine does the judging the markdown file leaves to you. If your agent never runs long enough to contradict itself, keep the file. When it does, that file is the bug. ## How is it different from memory or RAG? Memory systems and vector stores (mem0, LangGraph state, the Claude memory tool, RAG over embeddings) all do one thing: retrieve text that resembles a query. None of them hold a model of what is currently true. They hand back chunks and leave the judgment to you. | Dimension | Memory / RAG | Beliefs | |-----------|-------------|---------| | **What it stores** | Text chunks and vectors | Structured claims with confidence and evidence | | **Uncertainty** | None. Every chunk looks equally valid | Tracked per claim, separated from how much it has been investigated | | **Conflicts** | Returns both, or last-write-wins | Detected, tracked, resolved by source reliability | | **Multi-agent** | Per-agent stores, no shared coherence | One fused world; cross-agent contradictions become visible | | **Decay** | Falls out of context randomly | Beliefs lose strength as their evidence ages | | **Provenance** | "This chunk was retrieved" | Who stated it, what evidence, how confidence evolved | | **Unknowns** | No concept | Gaps are first-class and drive the next action | Retrieval returns what was said. A belief state returns a position on it: what holds, how sure, and what would change it. They solve different problems, and you can run both, retrieval to surface candidate text and beliefs to decide what to trust. ## How is it different from a "world model"? thinkⁿ is world model infrastructure, and the belief state is the live core of it. The world model is the whole frame an agent acts from: the [environment](/dev/core/environment), the [observations](/dev/core/observations) streaming in, the belief state over it, the [intent](/dev/core/intent), the [policy](/dev/core/policy), and the [actions](/dev/core/actions) available, projected into the bounded [worldview](/dev/core/worldview) it reads before each move. Beliefs are the part that stays current as reality changes: the claims with confidence, evidence, and lifecycle the rest of the frame is organized around. See [World model](/dev/core/world) for the full picture. ## What do you mean by the "operating state" of a business? It is the live picture a business runs on: what's true right now, what changed, what's uncertain, what conflicts, and what action is safe. Agents can already use tools and follow workflows, but they don't carry that picture, so a human keeps re-orienting them. Beliefs holds it as shared state every agent reads before it acts and writes back as work changes, so a company can scale work without scaling management. ## How is it different from an agentic framework (LangChain, CrewAI, AutoGen)? A framework orchestrates *what your agent does*: graphs of LLM calls, tool routing, multi-agent coordination, control flow. Beliefs holds *what your agent thinks*: the claims, confidence, evidence, contradictions, and gaps that persist across turns. They sit at different layers, so beliefs does not replace your framework, it wraps the loop. It works with LangGraph, CrewAI, AutoGen, the Claude Agent SDK, the Vercel AI SDK, or any custom loop. ## How is it different from LLM observability (LangSmith, Langfuse, Helicone)? Observability records what your agent did: traces, latency, token counts, replayable logs of past runs. Beliefs records what your agent currently believes. One is a backward-looking log; the other is decision-facing state. Observability answers "what happened?" Beliefs answers "what is true right now, and what should the agent do next?" Large deployments tend to run both. ## When should I not use beliefs? Skip beliefs if you do not have a coherence problem yet: - **Single-turn work.** Q&A bots, content generation, one-shot tasks. Memory or nothing is enough. - **Pure ephemeral session memory** with no cross-source claims, no evolving understanding, and no contradictions to track. - **Tasks where the agent never reasons about its own confidence or gaps.** Beliefs earns its keep when an agent runs more than a few turns on the same topic, accumulates information from multiple sources, or needs to know what it does not know. If none of those apply, use memory or skip both. ## Do I need to understand probability to use it? No. Confidence is a number between 0 and 1, and that is all you need to read it. The SDK handles the math. For the probabilistic foundations, see [How it works](/dev/internals/how-it-works). ## What happens when two claims conflict? The fusion engine detects the conflict, resolves it by trust weight, and keeps both in the trace. Nothing is silently discarded. You can read contradictions from `beliefs.read()` and decide how your agent handles them. ## Can I use beliefs without an adapter? Yes. The core `before` / `after` loop works with any framework: ```ts const context = await beliefs.before(input) const result = await yourAgent.run({ prompt: context.prompt }) await beliefs.after(result) ``` Adapters handle framework-specific plumbing, but the core SDK is framework-agnostic. ## Does performance degrade with many claims? No. The SDK prunes and decays automatically. Stale claims lose weight over time through [temporal decay](/dev/internals/how-it-works), so old, low-confidence claims do not accumulate indefinitely. ## Do beliefs persist across sessions? Yes. The hosted backend persists beliefs automatically; an API key is required. There is no local-only mode in the current release, and local persistence adapters are planned for a future version. ## Where do I get help? - **Beta support channel.** Direct access to the engineering team. - **GitHub.** [Open an issue](https://github.com/thinkn-ai/beliefs/issues) on the beliefs repo. - **Beta access.** [Request access](/dev/beta) if you are not yet in the program. --- # Concepts ## World model Source: https://thinkn.ai/dev/core/world Summary: The living picture an agent keeps of the world it works in: what's true, how sure it is, and what to do next. **A world model is the picture an agent acts from.** It is a living, structured representation of the world an agent works in: what is true right now, how sure the agent can be, what it is steering toward, what rules bind it, and what it can do next. The agent reads this picture before every move and updates it after, so each turn starts from what is actually true instead of from scratch. That is what separates a world model from a store of facts. A store answers what was said. A world model answers what is true, how confident the agent is, what would change its mind, what it can do about it, and what happens when it does. Build a memory system to act on rather than to retrieve from, and this is the shape it takes. The world model loop: an agent observes, updates its belief state, projects a bounded worldview, acts, and the action becomes the next observation. This page names the frame. Every page under it owns one part. ## The six parts you touch A world model has six parts a developer works with directly, plus a seventh the engine assembles from them. Each part answers a different question the agent asks before it acts: - **[Environment](/dev/core/environment).** *Where am I?* The world the agent operates on: a codebase, an account, a production line, a portfolio, a patient. The ground truth, plus the entities and relations that structure it. - **[Observations](/dev/core/observations).** *What just happened?* The input boundary. Everything the agent sees streams in as observations: tool results, files, messages, sensor readings, filings. This is the only way anything enters. - **[Belief state](/dev/core/beliefs).** *What's true, and how sure am I?* A graph of explicit posteriors over the environment, not an opaque latent vector. Each belief carries confidence, evidence, and provenance. Where a claim resolves against a real outcome, that confidence is calibrated against history (events labeled high resolve true at roughly that rate; see [Contracts](/dev/internals/contracts)). Where there's no outcome to resolve against, confidence reflects the balance of evidence, not a frequency. It spans three tenses: - **Past:** what's been observed, what evidence has arrived (the ledger). - **Present:** what's currently held true, with confidence and provenance (the active claim set). - **Future:** what action would move the world model closer to reality (the next moves, ranked). - **[Intent](/dev/core/intent).** *What am I after?* The goals and success criteria the current decision is measured against, plus the gaps still open. - **[Policy](/dev/core/policy).** *What must I respect here?* The rules of this world: invariants that must always hold, which sources outrank which when they disagree, the conventions of the place, and the anti-patterns to avoid. This is a modeling lens you encode today, not a constraint the engine enforces. - **[Actions](/dev/core/actions).** *What can I do?* The set of moves available in this world, each with a safety class (read-only, mutating, or needs-approval). The seventh thing is the **[worldview](/dev/core/worldview)**: the bounded projection of all six, scoped to the one move the agent is about to make. The agent reads the worldview, not the whole frame. Inside the belief state, beliefs are the unit of account, edges record how they relate, gaps are the open questions, and contradictions are where the state disagrees with itself or with new evidence. [Moves](/dev/core/moves) rank what action would close those gaps fastest; [clarity](/dev/core/clarity) scores how ready the picture is to act on. How edges are typed and how contradictions weight by downstream dependency are [Belief state](/dev/core/beliefs)'s to explain; this page just locates them in the frame. ## You declare the world, the engine runs it The split is clean. The environment, the action set, the policy, and the intents are things you encode for a domain. You name the entities and relations that matter in a codebase, lay out the actions an agent may take on an account, write down the invariants a portfolio must respect, and state the goals it is steering toward. That declaration is your world. The engine takes it from there. It maintains the belief state as observations arrive (extract, fuse, decay) and projects the worldview from that state plus your declaration. You do not hand-maintain confidences or relationships. You observe, and the posterior moves. Today, policy and the action set are how you *model* a world: a declarative surface you encode and your agent reasons over. The engine surfaces them in the worldview and tracks which actions a constraint would flag, but it does not itself gate execution. A declarative config surface that the engine enforces is on the roadmap. Until then, treat policy and actions as the structure your agent reads, not a guardrail the runtime imposes. ## The agent reads a worldview, not everything The agent never loads the whole frame at once. As a corpus grows, that would put a thousand items in front of a model that does its best work on a handful. Instead it reads a **[worldview](/dev/core/worldview)**: a decision-sized projection scoped to the move at hand. The worldview keeps what bears on the current decision and drops the rest, capped to a small working set and ranked by value to that decision. Then the agent acts. The action folds back in as the next observation, the belief state updates, and the next turn projects a fresh worldview. That is the loop: observe, believe, act, with each action becoming the next thing observed. ``` observe ──► believe ──► act ▲ │ └─────────────────────┘ the action becomes the next observation ``` ## The read side of the loop `beliefs.read()` returns the world model in full: the agent's beliefs, the relationships between them (what supports, contradicts, derives), all open gaps, active contradictions, the clarity score, the ranked next moves, and the serialized prompt. ```ts const world = await beliefs.read() world.beliefs // all beliefs the agent holds world.goals // what the agent is pursuing world.gaps // unknowns and open questions world.edges // relationships: supports, contradicts, derives world.contradictions // active conflicts that need resolution world.clarity // 0-1 readiness score world.moves // suggested next actions world.prompt // serialized context for LLM injection ``` `read()` is the full picture. For a single turn you want the slice, not the picture, and `before(query)` loads it: the decision-sized worldview for the query at hand, with the top move riding back as `context.moves[0]`. ```ts const context = await beliefs.before(userMessage) const result = await myAgent.run({ systemPrompt: context.prompt, message: userMessage, }) ``` When the turn produces output, `after(text)` folds it back into the state, closing the loop. The action becomes the next observation, and the world model gets sharper at predicting its own effects over time. ```ts await beliefs.after(result.text, { tool: 'edit_file' }) ``` The engine ranks the single next move today, and can project a move's value several steps forward once a world has enough action-to-outcome history. That forecasting layer is honest about its evidence and stays low-confidence until the history is real. [Moves](/dev/core/moves) covers it in full. ## One world model, every agent's contributions When several agents work the same world, the world model is the fused view: one picture assembled from every agent's contributions. Each agent writes under its own `agent` identifier, and the backend merges those writes into a single shared posterior. The merge is order-independent, so the same set of contributions lands on the same posterior no matter who wrote first. Order shapes the audit trail of who saw what and when; it never moves the answer. To share one fused world model, every contributing agent uses the same `namespace` (the logical problem space) and `writeScope: 'space'`, so writes land in the common store rather than per-agent or per-thread. With a single agent, the world model is just that agent's belief state. Why the merge is order-independent, and the byte-equal test and CI calibration gate that hold it there, are covered in [Belief state](/dev/core/beliefs); the namespace and scope rules are in [Scopes](/dev/sdk/scoping). ## The section map This page is the overview. Each part of the frame has its own page. The six parts above carry the frame; Worldview, Clarity, and Moves are the read-side projections of it, listed on their own here for findability rather than as a seventh, eighth, and ninth part. ## Environment Source: https://thinkn.ai/dev/core/environment Summary: The world the agent operates on: the entities and relations that structure it, declared per domain. Every agent wakes up asking the same question: *where am I, and what is this place?* A codebase. An account. A production line. A portfolio. A patient. The same engine runs all of them. What changes from one world to the next is never the engine. It is the declaration. An environment is the world the agent acts on and the structure of that world: the things that exist in it and the ways they connect. Beliefs live inside an environment, which is what gives them something to refer to. A claim about a service is a claim *about something in this world*, and the environment is what makes "something" a first-class object the agent can reason over. ## Entities and Relations Two pieces structure a world. **Entities** are the things that exist in it. A service. A customer. A position. A route. A test suite. Each is a thing the agent's beliefs can be *about*. **Relations** connect them. `depends_on`, `documented_by`, `owns`. A relation ties two entities together with a typed predicate, so "the ledger service depends on Postgres" is a structured fact, not a sentence the agent has to re-parse every turn. Both are first-class nodes in the belief graph. That choice is load-bearing. The engine stays *uncertain about its own structure*: it does not treat the entity set and the wiring between entities as fixed truth handed down from a config file. It treats them as claims it holds, with the same evidence and confidence machinery every other belief gets. The graph can be wrong about what exists, and find out it was wrong, the same way it can be wrong about any fact. ## Relations Are Claims, Not Raw Edges In most graph stores an edge is a hard link. It is there or it is not. An entity-to-entity relation here is a belief: it carries its own posterior and its own evidence. "Service A depends on service B" can be supported by what the engine has seen, refuted by later evidence, or superseded when the dependency moves. It updates like any other claim, because it *is* one. A graph edge, then, is the projected view of that relation once it clears a threshold. The relation belief accumulates evidence; when its confidence rises past the bar for its kind, it surfaces as an edge you can draw and traverse. Below the bar, the relation still exists as a claim under consideration. The edge you see is the confident slice of a richer, uncertain structure underneath. This is why a connection can disagree with itself, age out, or get reconsidered. The structure is held as belief, not asserted as fact. ## A World Is Declared Per Domain A world is declared once, per domain. The declaration names three things: - its **kind** (what sort of place this is), - its **entity types** (what kinds of things exist here), and - the **relation kinds** it supports (which predicates connect them). The engine reads this declaration to route extraction and to shape the graph. When an observation comes in about a codebase, the declaration tells the extractor that services and modules are entities worth pulling out, and that `depends_on` is a relation worth drawing. Point the same engine at a portfolio and the entity types become positions and instruments, the relations become `hedges` and `correlates_with`. Nothing in the engine changes. Adding a world is shipping a declaration. It is not editing the engine. That is the whole design: the inference machinery is fixed and shared, and a domain is a file that tells that machinery what its world is made of. The set of entity types and relation kinds a world supports is the lever a developer pulls to teach the engine a new domain. The math, the fusion, the calibration, and the projection are constant across every world. Domain knowledge enters as the declaration, not as forked engine code. ## Ground Truth and the Belief Over It The environment has a real state, whether or not anyone has observed it. The dependency graph of a codebase exists as it actually is. Call that ground truth: the latent state of the world. The belief state is the agent's posterior over that latent state. The agent never sees ground truth directly. It sees observations, and from them holds an estimate of what is true, calibrated against outcomes where a claim resolves and balance-of-evidence where it does not. The environment is the thing being estimated. The belief state is the estimate. When an agent orients, the environment slice of its worldview surfaces what it needs to know before it reasons: the world id, the world kind, the entities currently in play, and the source-of-truth ranking (which sources outrank which when they disagree). That slice answers *what am I looking at* so the rest of the orientation, what's true now and what to do next, has a place to stand. ## Reading Relations Back from the Graph `beliefs.graph()` returns the environment as a graph: the entities and the typed relationships between them. Each relationship carries confidence rather than a flat link, so the view tells you how strongly the evidence backs a connection, not only that the connection exists. ```ts const projection = await beliefs.graph({ filter: { kinds: ['claim', 'goal'], minConfidence: 0.4 }, }) for (const node of projection.nodes) renderNode(node) for (const edge of projection.edges) { // edge.confidence is the posterior on the relation, not a 0/1 link renderEdge(edge) } ``` The `minConfidence` filter drops edges below a confidence floor, which is the threshold idea made concrete: ask for the confident structure and the low-evidence relations stay out of the view. The full belief state, including the relations still under consideration, is always there to read; the graph projection is the slice you draw. For the broader read shape, `beliefs.read()` returns the entities and edges alongside the rest of the world model (beliefs, gaps, contradictions, moves), so the environment is never separated from the claims that live in it. ## Observations Source: https://thinkn.ai/dev/core/observations Summary: The input boundary: how evidence enters the world model, and how the agent's own action becomes the next observation. Every belief in the world model entered through an observation. Tool results, files, diffs, messages, command output, docs, logs, filings, a piece of evidence the user hands over by hand: all of it crosses this one boundary, and nothing reaches the posterior without crossing it. This is the seam where raw, unstructured input becomes typed beliefs the engine can reason over. The boundary also fixes what you are on the hook for. You hand the engine the artifact. It reads the artifact, extracts the typed beliefs inside it (claims, goals, gaps, evidence, contradictions), and folds them into the graph by Bayesian fusion. You never construct a pre-scored belief list. You submit the artifact, and the engine decides what it means. ## What an Observation Is An observation is unstructured by default. A paragraph of user text, a tool's stdout, a file diff, a PR description: the engine takes the raw payload and does the work of turning it into structure. That work is extraction. An LLM reads the artifact in the context of the conversation, emits typed belief events, and fusion merges those events into the existing graph. The split is deliberate. The developer owns the content; the engine owns the extraction and the labeling. You do not decide that a sentence is a "risk" at confidence 0.7. You submit the sentence, and the engine assigns the type, sets the posterior from the balance of evidence, and links it to what is already known. ## Two Ways In Two SDK paths submit an observation. They differ in how much control you want over what enters and where it came from. `after(text, { tool?, source? })` folds an agent turn's output back into the world model. It is the natural close of the agent loop: the agent produced something, and `after()` hands that result to extraction so the next turn opens against an updated state. ```ts const context = await beliefs.before(userMessage) const result = await runAgent(context.prompt) const delta = await beliefs.after(result.text, { tool: 'web_search' }) ``` `observe(envelope)` takes a structured artifact when you want explicit control. It carries the engine's provenance vocabulary directly (`surface`, `kind`, `actor`, `tags`), so a non-agent surface (a UI event, a document edit, a webhook, a hook) can say exactly what entered and where it came from without smuggling metadata through a free-text `source` string. ```ts await beliefs.observe({ content: 'CI failed on main: auth-middleware.test.ts timed out after 30s.', surface: 'tool', kind: 'ci_result', actor: 'system', tags: ['repo:api', 'suite:auth'], }) ``` Both run the same extraction pipeline and both fold into the same fused state. `after()` is the agent loop's verb; `observe()` is the structured-ingest verb for everything else. ## Dedup Is Part of Intake Raw extraction is noisy in a specific way: an LLM reading one artifact routinely emits several near-duplicate phrasings of the same claim. Left alone, the engine would treat each phrasing as independent support and inflate the posterior. So dedup runs inside intake, before fusion. The engine collapses near-duplicates so one source of evidence produces one update. The line it draws is the one most systems miss. The same claim restated three ways is one source, not three. Three documents that independently confirm a claim should move the posterior; one document that says the same thing in three sentences should not. Dedup enforces that distinction at intake, which is what keeps the fusion math downstream honest about how much evidence actually arrived. Submitting the same artifact twice does not double the evidence. Dedup at intake plus idempotent fusion means a repeated observation converges to the same state rather than compounding it. ## The Act, Then the Effect An observation can carry the action the agent just took. That single field is the seam that lets the engine attribute a later belief change back to the action that caused it. The loop is short. The agent fires an action. The next observation rides in carrying the action that preceded it. When that observation shifts the belief state, the engine holds both halves: the move, and what moved because of it. The agent's own action has become evidence, and over enough turns the model learns how this particular world tends to respond to that particular action. ```ts // The agent acts, then folds the effect back in. const result = await runMigration() await beliefs.after(result.log, { source: 'migration-runner' }) // The action the agent just took rides with this observation, // so a later confidence shift can be traced to that migration. ``` This is what puts record-now, forecast-later within reach. Every action and the effect that followed it land as a replayable episode. The forecasting layer runs on exactly that history: before it can project a move's value forward, it has to have watched enough real act-to-effect pairs to know how the world responds. Those pairs get recorded here, at the observation boundary, one turn at a time. The act-to-effect episodes the forecasting layer runs on are recorded here, at the observation boundary. The projection itself, and its honesty about a cold start, lives in [Moves](/dev/core/moves). ## Where This Sits Observations are the input edge of the world model. They feed the [belief state](/dev/core/beliefs), which the [worldview](/dev/core/worldview) projects into a decision-sized read, which [moves](/dev/core/moves) rank into next actions. Close the loop, and the action you take becomes the next observation. ## Belief state Source: https://thinkn.ai/dev/core/beliefs Summary: The graph of explicit posteriors over the environment: the belief as the unit, with confidence, evidence weight, edges, and lifecycle. The belief state answers one question for the agent: *what is true here, and how sure am I?* The other parts of the world model (the [environment](/dev/core/environment), the [intent](/dev/core/intent), the action set) frame the decision. The belief state holds the agent's read on reality while it makes that decision. It is a graph of explicit posteriors over the environment, not an opaque latent vector. The belief is the unit of account: each one records what the agent holds true and why, so a peer-reviewed citation and a guess from three turns ago carry different authority instead of reading as equally settled. Retrieval hands both back the same way; the belief state weighs which one still holds. The state lives outside the model. It survives the context window, stays inspectable, and lets you read it, write to it, and trace any belief back to the observations that produced it. ## What a belief is A belief carries text, a type, an explicit posterior over its truth, an evidence weight, and an optional label. ```ts { id: 'belief_auth_middleware', text: 'Authentication is enforced at the API middleware layer', type: 'claim', confidence: 0.82, evidenceWeight: 4, label: 'load-bearing', createdAt: '2026-04-15T10:30:00Z', // Tracked alongside the belief: // evidence: middleware.ts, auth.test.ts, architecture.md // contradicts: /api/internal/export bypasses middleware // next move: inspect route-level auth coverage before touching export } ``` - **text.** The natural-language assertion. - **type.** What kind of belief: `claim`, `assumption`, `risk`, `evidence`, `gap`, `goal`. - **confidence.** A 0-1 posterior: where the balance of evidence currently sits, a best estimate that moves as evidence arrives rather than a verdict. - **evidenceWeight.** How much evidence stands behind the belief. `0` means stated but uninvestigated; higher means corroborated. - **label.** A richer category: `risky-assumption`, `load-bearing`, `limiting-belief`, and so on. The engine assigns the type and label during extraction. You can also set them by hand via `add()`. The example comes from a coding agent's world model, a repo, but the shape is fixed across domains: a research agent's read on market size, an analyst's call on churn, a finance agent's position on a book. Only the content changes. ### Belief types | Type | Gloss | Use | |------|-------|-----| | `claim` | An evidenced assertion | Supported or refuted by collected evidence | | `assumption` | An untested supposition | Held true without direct evidence yet | | `risk` | A potential negative outcome | Something to hedge against | | `evidence` | A data point or source | Backs or refutes other beliefs | | `gap` | A known unknown | Flagged as unresolved | | `goal` | A pursued outcome | What the agent is trying to accomplish | `goal` and `gap` share this store for convenience, but they are firewalled from the posterior math: neither carries a probability, and setting out to prove a goal is not evidence for it. They are [intent](/dev/core/intent), and they live on a separate track. ## Two channels, not one number A belief carries two readings, and they answer different questions: - **confidence** is where the balance of evidence currently sits, on a 0-1 scale. - **evidence weight** is how much evidence stands behind that reading. A claim at 50 percent with no evidence and a claim at 50 percent with forty split observations are different situations. The first is an open question nobody has touched. The second is a genuine standoff where the evidence is real and contested. A single number collapses both into one ambiguous middle. Two channels keep them apart, so the agent can chase the first and adjudicate the second. ## How confidence moves How far confidence moves on a new observation depends on the quality of that observation. A verified measurement moves the posterior more than several inferences. Corroboration is a likelihood, not a vote: ten paraphrases of one source do not count as ten sources. The direction of each piece of evidence sets which way the posterior goes: - **supports** raises the posterior. - **refutes** lowers it. - **neutral** adds weight without shifting the estimate. When a research agent finds a Gartner report backing "Market size is $4.2B," the posterior goes up. An SEC filing with a smaller number pushes it back down. Both are kept. The disagreement is not averaged away; it surfaces as a contradiction (wider uncertainty, flagged explicitly) rather than papered over. ### Evidence quality Different evidence types carry different weight, so quality counts for more than repetition: | Type | What it is | |------|------------| | `measurement` | Audited metric, verified data point | | `citation` | Research report, external source with provenance | | `user-assertion` | User explicitly stated this | | `expert-judgment` | Expert opinion with rationale | | `inference` | Agent-derived from available data | | `assumption` | Explicit assumption, no supporting evidence | ## Edges, gaps, and contradictions Beliefs relate to each other, and those relations are the graph. | Edge type | Meaning | |-----------|---------| | `supports` | Evidence or reasoning that backs a claim | | `contradicts` | Direct conflict between two beliefs | | `supersedes` | A newer belief replaces an older one | | `derived_from` | One belief was inferred from another | | `depends_on` | A conclusion that rests on an assumption | Edges are themselves claims with their own posteriors, so the graph carries uncertainty about its own structure: a `depends_on` link can be strong or tentative, and the confidence on it says which. **Gaps** are known unknowns the agent has flagged: the open questions, weighted by stake against uncertainty. **Contradictions** are where the state disagrees with itself or with new evidence. Rather than collapsing to whichever side an agent happened to read last, a contradiction widens uncertainty and is surfaced as a first-class object the agent can act on. ## Staleness Old evidence weighs less than new under an explicit decay schedule: a half-life. An observation's contribution drops to half its weight after one half-life, so a stale fact and a fresh one do not read identically. When support has decayed past the floor, the belief is flagged stale rather than silently trusted. Freshness carries into the reading itself, so the agent knows when a claim is coasting on aging evidence. ## Provenance Provenance is first class. Every belief traces back to the observations that produced it, so *why do we believe this, who said it, when, and how sure were they* is answerable. ```ts const chain = await beliefs.trace('belief_auth_middleware') ``` `trace(id)` walks that chain: the evidence behind the belief, the sources, and how confidence moved over time. `get(id)` returns a single belief by id, and `list()` returns the active set. The full read shape (beliefs, edges, gaps, contradictions, and ranked moves) comes back from `read()`; see the [world model](/dev/core/world). ## Seeding a belief by hand When you already hold domain knowledge, assert it directly: ```ts await beliefs.add('Market size is $4.2B', { confidence: 0.85, type: 'assumption', }) ``` The `confidence` you pass is a weak prior. It seeds the posterior, and the first corroborating or refuting evidence starts moving it from there. Manual assertions feed the same fusion pipeline as extracted ones, so a hand-entered claim and one the engine lifted from an artifact reconcile under the same rules. Most beliefs arrive automatically: pass an artifact to [`observe()`](/dev/core/observations) or an agent's output to `after()`, and the engine extracts and folds them in for you. ## Intent Source: https://thinkn.ai/dev/core/intent Summary: Goals and success criteria, the is/ought firewall, and how moves rank against what the agent is after. ## What Intent Is Beliefs say what is true. Intent says what the agent is trying to make true. The two never share a track, because the moment a want can edit a fact, the agent starts believing whatever it most wants to be the case. Intent holds the agent's goals and unknowns: the job it is steering toward, the boundaries it has to respect, and the questions it has not answered yet. "Map the competitive landscape" is intent. It sets a direction and cannot be true or false. "The market is $4.2B" is a belief. Evidence can support it or refute it. A memory system files both as text and treats them alike. thinkⁿ keeps them apart on purpose, because they answer different questions and obey different rules: evidence moves beliefs, and nothing else does. ## Goals carry success criteria A goal declares what done looks like. Alongside the text, a goal carries its **success criteria**: the predicates that say when it is met. That is what lets the agent tell "still working" from "finished" without a human in the loop. ```ts await beliefs.add('Ship the v2 pricing page', { type: 'goal' }) await beliefs.add('Identify top 3 market opportunities', { type: 'goal' }) ``` Goals drive action selection. They tell the system what to answer and which gaps are worth closing. What they do not do is carry a posterior. A goal holds no probability and never touches the evidence math. Pursuing a hypothesis is not evidence for it, so an agent should not grow surer of a claim just because it set out to prove that claim. ## Constraints A constraint declares what must or should hold while the agent works. Some are hard boundaries it may not cross; others are soft preferences it should honor when it can. A constraint is a goal-typed belief: it states something the agent ought to keep true, not something evidence can refute, so it rides the intent track alongside the goals it bounds. ```ts await beliefs.add('Must stay SOC2-compliant', { type: 'goal' }) await beliefs.add('Prefer open-source dependencies', { type: 'goal' }) ``` Whether a constraint is hard or soft lives in your world's [policy](/dev/core/policy), not in a flag on the call. The same `goal` type carries both; policy is what tells them apart. When a worldview is projected, an action that runs into an active hard constraint comes back `flagged` with the constraint named, and an action a constraint calls out by name comes back `blocked`. Either way the agent sees the conflict before it acts, not after. That gating is a modeling lens you encode in the world definition today. There is no separate `severity` argument on `add`, and there will not be one: the hard-or-soft distinction is policy's job, not the call site's. ## The is/ought firewall Everything on this page rests on one rule: evidence updates beliefs, and preferences and goals never do. Facts enter through one door and move the posterior. Wants and oughts enter through another, get recorded as intent, and stay out of the evidence math entirely. | Input | Type | Effect | |-------|------|--------| | "The TAM is $5B" | Factual | Updates the market-size belief | | "Gartner reports 34% growth" | Factual | Updates the growth-rate belief | | "I want to target enterprise" | Normative | Recorded as a goal | | "We must support SOC2" | Normative | Recorded as a constraint | Two stated facts move the posteriors. Two normative inputs steer the work without touching a single probability. That is the firewall. Drop it and the failure mode arrives fast: every "I want X" nudges the posterior toward X being true, and the loudest stakeholder wins regardless of the evidence. Keep it and a strong preference decides what the agent pursues while the balance of evidence stays honest about what is actually so. A user repeating "I want X" would, in a naive system, inflate the agent's confidence that X is *true*: preference masquerading as evidence. The firewall holds factual claims and normative intent apart, so conviction steers the work but never distorts what the agent believes. ## Gaps: named unknowns, made first class A gap is something the agent has not investigated or cannot answer yet. Most systems treat ignorance as silence. thinkⁿ makes it an object you can read, rank, and act on. ```ts await beliefs.add('No data on enterprise pricing models', { type: 'gap' }) await beliefs.add('Missing APAC market analysis', { type: 'gap' }) ``` Gaps are what turn a world model into a research plan. An agent that knows what it does not know can decide where to look next instead of guessing. Each open gap pulls [clarity](/dev/core/clarity) down, and high-dependency gaps (the ones many beliefs hang on) pull harder, so the most consequential unknowns rise to the top on their own. When the agent has answered one, `resolve()` marks it closed; it accepts the gap's text or its id. A resolved gap stops pulling clarity down and updates the world model. ```ts await beliefs.resolve('Missing APAC market analysis') ``` ## Intent is what ranks moves Goals and gaps do real work. They are the criterion the next-move surface is scored against. Out of all the open questions, the **limiting factor** is the single one most in the way of the goal, and the [moves](/dev/core/moves) the agent sees are ranked by how much each would carry it toward what it is after. The destination decides what is worth doing next. Goals and gaps ride back on every orientation read, so the agent opens each turn already knowing what it is chasing and what it is missing: ```ts const world = await beliefs.read() console.log(world.goals) // ['Ship the v2 pricing page', ...] console.log(world.gaps) // ['No data on enterprise pricing models'] const ctx = await beliefs.before('draft the pricing tiers') console.log(ctx.moves[0]) // the top-ranked next move, given the goal ``` ## You declare intent; the engine reads it Goals, success criteria, and constraints are part of the world you declare for the domain. The engine reads them to scope the worldview and rank moves. It does not invent goals or decide what counts as done. You set the destination and the boundaries. The engine keeps them honest against the evidence and turns them into a ranked plan. ## Policy Source: https://thinkn.ai/dev/core/policy Summary: The rules of the world: invariants, source hierarchy, conventions, and anti-patterns, and how policy weighs conflicting sources. ## What Policy Is Before an agent acts, it needs an answer to a question most agents never ask: what must I respect here? Policy is that answer, written down. It holds the rules of the world, declared once per domain and read on every turn. A codebase has invariants that must never break. A finance book has filings that outrank a stray Slack message. A research domain draws a line between a primary source and a secondhand summary. Same engine, different rules, and policy is where the developer states them. The belief state tells the agent what is true. Intent tells it what it is after. Policy draws the boundaries the work happens inside. Drop it, and the agent treats every source as equally credible and every action as equally safe to take, which is how a model talks itself into shipping the wrong thing with full confidence. Policy comes in four buckets, all of them plain natural language: - **Invariants.** Properties that must always hold. "Migrations are reversible." "Every read goes through the scope check." "Net debt reconciles to the balance sheet." - **Source hierarchy.** Which channels outrank which when they disagree, ordered from most authoritative down. - **Conventions.** How work is done here. "Prefer composition over inheritance." "Cite the filing, not the press release." - **Avoid.** Named anti-patterns to steer clear of. "No raw SQL in route handlers." "Don't quote forward-looking guidance as settled fact." ## The Source Hierarchy Of the four, the source hierarchy is the one that earns its keep when evidence collides. Two sources say different things about the same claim. Average them and you land on a confident nothing. The hierarchy says which one to lean on. A code diff outranks a chat message on a claim about what the code does. An audited filing outranks an analyst's inference on a claim about the numbers. The developer writes the ordering once; the world model applies it every time those channels disagree. ```ts { sourceHierarchy: [ 'audited filing', 'primary document', 'analyst inference', 'chat message', ], } ``` This is how the world model adjudicates disagreement instead of papering over it. A flat store hands back every source at the same weight and leaves the agent to re-litigate credibility on every read. The hierarchy settles it once, in the open, where you can read the ordering, hand it to a teammate, and change it. ## Policy Is Guidance, Not a Score Each bucket is human-readable text the developer writes for the domain. No opaque weighting matrix sits behind it, no learned credibility model the agent has to trust on faith. The agent reads the rules as prose and reasons over them, the way a new engineer reads the team's conventions doc on day one. That legibility is the whole point. You can see exactly what the world model treats as a rule, and edit it in one place. When the agent opens a turn, the policy slice of its worldview surfaces all four buckets at once, so it acts already knowing the invariants it can't break, the sources it should weight, the conventions it should follow, and the patterns to avoid. ```ts const { prompt, beliefs, goals, gaps, clarity, moves } = await beliefs.before(userMessage) // The worldview the agent reads carries the policy slice: // invariants, source hierarchy, conventions, avoid. ``` ## Where Policy Meets the Action Set Policy does more than weight evidence. When a worldview is projected, the declared rules run over the action surface and stamp each affordance `allowed`, `flagged`, or `blocked`, with the rule that did the stamping named alongside. An action that trips an active hard constraint comes back `flagged`, the constraint named so the agent knows why. An action a constraint calls out by name comes back `blocked`. Everything else stays `allowed`. So the agent never reads a flat list of verbs; it reads a list already filtered by the rules of the world, each flag carrying its reason. That is the moment policy stops being a note in the prompt and becomes part of the choice. [Actions](/dev/core/actions) covers the action surface itself, its safety classes, and the gating rules in full. ## What Ships Today, and What Doesn't Here is the honest boundary. Policy is a modeling lens the developer encodes in the world definition. It shapes how the worldview is projected and how moves and actions are framed to the agent. It is declarative config: text the developer authors, that the engine reads and surfaces. It is not engine-enforced. The engine does not halt a `mutates` action because an invariant forbids it; it surfaces the rule and the flag, and the agent (or the code wiring the agent) decides what to do with that. An enforced policy surface, where rules are checked and gate execution directly, is on the roadmap, not shipped. There is no `policy.define()` call to reach for, because policy lives in the world definition rather than a runtime registry. Treat the four buckets as the contract the agent is asked to honor, and write your own enforcement around the flags until the declarative surface lands. ## One World, One Set of Rules Because policy is per domain, one engine runs a codebase, a finance book, and a research domain on rules that share nothing. Nothing in the engine presumes what a credible source is or what an invariant looks like. The developer declares it for each world, and the world model adjudicates conflicts and frames actions against exactly those rules. Change the domain, change the policy, and what the agent respects changes with it. ## Actions Source: https://thinkn.ai/dev/core/actions Summary: What the agent can do here: a safety-classified action surface, the predict-then-verify shape, and the act-then-record loop. ## What Actions Are Most agents decide what to do by reading the room. A prompt mentions a deploy, and somewhere downstream the model talks itself into shipping. Actions close that gap. A belief state tells the agent what is true; intent tells it what it is after; actions answer the next question in the frame: *what can I do here?* They are the verbs available in this world, declared up front, so the agent chooses from a known set instead of improvising whatever the context window seemed to license. An action is an **affordance**: a verb the agent may choose in this world. Commit. Deploy. Gather evidence. Open a position. Each one carries a description the agent reasons over when it picks among them, so the choice is grounded in what the action does rather than in what a sentence implied it could do. That is the difference between a world model and a chat loop. A chat loop acts on intentions inferred from prose. A world model acts over a declared surface where every verb has a name, a safety class, and a predicted effect. ## Safety Classes Every affordance carries a safety class. It is the first thing a developer declares about an action and the first thing the engine reasons over when it surfaces the set. | Safety class | Meaning | |--------------|---------| | `info` | Read-only. Looks at the world, changes nothing. | | `mutates` | Changes state. A commit, a write, a position. | | `needs-approval` | Gated. Requires a human on the loop before it runs. | The point of the class is that the agent decides over a set that is already sorted by consequence. A read-only lookup and a production deploy are not the same kind of choice, and the agent should never have to infer the difference from tone. An `info` action is free to take. A `mutates` action carries weight. A `needs-approval` action stops and waits for a person. Declaring the class is how a developer makes that distinction structural instead of hoping the model picks it up. ## Preconditions and Effects An affordance can declare two more things, and together they give it the shape of a real action model: - a **precondition**: when the action applies, the gate that says this verb is even a candidate right now. - an **effect**: the predicted state change, what the world should look like after the action fires. The effect earns its keep twice. Going in, it is the agent's expectation; coming out, it is the verification target. Predict what the action should do, fire it, then check that the world actually matched. That predict-then-verify shape is what lets the agent catch a deploy that "succeeded" while the thing it was supposed to change never changed. An action with a declared effect is an action you can hold accountable. ```ts // Conceptual: an affordance, as the world declares it. { id: 'deploy', description: 'Ship the current build to production', safetyClass: 'needs-approval', precondition: 'tests are green and the build artifact exists', effect: 'the production version matches the current commit', } ``` ## Actions Are Policy-Gated Before the Agent Sees Them The action surface the agent reads is not the raw set. It is the set after [policy](/dev/core/policy) has run over it. An affordance that violates an active rule shows up flagged or blocked, with the rule that flagged it named, so the agent is choosing from a surface that already respects the constraints of the world. The gating is structured, not a matter of taste: - a `needs-approval` action is always flagged for a human. - a `mutates` action is flagged when a hard constraint is active. - an action whose name appears in a constraint is blocked outright, and the blocking rule rides along as the reason. A policy that advises and a policy that constrains are not the same thing. Advice sits in the prompt hoping to be honored. Constraints reshape the action set before the choice is ever made, and the agent sees exactly which rule narrowed its options and why. ## The Act-Then-Record Loop The agent acts; the engine records. When the agent fires an action, that action rides back as part of the next [observation](/dev/core/observations), stamped onto the loop as the thing that just happened. This is the act-then-effect seam: the move the agent made becomes evidence about how this world responds. Over many turns, that is how the world model learns its own dynamics. Each recorded action sits next to the belief change that followed it, and the engine accumulates a picture of how each action tends to move this particular world. The forecasting layer reads that history. ```ts // The action the agent took rides back in as the next observation, // so the engine can attribute the belief change to the move that caused it. await beliefs.observe({ content: 'Deployed build 4f2a to production', surface: 'tool', kind: 'agent_action', actor: 'system', tags: ['build:4f2a'], }) ``` That recorded history is what the forecasting layer reads to project an action's value a few steps forward. It lives in [Moves](/dev/core/moves), and stays low-confidence until the act-to-effect history is real. ## Affordances Are Not Moves Two surfaces in the world model both look like "things the agent could do," and they are not the same. - **Affordances** are the *declared action surface*: the verbs this world supports, classified by safety, gated by policy. The developer encodes them as part of defining the world. - **[Moves](/dev/core/moves)** are the engine's *VOI-ranked epistemic next steps*: where to look to reduce the most uncertainty, ranked by expected information gain off the belief state. Affordances are about acting on the world. Moves are about sharpening the picture of it. The top move rides back on the turn result as `context.moves[0]`; the affordances come back on the world model as the action surface for the decision at hand. Most agents read both each turn: the move points at the open question, the affordances say what the agent is allowed to do about it. The action set is a modeling lens the developer encodes when defining the world. The engine surfaces affordances, gates them through policy, ranks them, and records their effects so the world model can learn its own dynamics. It does not execute arbitrary side effects on your behalf. There is no `actions.register()` call. You declare the action surface as part of the world definition, your agent fires the verbs in your own runtime, and you record what happened back through `beliefs.observe`. The declarative config surface for policy and actions is on the roadmap; today it is the lens you encode, not a contract the engine enforces. ## Worldview Source: https://thinkn.ai/dev/core/worldview Summary: The bounded, decision-sized projection the agent actually reads to act: deterministic, auditable, and capped. The belief state is the full posterior over a world. The worldview is the part of it that fits in front of a single decision. It answers one question: what is the minimal sufficient picture for the move I am about to make? The belief state grows for years. The worldview stays small on purpose, because the thing reading it has exactly one move to make right now. A worldview is the read contract of the world model. Everything upstream (observations, extraction, fusion) exists to keep the posterior honest. The worldview is what the agent reads off that posterior the instant before it acts: `beliefs.before(query)` renders it into `context.prompt`, scoped to the move at hand. For the full picture rather than the turn slice, `beliefs.read()` returns the whole world model (see [World model](/dev/core/world)). ## Six slices, six questions An oriented agent asks the same six questions every time it wakes. The worldview is organized as one slice per question, so the agent never has to assemble its bearings from scratch. - **environment.** *Where am I?* The world identity and kind, the entities in play, the observation surfaces that fed the state, and the source ranking that holds here. - **current.** *What is true right now?* The load-bearing claims, recent changes, open gaps, and active contradictions, each carrying derived flags: stale, stable, contradicted, uncertain. - **intent.** *What am I after?* Goals with their success criteria, the constraints that bound them, and the single limiting factor: the one open belief most in the way of the goal. - **policy.** *What must I respect here?* The invariants, source hierarchy, conventions, and anti-patterns declared for this world. - **affordances.** *What can I do?* The world's action set, already filtered through policy and tagged with a safety class, surfaced as `allowed`, `flagged`, or `blocked`. - **moves.** *What next?* The ranked next moves, each anchored to the specific belief its value would inform. Those six slices line up with the six declared parts of the [world model](/dev/core/world): environment, intent, and policy carry straight over; observations fold into `current`; actions become policy-filtered `affordances`; and the belief state fans into `current` plus the ranked `moves`. The SDK hands them to the agent through `before(query)`: the serialized `prompt` the model reads, plus structured fields to route on. The top-ranked move rides back as `context.moves[0]`, so the agent opens every turn already pointed at its highest-value next step. ```ts const context = await beliefs.before(query) context.prompt // the worldview, serialized for the model to read context.beliefs // the load-bearing claims in play context.goals // what we're after context.gaps // open questions context.clarity // 0-1 readiness: is there enough to act yet context.moves // ranked next moves; context.moves[0] is the top one ``` ## Bounded on purpose A worldview does not grow to a thousand items as the corpus grows. It is held to a small working set (think single digits, not pages), it drops items whose value falls below a relative cut against the leader, and it keeps only enough state to settle the move at hand. This is a design commitment, not a side effect of truncation. The reason is empirical. Unbounded context measurably hurts agents: the more marginal material you pour into the window, the worse the agent gets at finding the thing that matters. A tight projection is the fix. The worldview sizes itself by relevance to the decision, not by how much happens to be in the store, so the picture stays the same shape whether the belief state holds fifty claims or fifty thousand. The cut is relative, never a tuned threshold. The projection orders claims by their decision value, keeps the prefix that stays within a fraction of the top score, floors it so the agent always sees at least the top hinge, and caps it so the surface never sprawls. What survives is the set that actually changes the next move. ## Deterministic and auditable Same inputs, same worldview. The projection reads a clock that is passed in rather than sampled, so a given belief state and world produce a byte-identical worldview every time. That is what makes the read-side replayable. Every selected item carries a `trace`: the belief ids that produced it. A load-bearing claim traces to itself plus the dependencies whose movement propagates through it. A contradiction traces to both endpoints. A goal traces to the beliefs that ground it. A flagged affordance traces to the constraint that flagged it. Any leaf in the worldview can be walked back to the beliefs that put it there, which means an agent's decision is auditable from the surface it read, without reaching back into the store behind it. The prose an LLM consumes is a render of this typed projection, never the source of it. The structured worldview is computed first and deterministically; the natural-language briefing is a downstream view. You can pin behavior on the typed object and trust that the prose follows it, rather than parsing prose to recover what the agent saw. A retrieved chunk tells you what matched a query. A worldview leaf tells you which beliefs produced it. That `trace` is the difference between a result you have to trust and one you can replay. ## It computes no new math The worldview is a selection over the belief state, not a second opinion on it. Every signal it ranks and flags by is already defined elsewhere in the engine: - **load-bearing influence** orders the claims and sizes the working set. - **fragility** decides which beliefs read as stable versus exposed. - **the decay schedule** decides which beliefs read as stale. - **claim-truth uncertainty** decides which read as uncertain. Nothing here re-scores a belief or invents a number. The projection picks, orders, and bounds; the values it reports are the same ones the belief state already holds. This keeps the read side cheap and keeps the math in one place: change how fragility or decay works once, and the worldview reflects it without a parallel implementation drifting out of sync. ## Where a worldview parts ways with retrieval A memory system hands back retrieved chunks scored by a query: relevant to what you asked, in whatever quantity matched, with the burden of interpretation left to you. A worldview hands back a bounded, typed, ranked picture scoped to a decision, the same way every time, with provenance attached to each item. The worldview is judgment shaped for a single move: not the documents that mention your topic, but the claims, gaps, conflicts, and next steps that actually bear on what you are about to do. ## The forward-looking move As action history accumulates, the worldview can carry one more thing: a forward-looking recommendation, a projection of which action has the most value from here, ranked by expected goal-information gain. Until that history is real the worldview still ranks the single best next move, and the multi-step projection rides on top as an enrichment, never a claim the engine cannot yet back. [Moves](/dev/core/moves) covers the forecasting layer and how it earns trust. The policy and affordance slices are how a developer encodes the rules and verbs of a world today: they shape what the worldview surfaces and how affordances are flagged. The engine does not yet enforce policy or execute actions on your behalf. A declarative config surface for that is on the roadmap. ## See also ## Clarity Source: https://thinkn.ai/dev/core/clarity Summary: One score that answers whether your agent has enough to act. ## The Job The world model exists to answer one recurring question: investigate more, or move? Every agent loop hits that fork. Lean too far one way and the agent acts on a hunch. Lean the other way and it researches a question it already answered. Clarity is the single 0-1 number that settles the fork, read straight off the belief state. Low clarity means keep investigating. High clarity means there is enough on the table to act, even when the call itself is hard. Clarity is that judgment compressed into one number your loop can branch on. ## The Two-Channel Insight Take two claims, both sitting at 50%. One was typed once and never tested, a coin flip nobody touched again. The other has forty observations behind it that genuinely split down the middle. Same number, opposite situations. The first needs research. The second needs a decision. A scalar confidence cannot tell them apart, so the belief state tracks two channels: ``` ┌────────────────────────────────────────────────────────────────┐ │ THE TWO QUESTIONS │ │ │ │ 1. DECISION RESOLUTION ("Can we make a call?") │ │ 80% yes, lean toward it │ │ 50% no, it is ambiguous │ │ 99% strong signal │ │ │ │ 2. KNOWLEDGE CERTAINTY ("Have we done the work?") │ │ just stated no evidence yet │ │ 10 obs some certainty │ │ 100 obs high certainty in the assessment │ │ │ └────────────────────────────────────────────────────────────────┘ ``` Decision resolution is where the posterior sits, how far the balance of evidence has moved off ambiguous. Knowledge certainty is how much evidence stands behind that position. The two move independently, and that independence is the whole point. ### The Four Quadrants ``` Knowledge Certainty Low High ┌─────────────────┬─────────────────┐ High │ Belief without │ Validated │ Decision │ evidence. │ belief. │ Resolution │ > Investigate. │ Ready to act. │ ├─────────────────┼─────────────────┤ Low │ No idea. │ Genuinely │ Decision │ Start from │ uncertain. │ Resolution │ scratch. │ > Decide, │ │ │ do not │ │ │ research. │ └─────────────────┴─────────────────┘ ``` The bottom-right quadrant is the one most systems miss. "We did the work, and it is a genuinely close call" is a conclusion, not a failure. The right response is to help the user decide, not to send the agent back to research a question already settled as uncertain. Knowing you do not know is categorically different from not knowing. The two-channel model is what lets thinkⁿ tell the two apart. ## What Clarity Folds In Clarity is a scalar aggregate. The two primary channels carry it: decision resolution and knowledge certainty are what the score is built to summarize. Two further reads on the state then apply as penalties, docking clarity when the picture is incoherent or thin in places that matter. **Decision resolution** (primary channel). Are the key claims far enough from ambiguous to act on? **Knowledge certainty** (primary channel). Has enough evidence accumulated to trust the current picture? **Coherence** (penalty). Unresolved contradictions pull the score down. A state that disagrees with itself is not ready to act on, however confident its individual claims look. **Coverage** (penalty). Open gaps pull the score down, and a gap with more downstream dependencies pulls harder. The result is a gradient that points the agent at the gaps worth closing first. So a high clarity score means the same thing every time: the calls that matter have resolved, the evidence behind them is real, nothing is openly contradicting, and the ground that bears on the decision is covered. ## Load-Bearing Beliefs Some beliefs hold up everything above them. A load-bearing belief is one that, if it turned out false, would take the whole plan's thinking down with it. "The TAM is $4.2B" is load-bearing when the pricing model, the projections, and the go-to-market plan all rest on it. thinkⁿ finds these by walking the belief graph: a claim is load-bearing when many other beliefs derive from, support, or depend on it, so removing it invalidates everything downstream. This is where clarity surfaces risk. The engine flags a load-bearing belief when its evidence is weak, has decayed past its half-life, or is contradicted, so the agent stops building on a foundation that is eroding underneath it. Those are the liveness flags that ride on the current state: `stale`, `stable`, `contradicted`, `uncertain`, computed at read time for the beliefs that matter most. ## Routing on Clarity Clarity rides back on every read. Pull it off the world model and branch your loop on it: ```ts const { clarity, gaps, beliefs } = await beliefs.read() if (clarity < 0.3) { // Not enough to work with. Research the biggest gaps. await runResearch(gaps) } else if (clarity > 0.7) { // Enough to act. Draft recommendations. await draftRecommendations(beliefs) } else { // In between. Close the gaps that still matter. await investigateGaps(gaps) } ``` It comes back on `before(query)` too, alongside the prompt, beliefs, goals, gaps, and the top-ranked move, so a turn can read its own readiness before it spends a token. The thresholds are yours to set. Clarity gives you a stable axis to set them on. ## High Clarity Is Not High Confidence People conflate the two, and pulling them apart is deliberate. An agent can investigate a market thoroughly, conclude it could break either way, and still land at high clarity, because it has done the work to know the question is genuinely open. Clarity measures readiness to act, not certainty in the answer. Low clarity says "keep investigating." High clarity says "you have enough to decide," and that includes deciding the call is a hard one. The agent in the bottom-right quadrant is at high clarity and low confidence at the same time, and routing it back to research would be the wrong move. ## How Knowledge Certainty Earns Itself When you seed a belief with `add()`, knowledge certainty starts near zero. That is on purpose. `add('Market is $4.2B', { confidence: 0.8 })` sets the belief's starting position, so decision resolution reflects the 0.8. But the 0.8 is a weak prior, not a verdict. The engine has seen no evidence yet. Knowledge certainty tracks earned evidence: how much has actually accumulated against the claim since it was stated. It grows when the same claim picks up supporting or refuting evidence, when repeated observations reinforce it, and when tool results confirm it independently. This is why a high-confidence `add()` with nothing behind it lands squarely in the "belief without evidence" quadrant, where clarity flags it for validation instead of treating your number as proof. To build knowledge certainty fast, route real agent output through `after()` rather than seeding everything by hand. The extraction pipeline mines evidence from the work the agent is already doing and folds it into the matching beliefs. ## Where Clarity Sits ## Moves Source: https://thinkn.ai/dev/core/moves Summary: VOI-ranked next actions: the engine's answer to where the agent should look next. ## The Job Knowing what you believe is half a loop. The other half is knowing where to look next, and most agents answer it by vibes. Moves answer it off the graph. Each one names a specific belief, gap, or contradiction and estimates how much acting on it would sharpen the agent's picture of its world, scored against where the agent is trying to go. A move is judgment pointed somewhere: the engine reading the graph and naming where attention pays off most. The top move is the one that buys the most certainty per step toward the goal, so a loop that reads it never has to guess at its own next step. In the world-model frame this is the planning slice. [Clarity](/dev/core/clarity) tells the agent whether it has enough to act; moves tell it what to do when it doesn't. ## What a Move Is A move is a recommended action the engine surfaces from the current belief state. It carries an action type, a finer subtype, a target, a reason in plain language, an expected value, and an executor. ```ts { action: 'gather_evidence', subType: 'research', target: 'Missing APAC market analysis', reason: 'High-impact gap with 3 downstream dependencies', value: 0.72, executor: 'agent', } ``` - **action.** The kind of move: `clarify`, `resolve_uncertainty`, `gather_evidence`, `compare_paths`. - **target.** The belief, gap, or contradiction the move addresses. Every move anchors to one, so it is always replayable back to the claim it would inform. - **reason.** Why it matters, in natural language. - **value.** The uncertainty the move would drain if it resolves, on a 0-1 scale. Higher means a bigger shift in the picture. - **executor.** Who should act: `agent`, `user`, or `both`. The `action` is the routing key. It is a small, closed set, which is what makes the loop below a clean switch. Finer dispatch lives on the `subType`: `validate_assumption`, `resolve_contradiction`, `quantify_risk`, `design_test`, `research`, `synthesize`, `reframe`. So a `gather_evidence` move with `subType: 'research'` tells the loop both the class of action to take and the exact shape of it. Route on `action`; branch on `subType` when you need the detail. | Action | When it surfaces | |--------|-----------------| | `gather_evidence` | A gap or thinly supported belief needs investigation | | `clarify` | A contradiction sits between two beliefs | | `resolve_uncertainty` | A load-bearing belief is short on evidence | | `compare_paths` | Several valid interpretations need a decision framework | ## Ranking Is by Impact, Not Count The engine does not surface the most moves. It surfaces the most consequential one first. A gap with three downstream dependencies outranks an isolated gap. A contradiction between two load-bearing beliefs (the ones the rest of the strategy rests on) outranks a contradiction between peripheral claims that nothing leans on. The cheap isolated fix loses to the structural one. That ordering is the value-of-information calculation. Four signals feed it: ``` ┌────────────────────────────────────────────────────────────────┐ │ WHAT FEEDS THE RANKING │ │ │ │ 1. EXPECTED RESOLUTION - how much uncertainty drains if │ │ the move resolves │ │ 2. DEPENDENCY WEIGHT - how many beliefs lean on the │ │ target │ │ 3. LOAD-BEARING - whether the target holds up │ │ everything above it │ │ 4. CLARITY LIFT - which move would raise clarity │ │ the most │ │ │ │ Scored against intent: the top move is the one most in │ │ the way of the goal. │ │ │ └────────────────────────────────────────────────────────────────┘ ``` Because moves are scored against the active goal, the ranking is goal-conditioned. The same belief state pursuing a different intent surfaces different top moves. The engine is not asking "what is most uncertain," it is asking "what uncertainty is most in the way of where I am trying to go." ## Reading Moves The top move rides back on the turn result, so most loops never need a separate ranking call. ```ts // Before the agent acts const context = await beliefs.before(userMessage) console.log(context.moves[0]) // the single best next action this turn // After the agent acts const delta = await beliefs.after(result.text) console.log(delta.moves) // updated recommendations // Full world state, on demand const world = await beliefs.read() console.log(world.moves) // all current moves ``` When you want an explicit ranked list, ask for one: ```ts const ranked = await beliefs.moves.rank({ topN: 5 }) ``` ## Routing on Moves Switch on `action` to steer the loop. Because the action set is small and closed, the branch stays exhaustive: ```ts const delta = await beliefs.after(result.text) const next = delta.moves[0] if (!next) { // Nothing worth chasing. Clarity is high enough to finalize. await finalize(delta) } else if (next.action === 'gather_evidence') { await gatherEvidence(next.target) // next.subType narrows it: 'research', 'design_test', ... } else if (next.action === 'clarify') { await resolveContradiction(next.target) } else if (next.action === 'resolve_uncertainty') { await deepDive(next.target) } else if (next.action === 'compare_paths') { await presentTradeoffs(next.target) } ``` ## The Executor Keeps the Agent in Its Lane Not every move belongs to the agent. The `executor` field says who should act on it. | Executor | Meaning | |----------|---------| | `agent` | The agent can handle this on its own | | `user` | Needs human input or judgment | | `both` | The agent can start; the user has to weigh in | A `user` move surfaces when the engine reads the target as a value judgment or strategic call the agent should not make alone. The point is to route to a human at that boundary rather than guess past it. The engine marks where the agent's authority runs out so the loop does not quietly cross a line it was never given. ## Moves Versus Affordances Keep two surfaces distinct. [Actions](/dev/core/actions) are affordances: the declared set of verbs the agent can enact in this world, each carrying a safety class. Moves are the engine's epistemic next steps, the reasoning about where attention pays off most. A move often points at the gap an affordance would close, yet the two answer different questions: what am I allowed to do here, versus where is it worth looking. Read the move to find the open question, read the affordances to find the legal way to close it. ## Forecasting a Move Forward A move's `value` scores the immediate gain. As act-to-outcome history accumulates, the engine can also project that value a few steps forward. ```ts const forecast = await beliefs.moves.forecast(action) const projection = await beliefs.forecast.predict(actions) ``` `moves.forecast(action)` projects one move's value across the next few steps; `forecast.predict(actions)` does it across a set. Both are live, and both report low confidence on a fresh workspace: the projection only earns trust once there is enough history linking actions to the outcomes they produced. On day one you get the immediate ranking and an honest "not enough history yet" on the look-ahead. The forecast sharpens as the agent acts and the engine watches what each action actually moved. --- # Tutorial ## Build a Research Agent Source: https://thinkn.ai/dev/tutorial/research-agent Summary: A 30-minute guided build. Learn the model by writing one. End with a working agent that knows what it does not know. This tutorial teaches the model by building one thing end-to-end: a research agent that investigates a question, accumulates evidence, detects when sources disagree, and stops when it has enough to act. You will not call an LLM during this tutorial. Every "agent output" is a literal string so the tutorial runs deterministically with no API key beyond the Beliefs key. At the end you'll see how to swap in Claude, GPT, or any other model. **You'll learn one concept per section.** Each builds on the previous. Copy the code as you go. The final section assembles everything into one runnable file. - Node 18+ - A Beliefs API key ([request access](/dev/beta) if you don't have one yet) - ~30 minutes ## What you're building By the end, you'll have a research agent that: 1. Takes a research question 2. Reads what it currently believes about the question 3. Investigates the highest-priority gap 4. Detects when new evidence contradicts what it already knew 5. Stops when its **clarity score** crosses a threshold 6. Reports what it found, and what it deliberately doesn't know ``` ┌────────────────────────────────────────────────────────────┐ │ Turn 1 clarity 0.18 ████░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ │ Turn 2 clarity 0.34 ██████████░░░░░░░░░░░░░░░░░░░░░░ │ │ Turn 3 clarity 0.51 ███████████████░░░░░░░░░░░░░░░░░ │ │ Turn 4 clarity 0.68 ████████████████████░░░░░░░░░░░░ │ │ Turn 5 clarity 0.74 ██████████████████████░░░░░░░░░░ │ → STOP └────────────────────────────────────────────────────────────┘ ``` The clarity score is what makes this different from a turn-counter or a token budget. The agent stops when it has *learned* enough, not when it has *talked* enough. --- ## 1. Setup ### Install ```bash mkdir research-agent && cd research-agent npm init -y npm pkg set type=module npm install beliefs tsx typescript npm install -D @types/node ``` The `npm pkg set type=module` line marks the package as ESM so top-level `await` works in your script. Without it, you'd need to wrap each section in `async function main() { ... }`, a small annoyance, but worth setting up once. ### Configure TypeScript Create `tsconfig.json`: ```json { "compilerOptions": { "target": "ES2022", "module": "ES2022", "moduleResolution": "Bundler", "esModuleInterop": true, "strict": true } } ``` ### Set your key ```bash export BELIEFS_KEY=bel_live_xxx ``` ### First call Create `agent.ts`: ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY!, namespace: `research-${Date.now()}`, writeScope: 'space', }) const world = await beliefs.read() console.log('beliefs:', world.beliefs.length) console.log('clarity:', world.clarity) ``` Run it: ```bash npx tsx agent.ts ``` You should see something like: ``` beliefs: 0 clarity: 0.25 ``` **What just happened.** You created a fresh belief state in a unique namespace. It has zero beliefs. Clarity defaults to a low baseline because there's nothing to be clear about yet. Every call from here will operate against this same namespace. `namespace: \`research-${Date.now()}\`` makes every tutorial run a fresh slate. In production, pick stable namespaces (one per project, customer, or session). The engine uses LLM extraction, so exact claims, counts, and clarity values vary across runs. Your output will show the same *patterns* (clarity rising turn-over-turn, contradictions detected when sources disagree, gaps closing as evidence comes in) but different *exact* values. That's expected and correct. Wherever a tutorial output shows a range or `~`, treat it as illustrative. → Concept: **[World](/dev/core/world)**. The read-only view of everything your agent currently believes. --- ## 2. Tell the agent what to research A research agent needs a goal. In beliefs a goal is a first-class object, not a string buried in your prompt. Add this to `agent.ts`: ```ts await beliefs.add('Determine the size of the AI developer tools market', { type: 'goal', }) const world = await beliefs.read() console.log('goals:', world.goals) ``` Output: ``` goals: [ 'Determine the size of the AI developer tools market' ] ``` **What just happened.** `add()` with `type: 'goal'` registers what the agent is *trying to do*, distinct from claims (what the agent thinks is *true*). The clarity score now considers whether the goal has been resolved. → Concept: **[Intent](/dev/core/intent)**. Goals, decisions, and constraints. What the agent wants, distinct from what it knows. --- ## 3. Seed what you already know Often you start an investigation with priors: things you've heard, suspect, or have weak evidence for. Beliefs lets you assert these explicitly with a confidence score. ```ts await beliefs.add('AI dev tools market is around $4B', { confidence: 0.6, type: 'assumption', }) await beliefs.add('GitHub Copilot has the largest market share', { confidence: 0.7, type: 'assumption', }) await beliefs.add('Missing breakdown by enterprise vs individual developers', { type: 'gap', }) const state = await beliefs.read() console.log('beliefs:', state.beliefs.length) console.log('gaps: ', state.gaps.length) console.log('clarity:', state.clarity.toFixed(2)) ``` Output (illustrative; exact values vary): ``` beliefs: 2 gaps: 1 clarity: 0.30–0.40 ``` **What just happened.** Three calls, three different shapes: - `type: 'assumption'`: a stated belief without supporting evidence yet. Confidence = 0.6 means "I lean this way, but haven't done the work." - `type: 'gap'`: something the agent has flagged as unknown. Gaps are first-class. They reduce clarity until filled. Notice clarity went up, but only slightly. That's because **stated confidence is not the same as evidence**. The system tracks both: - **Decision resolution:** how confident you are in the answer (0.6 here) - **Knowledge certainty:** how much *evidence* backs the answer (zero here, you just stated it) A claim stated at 0.95 with zero evidence is in a different epistemic category than a claim at 0.65 with 40 supporting observations. The clarity score reflects this. → Concept: **[Beliefs](/dev/core/beliefs)**. What types exist, how confidence works, why the two-channel model matters. --- ## 4. The core loop Now the loop that defines a beliefs-aware agent: **read context, act, feed observation**. For this tutorial, "act" returns a literal string: what your agent might output if you'd run a real LLM. In Section 9 you'll swap it for a real model. ```ts async function fakeAgent(_systemPrompt: string, _userMessage: string): Promise { // Pretend an LLM ran. Return a realistic agent output. return `Based on a Gartner 2024 report, the AI developer tools market is valued at $4.2B. The top three players (GitHub Copilot, Cursor, and Tabnine) account for approximately 65% of the market. Enterprise adoption is currently around 40% of total spend, with individual developers making up the remainder. The market is growing at roughly 25% year over year.` } const userMessage = 'Research the AI developer tools market' // 1. Read what the agent currently believes const context = await beliefs.before(userMessage) // 2. Run the agent const output = await fakeAgent(context.prompt, userMessage) // 3. Feed the observation const delta = await beliefs.after(output) console.log('changes: ', delta.changes.length) console.log('clarity: ', delta.clarity.toFixed(2)) console.log('readiness:', delta.readiness) ``` Output (illustrative): ``` changes: 4–7 clarity: 0.45–0.55 readiness: medium ``` **What just happened.** Three calls did real work: - `before(message)`: returned a `BeliefContext` with `prompt` (a serialized summary of the current belief state, ready to inject into a system prompt), plus the agent's beliefs, goals, gaps, clarity, and recommended next moves. - `fakeAgent(...)`: produced output. In production this is your LLM call; the system prompt is `context.prompt`. - `after(output)`: extracted beliefs from the agent's text, detected if any conflicted with what was already there, and updated the world model. `delta.changes` is the list of what changed; `delta.readiness` is a coarse `'low' | 'medium' | 'high'` label derived from `clarity`. Clarity jumped because the agent now has evidenced claims (Gartner cited as a source) instead of bare assumptions. → Concept: **[The loop](/dev/sdk/patterns)**. Patterns for single-turn, multi-turn, streaming, and tool-aware agent loops. --- ## 5. Watching clarity rise A research agent should run more than one turn. Let's loop until clarity is high enough. ```ts async function fakeAgentTurn2(_prompt: string, _focus: string): Promise { return `Looking deeper into enterprise adoption: among Fortune 500 companies, 72% have at least piloted an AI coding assistant, but only 31% have rolled it out company-wide. The biggest blockers cited are security review (mentioned by 58% of CIOs surveyed), licensing complexity (44%), and uncertainty about ROI (37%). Adoption is highest in technology and financial services, lowest in healthcare and government.` } async function fakeAgentTurn3(_prompt: string, _focus: string): Promise { return `On individual developer adoption: of approximately 28 million professional developers worldwide, 9.2 million have used an AI coding assistant at least monthly in 2024, about 33% penetration. Among those, 4.1 million pay personally for a tool (the rest use free tiers or employer-provided licenses). Average individual spend is ~$15/month across paid users.` } const turns = [fakeAgentTurn2, fakeAgentTurn3] for (let i = 0; i < turns.length; i++) { const ctx = await beliefs.before(userMessage) // If clarity is already high, stop early if (ctx.clarity > 0.7) { console.log(`\nclarity ${ctx.clarity.toFixed(2)}, stopping`) break } // Use the highest-value move as the focus for this turn const focus = ctx.moves[0]?.target ?? userMessage const output = await turns[i](ctx.prompt, focus) const d = await beliefs.after(output) console.log( `turn ${i + 2}: clarity ${d.clarity.toFixed(2)}, ` + `+${d.changes.length} changes, readiness ${d.readiness}`, ) } ``` Output (illustrative; typically reaches `'high'` readiness within a few turns): ``` turn 2: clarity 0.55–0.65, +N changes, readiness medium turn 3: clarity 0.70–0.80, +M changes, readiness high ``` **What just happened.** The loop reads `clarity` and stops when it's high enough. Each turn: - Uses `ctx.moves[0].target` as the focus. The engine suggests the highest-value gap to investigate next. - Calls the "agent" with `ctx.prompt`: a serialized summary of current state, so the agent acts with awareness of what's already known. - Feeds the output back via `after()`. Extraction, conflict detection, and clarity recompute happen automatically. After a few turns, clarity typically crosses your `'high'` threshold. The agent has enough to act. → Concept: **[Clarity](/dev/core/clarity)**. What the score actually measures, the two-channel model, and how to use it for routing decisions. → Concept: **[Moves](/dev/core/moves)**. How the engine ranks next-best actions by how much they'd reduce uncertainty. --- ## 6. Contradictions What happens if a tool returns evidence that disagrees with what the agent already believes? ```ts const conflictingTool = `Tool result from market_research_db: { "source": "IDC Q4 2024 AI DevTools Tracker", "finding": "Global AI developer tools market is $6.8B, not $4.2B as earlier estimates suggested. The discrepancy is because earlier figures excluded embedded AI features in mainstream IDEs (VS Code Copilot, JetBrains AI Assistant). When those are included, the market is 60% larger than Gartner's narrower scope.", "methodology": "Bottom-up survey of 2,400 enterprises across 18 countries" }` const delta = await beliefs.after(conflictingTool, { tool: 'market_research_db', source: 'IDC Q4 2024 Tracker', }) const world = await beliefs.read() console.log('contradictions:', world.contradictions.length) for (const c of world.contradictions) { console.log(' -', c) } ``` Output (the contradiction summary string is engine-formatted and will vary): ``` contradictions: 1 - ``` **What just happened.** The engine extracted a new belief from the tool result ("market is $6.8B") and recognized it directly conflicts with an existing belief ("market is around $4B"). Both are kept. Nothing is silently overwritten. The contradiction surfaces in `world.contradictions` (a `string[]`) and reduces the clarity score until you resolve it. The two beliefs aren't equally weighted, though. The new claim has: - A tool source (`market_research_db`) tagged via `{ tool, source }` - A concrete methodology cited in the text The original was `type: 'assumption'` with no evidence. When the system fuses them, the evidenced claim dominates, but the original is preserved in the trace so you can see how the agent's view shifted. → Concept: **[World](/dev/core/world)**. How `world.contradictions` and `world.edges` surface conflicts and supersedence. --- ## 7. Resolving, and following moves Now use the engine's recommended next move to direct what to investigate. ```ts const ctx = await beliefs.before(userMessage) console.log('top 3 moves the engine suggests:') for (const m of ctx.moves.slice(0, 3)) { console.log(` - [${m.action}] ${m.target}`) console.log(` reason: ${m.reason}`) } // Investigate the top move const topMove = ctx.moves[0] if (topMove) { const investigation = `Resolving the market-size question: I cross-checked the IDC figure against Forrester and McKinsey reports. Forrester pegs the "AI-augmented dev tools" market at $7.1B for 2024, closer to IDC than Gartner. The discrepancy is methodology: Gartner's $4.2B excludes embedded AI features in IDEs, while IDC and Forrester include them. The $6.8-7.1B range is the broader market; $4.2B is the narrow "AI-native" tools market.` await beliefs.after(investigation, { source: 'Forrester + McKinsey cross-check' }) } const final = await beliefs.read() console.log('\nfinal clarity:', final.clarity.toFixed(2)) console.log('contradictions:', final.contradictions.length) ``` Output (illustrative; move actions, targets, and reasons are engine-generated): ``` top 3 moves the engine suggests: - [] reason: - ... final clarity: 0.75–0.85 contradictions: 0 ``` Common `action` values: `clarify`, `gather_evidence`, `resolve_uncertainty`, `compare_paths`, `validate`. `target` is the specific claim or gap to act on (for example, `"market size by region"` or `"enterprise vs individual split"`); `reason` is the engine's plain-English explanation of why the move is high-value right now. **What just happened.** `ctx.moves` is a ranked list of recommended next actions. The engine derived these from the current state. It knows which gaps are open, which beliefs are weakly evidenced, and which contradictions need clarifying. You don't have to plan the next step yourself; you can just route on `moves[0]`. After feeding the cross-check, the engine sees the methodology distinction, supersedes the old "around $4B" assumption, and the contradiction typically resolves. Clarity climbs. → Concept: **[Moves](/dev/core/moves)**. Q-value ranking, executor types, and how to use moves for autonomous routing. --- ## 8. Trace: what changed and why Every transition is recorded. Look at the audit trail. ```ts const entries = await beliefs.trace() console.log(`total transitions: ${entries.length}\n`) console.log('most recent 5:') for (const e of entries.slice(0, 5)) { const conf = e.confidence ? ` (${e.confidence.before?.toFixed(2) ?? '?'} → ${e.confidence.after?.toFixed(2) ?? '?'})` : '' console.log(` - ${e.action}${conf} | ${e.reason ?? '(no reason)'}`) } ``` Output (abbreviated; specific reasons and confidence shifts will vary): ``` total transitions: most recent 5: - updated (0.X → 0.Y) | - resolved | - created | - ... ``` Each `TraceEntry` carries `action` (`'created' | 'updated' | 'removed' | 'resolved'`), optional `beliefId`, optional `confidence` shift `{ before, after }`, optional `agent`, optional `source`, `timestamp`, and optional `reason`. **What just happened.** Every belief mutation (created, updated, removed, resolved) landed in the ledger with the reason and the confidence shift. You can replay the agent's reasoning at any point. In production this is what you show on a "why did the agent decide X?" debug page. → Concept: **[Ledger](/dev/internals/how-it-works)**. What's recorded, replay semantics, and how to query the trail. --- ## 9. The complete agent Here's everything assembled into one file. Save as `agent.ts` and run. ```ts import Beliefs from 'beliefs' // ─── Setup ───────────────────────────────────────────────────────── const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY!, namespace: `research-${Date.now()}`, writeScope: 'space', }) const userMessage = 'Research the AI developer tools market' // ─── Stub agent ──────────────────────────────────────────────────── // Each turn returns a realistic agent output. In production, replace // the body of `runAgent` with a call to your LLM (see Section 10). const turnOutputs = [ `Based on a Gartner 2024 report, the AI developer tools market is valued at $4.2B. The top three players (GitHub Copilot, Cursor, and Tabnine) account for approximately 65% of the market. Enterprise adoption is currently around 40% of total spend, with individual developers making up the remainder. Growing at roughly 25% YoY.`, `Looking deeper into enterprise adoption: among Fortune 500 companies, 72% have at least piloted an AI coding assistant, but only 31% have rolled it out company-wide. Biggest blockers: security review (58%), licensing complexity (44%), and uncertainty about ROI (37%). Highest in technology and financial services, lowest in healthcare and government.`, `On individual developer adoption: of approximately 28 million professional developers worldwide, 9.2 million have used an AI coding assistant at least monthly in 2024, about 33% penetration. Of those, 4.1 million pay personally for a tool. Average individual spend is ~$15/month across paid users.`, ] const conflictingTool = `Tool result from market_research_db: { "source": "IDC Q4 2024 AI DevTools Tracker", "finding": "Global AI developer tools market is $6.8B, not $4.2B. Earlier estimates excluded embedded AI features in mainstream IDEs.", "methodology": "Bottom-up survey of 2,400 enterprises across 18 countries" }` const reconciliation = `Cross-checked IDC against Forrester and McKinsey. Forrester: $7.1B for 2024, closer to IDC. The discrepancy is methodology: Gartner's $4.2B excludes embedded IDE features; IDC and Forrester include them. The $6.8-7.1B range is the broader market; $4.2B is the narrow "AI-native" tools market.` async function runAgent(_systemPrompt: string, turn: number): Promise { return turnOutputs[turn] ?? '' } // ─── Goal + priors ───────────────────────────────────────────────── await beliefs.add(userMessage, { type: 'goal' }) await beliefs.add('AI dev tools market is around $4B', { confidence: 0.6, type: 'assumption', }) await beliefs.add('GitHub Copilot has the largest market share', { confidence: 0.7, type: 'assumption', }) await beliefs.add('Missing breakdown by enterprise vs individual developers', { type: 'gap', }) // ─── Research loop ───────────────────────────────────────────────── const TARGET_CLARITY = 0.7 const MAX_TURNS = 5 for (let turn = 0; turn < MAX_TURNS; turn++) { const ctx = await beliefs.before(userMessage) console.log( `turn ${turn + 1}: clarity ${ctx.clarity.toFixed(2)}, ` + `${ctx.beliefs.length} beliefs, ${ctx.gaps.length} gaps`, ) if (ctx.clarity >= TARGET_CLARITY) { console.log(` → clarity hit ${TARGET_CLARITY}, stopping`) break } const output = await runAgent(ctx.prompt, turn) if (!output) break await beliefs.after(output) } // ─── Conflicting evidence + reconciliation ───────────────────────── console.log('\nfeeding conflicting tool result...') await beliefs.after(conflictingTool, { tool: 'market_research_db', source: 'IDC Q4 2024 Tracker', }) const afterConflict = await beliefs.read() console.log(` contradictions: ${afterConflict.contradictions.length}`) console.log('\ncross-checking and reconciling...') await beliefs.after(reconciliation, { source: 'Forrester + McKinsey' }) // ─── Report ──────────────────────────────────────────────────────── const final = await beliefs.read() console.log('\n── Final state ──') console.log(`clarity: ${final.clarity.toFixed(2)}`) console.log(`beliefs: ${final.beliefs.length}`) console.log(`gaps remaining: ${final.gaps.length}`) console.log(`contradictions: ${final.contradictions.length}`) console.log('\n── Top beliefs ──') const top = [...final.beliefs] .sort((a, b) => b.confidence - a.confidence) .slice(0, 5) for (const b of top) { console.log(` [${b.confidence.toFixed(2)}] ${b.text}`) } console.log('\n── What we still don\'t know ──') for (const gap of final.gaps) console.log(` - ${gap}`) ``` Run it: ```bash npx tsx agent.ts ``` Expected output (illustrative; specific numbers and extracted texts vary across runs): ``` turn 1: clarity 0.25–0.35, 2 beliefs, 1 gaps turn 2: clarity 0.45–0.55, 5–7 beliefs, 1 gaps turn 3: clarity 0.60–0.70, 8–10 beliefs, 1 gaps turn 4: clarity 0.70–0.80, 11–13 beliefs, 0 gaps → clarity hit 0.7, stopping (or: loop exits when stub outputs are exhausted) feeding conflicting tool result... contradictions: 1 cross-checking and reconciling... ── Final state ── clarity: 0.75–0.85 beliefs: 13–15 gaps remaining: 0 contradictions: 0 ── Top beliefs ── [0.85–0.95] [0.80–0.90] [0.75–0.85] ... ── What we still don't know ── (empty when the agent has filled its declared gaps) ``` That's a complete agent. It investigated, recognized when its assumptions were wrong, reconciled competing sources, and stopped when it had enough to act, all without you writing any tracking code. --- ## 10. Swap in a real LLM Replace `runAgent` with a call to your model of choice. The rest of the file stays identical. ### Anthropic ```ts import Anthropic from '@anthropic-ai/sdk' const client = new Anthropic() async function runAgent(systemPrompt: string, turn: number): Promise { const focus = turn === 0 ? userMessage : `Investigate further: ${(await beliefs.before(userMessage)).moves[0]?.target ?? userMessage}` const msg = await client.messages.create({ model: 'claude-sonnet-4-20250514', max_tokens: 1024, system: systemPrompt, messages: [{ role: 'user', content: focus }], }) return msg.content .filter((b): b is { type: 'text'; text: string } => b.type === 'text') .map((b) => b.text) .join('') } ``` ### Vercel AI SDK ```ts import { generateText } from 'ai' import { anthropic } from '@ai-sdk/anthropic' async function runAgent(systemPrompt: string, _turn: number): Promise { const { text } = await generateText({ model: anthropic('claude-sonnet-4-20250514'), system: systemPrompt, prompt: userMessage, }) return text } ``` ### OpenAI ```ts import OpenAI from 'openai' const openai = new OpenAI() async function runAgent(systemPrompt: string, _turn: number): Promise { const completion = await openai.chat.completions.create({ model: 'gpt-4o', messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: userMessage }, ], }) return completion.choices[0]?.message?.content ?? '' } ``` The belief layer is provider-agnostic. Anything that takes a system prompt and returns text plugs in here. → Reference: **[Hack Guide](/dev/tutorial/hack-guide)**. Full integration patterns for every major framework. --- ## What you learned You touched eight concepts in one build: | Concept | Where it appeared | Reference | |---|---|---| | World model | `beliefs.read()` returning the full picture | [World](/dev/core/world) | | Goals | `add(text, { type: 'goal' })` | [Intent](/dev/core/intent) | | Beliefs + types | `add` with `type: 'assumption'`, `'gap'` | [Beliefs](/dev/core/beliefs) | | The loop | `before → act → after` | [Loop Patterns](/dev/sdk/patterns) | | Clarity | Stopping condition | [Clarity](/dev/core/clarity) | | Moves | Ranked next actions | [Moves](/dev/core/moves) | | Contradictions | Auto-detected via `after()` | [World](/dev/core/world) | | Trace | Audit trail of every transition | [Ledger](/dev/internals/how-it-works) | ## Where to go next You now have the model. The rest of the docs are reference for the parts you haven't needed yet. ## Hack Guide Source: https://thinkn.ai/dev/tutorial/hack-guide Summary: Zero to building with beliefs in 10 minutes. Everything you need for the hackathon. ## Get Your Key 1. Sign in at [thinkn.ai](https://thinkn.ai) 2. Go to [Profile > API Keys](/profile/api-keys) 3. Click **Create Key**, copy the `bel_live_...` value ```bash export BELIEFS_KEY=bel_live_... ``` ## Install ```bash npm i beliefs ``` Verify the connection: ```bash node -e "import('beliefs').then(async ({default: B}) => { const b = new B({ apiKey: process.env.BELIEFS_KEY, namespace: 'hack-guide', writeScope: 'space' }); const s = await b.read(); console.log('beliefs:', s.beliefs.length, 'clarity:', s.clarity) })" ``` You should see `beliefs: 0 clarity: 0.25`: an empty belief state with baseline clarity, ready to go. These examples use `writeScope: 'space'` so they run immediately. For chat apps, keep the SDK default `writeScope: 'thread'` and bind a thread with `thread` or `beliefs.withThread(threadId)`. Give your agent the SDK reference so it can write correct code on the first try: `https://thinkn.ai/llms.txt` ## The Pattern Every agent turn follows three steps: ```ts // 1. What does the agent believe right now? const context = await beliefs.before(userMessage) // 2. Run your agent with belief context injected const result = await myAgent.run({ system: context.prompt }) // 3. Feed the output. Beliefs extracted automatically. const delta = await beliefs.after(result.text) ``` That is the entire integration. `before()` gives your agent context about what's already known. `after()` feeds the result back, so the world model learns from the turn: claims extracted, conflicts detected, confidence updated, next moves recomputed. ``` ┌─────────┐ ┌───────────┐ ┌─────────┐ │ before()│────▶│ your agent │────▶│ after() │ │ beliefs │ │ runs here │ │ extract │ │ + moves │ │ │ │ + fuse │ └─────────┘ └───────────┘ └────┬────┘ ▲ │ └──────────── next turn ───────────┘ ``` ### What comes back `before()` gives you a `BeliefContext`: - `prompt`: inject this into your agent's system prompt - `beliefs`: current claims with confidence scores - `gaps`: what the agent doesn't know yet - `clarity`: 0-1 readiness score (higher = more confident) - `moves`: ranked next actions, prioritized by how much they'd reduce uncertainty `after()` gives you a `BeliefDelta`: - `changes`: what was created, updated, or removed - `clarity`: updated readiness score - `readiness`: `'low'`, `'medium'`, or `'high'` - `moves`: updated next actions - `state`: full world state after this turn --- ## Framework Recipes ### Vercel AI SDK Best for: streaming, multi-provider model swapping, and TypeScript-first ergonomics. ```ts import { generateText } from 'ai' import { anthropic } from '@ai-sdk/anthropic' import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'vercel-ai-hack', writeScope: 'space', }) async function research(question: string) { const context = await beliefs.before(question) const { text } = await generateText({ model: anthropic('claude-sonnet-4-20250514'), system: context.prompt, prompt: question, }) const delta = await beliefs.after(text) console.log(`clarity: ${delta.clarity}, changes: ${delta.changes.length}`) return text } ``` With streaming: ```ts import { streamText } from 'ai' const context = await beliefs.before(question) const result = streamText({ model: anthropic('claude-sonnet-4-20250514'), system: context.prompt, prompt: question, }) let fullText = '' for await (const chunk of result.textStream) { process.stdout.write(chunk) fullText += chunk } await beliefs.after(fullText) ``` Call `after()` exactly once per turn, after the stream completes. Do not call it on partial chunks. Each call triggers extraction and fusion. Calling per-chunk creates duplicate beliefs from incomplete text. ### Anthropic SDK Best for: direct control over Claude features (tool use, vision, extended thinking), minimal dependencies. ```ts import Anthropic from '@anthropic-ai/sdk' import Beliefs from 'beliefs' const client = new Anthropic() const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'anthropic-hack', writeScope: 'space', }) async function research(question: string) { const context = await beliefs.before(question) const message = await client.messages.create({ model: 'claude-sonnet-4-20250514', max_tokens: 4096, system: context.prompt, messages: [{ role: 'user', content: question }], }) const text = message.content .filter(b => b.type === 'text') .map(b => b.text) .join('') const delta = await beliefs.after(text) return { text, delta } } ``` ### OpenAI SDK Best for: GPT/o-series models, Responses API workflows, OpenAI-native ecosystems. ```ts import OpenAI from 'openai' import Beliefs from 'beliefs' const openai = new OpenAI() const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'openai-hack', writeScope: 'space', }) async function research(question: string) { const context = await beliefs.before(question) const completion = await openai.chat.completions.create({ model: 'gpt-4o', messages: [ { role: 'system', content: context.prompt }, { role: 'user', content: question }, ], }) const text = completion.choices[0]?.message?.content ?? '' const delta = await beliefs.after(text) return { text, delta } } ``` > If using an o-series model (o3, o4-mini), change `role: 'system'` to `role: 'developer'`. ### Any LLM / Plain Fetch Best for: serverless/edge runtimes, custom or self-hosted models, anywhere you don't want a vendor SDK in the dependency tree. The SDK works with anything that produces text. Call `before`, pass `context.prompt` to your model, call `after` with the output. ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'plain-fetch-hack', writeScope: 'space', }) async function withBeliefs(input: string, runAgent: (prompt: string) => Promise) { const context = await beliefs.before(input) const output = await runAgent(context.prompt + '\n\nUser: ' + input) const delta = await beliefs.after(output) return { output, delta } } ``` --- ## Project Ideas The snippets below use `callLLM(systemPrompt, userMessage)` and `searchWeb(query)` as stand-ins for your model and search tool of choice. Plug in any of the four framework recipes above (Vercel AI, Anthropic, OpenAI, plain fetch) wherever you see `callLLM(...)`, and any search API for `searchWeb(...)`. The point of these examples is the belief flow, not the LLM wiring. ### Research Agent (Beginner) An agent that researches a topic and tracks what it knows, what conflicts, and what's missing. Use `clarity` to decide when to stop researching and summarize. ```ts const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'research-agent', writeScope: 'space', }) async function deepResearch(topic: string) { await beliefs.add(`Research: ${topic}`, { type: 'goal' }) for (let turn = 0; turn < 5; turn++) { const context = await beliefs.before(topic) if (context.clarity > 0.7) break const result = await callLLM(context.prompt, topic) const delta = await beliefs.after(result) console.log(`Turn ${turn + 1}: clarity ${delta.clarity.toFixed(2)}, ` + `${delta.changes.length} new beliefs`) } const world = await beliefs.read() return { beliefs: world.beliefs, gaps: world.gaps, clarity: world.clarity } } deepResearch('AI developer tools market').then(console.log) ``` ### Multi-Agent Debate (Intermediate) Two agents with different perspectives contribute to the same namespace. The belief system detects contradictions and tracks which claims survive. ```ts const optimist = new Beliefs({ apiKey, agent: 'optimist', namespace: 'debate', writeScope: 'space' }) const skeptic = new Beliefs({ apiKey, agent: 'skeptic', namespace: 'debate', writeScope: 'space' }) const bullCase = await callLLM('Make the bull case for AI startups in 2026') await optimist.after(bullCase) const bearCase = await callLLM('Make the bear case for AI startups in 2026') await skeptic.after(bearCase) const world = await optimist.read() console.log(`Contradictions: ${world.contradictions.length}`) console.log(`Beliefs: ${world.beliefs.length}`) ``` ### Fact Checker (Intermediate) Verify claims by gathering evidence. Watch confidence shift as supporting and refuting evidence arrives. ```ts const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'fact-check', writeScope: 'space', }) async function checkClaim(claim: string) { await beliefs.add(claim, { confidence: 0.5 }) const sources = await searchWeb(claim) for (const source of sources) { await beliefs.after(source.text, { tool: 'web_search' }) } const page = await beliefs.list({ query: claim }) return page.beliefs.map(b => ({ text: b.text, confidence: b.confidence })) } checkClaim('Global AI market is worth $200B by 2030').then(console.log) ``` ### Decision Support (Advanced) Use `moves` and `clarity` to build a system that tells you when you have enough information to make a decision, and what you should investigate next. ```ts const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'decision-support', writeScope: 'space', }) async function decisionLoop(question: string) { await beliefs.add(question, { type: 'goal' }) while (true) { const context = await beliefs.before(question) if (context.clarity > 0.8) { return { recommendation: context.beliefs, confidence: context.clarity } } const nextMove = context.moves[0] if (!nextMove) break console.log(`Investigating: ${nextMove.target} (value: ${nextMove.value})`) const result = await callLLM( `${context.prompt}\n\nInvestigate: ${nextMove.target}` ) await beliefs.after(result) } } decisionLoop('Should we enter the European market?').then(console.log) ``` --- ## API Cheatsheet | Method | What it does | Returns | |--------|-------------|---------| | `before(input?)` | Get current beliefs + next moves | `BeliefContext` | | `after(text, { tool? })` | Feed agent output, extract beliefs | `BeliefDelta` | | `add(text, opts?)` | Assert a belief, goal, or gap | `BeliefDelta` | | `add([...items])` | Assert multiple in one request | `BeliefDelta` | | `resolve(text)` | Mark a gap as resolved (exact text match) | `BeliefDelta` | | `retract(id, reason?)` | Retract a belief (stays in graph) | `BeliefDelta` | | `remove(id)` | Delete a belief entirely | `BeliefDelta` | | `reset()` | Clear all state in this scope | `{ removed }` | | `read()` | Full world state with clarity + moves | `WorldState` | | `snapshot()` | Lightweight state without clarity/moves | `BeliefSnapshot` | | `list({ query, filter, limit })` | Paged search by keyword + filters | `BeliefList` | | `trace(beliefId?)` | Audit trail of belief changes | `TraceEntry[]` | Belief types: `claim`, `assumption`, `evidence`, `risk`, `gap`, `goal` --- ## Troubleshooting **`BetaAccessError: beliefs is in private beta…`** Either your API key is missing from the environment, or it's not on the beta allowlist. Check that `BELIEFS_KEY` is exported in the shell you're running from, then verify the key at [Profile > API Keys](/profile/api-keys). New keys start with `bel_live_`. **`resolve()` didn't remove my gap** `resolve(text)` matches gap text exactly. Pass the same string you originally added, or call `read()` and copy the gap text from `state.gaps`. **HTTP 429: Rate limit exceeded** The API allows 60 requests/minute per key. Add a small delay between calls in loops, or batch your work into fewer turns. **Empty beliefs after `after()`** The text you passed might be too short or not contain extractable claims. Try passing a longer, more substantive output. The extraction works best with paragraphs of analysis, not single sentences. **`before()` returns empty state** This is expected on a fresh namespace. Beliefs accumulate as you call `after()` and `add()`. The first `before()` will always have zero beliefs. **Different agents not seeing each other's beliefs** Make sure both agents use the same `namespace` and a shared write scope. The `agent` parameter identifies who contributed, but `namespace` plus `writeScope: 'space'` determine the shared state. ```ts const a = new Beliefs({ apiKey, agent: 'agent-a', namespace: 'shared', writeScope: 'space' }) const b = new Beliefs({ apiKey, agent: 'agent-b', namespace: 'shared', writeScope: 'space' }) ``` **Need to see what the SDK is doing?** Enable debug mode to log every request and response: ```ts const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'debug-example', writeScope: 'space', debug: true, }) ``` --- # Reference ## Core API Source: https://thinkn.ai/dev/sdk/core-api Summary: The beliefs API: what each method does and what comes back. The SDK is a thin client over the belief engine. The core loop is `before`, `after`, and either `add` or `resolve` for explicit edits. That covers most use cases. Everything else (streaming, multi-agent, trust overrides, forecasting) is convenience for specific workflows. The SDK does not modify your agent, decide what it does, or sit in the critical path of your LLM calls. It observes, extracts, and surfaces. When you call `before` and `after`, the engine handles extraction, linking (supports/contradicts/derives), deduplication, fusion across multiple agents, and provenance recording, and surfaces decision aids you can route on (`clarity`, `moves`). ``` Your Agent Loop │ ├── beliefs.before() ←── get context + moves │ ├── agent.run() ←── your agent, unchanged │ └── beliefs.after() ←── feed observation → extract → fuse ``` ## Setup ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'project-alpha', writeScope: 'space', }) ``` That's it. The only required option is `apiKey`. For multi-agent systems, add `agent` and `namespace`: ```ts const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'research-agent', namespace: 'project-alpha', writeScope: 'space', }) ``` | Option | Default | What it does | |--------|---------|-------------| | `apiKey` | None | **Required.** Your API key. | | `agent` | `'agent'` | Who is contributing beliefs. Use different names for different agents sharing a namespace. | | `namespace` | `'default'` | Developer-facing isolation boundary. Each namespace maps to its own backing workspace. | | `thread` | None | Bind a thread for `writeScope: 'thread'`. Use this for per-conversation or per-task memory. | | `writeScope` | `'thread'` | Which layer is authoritative: `'thread'`, `'agent'`, or `'space'`. See [Scoping](/dev/sdk/scoping). | | `contextLayers` | Depends on `writeScope` | Which layers `before()` and `read()` merge. Thread defaults to `['self', 'agent', 'space']`. | | `baseUrl` | `'https://www.thinkn.ai'` | Override the API origin for local or self-hosted environments. | | `timeout` | `120000` | Request timeout in ms. | | `maxRetries` | `2` | Auto-retries on 429/5xx with exponential backoff. | | `debug` | `false` | Logs every request and response to console. | For copy-paste examples, `writeScope: 'space'` is the simplest setup. The SDK default is `writeScope: 'thread'`, which is ideal for chat and session memory but requires a bound thread. ### `beliefs.withThread(threadId)` Bind a thread later while preserving the rest of the client config: ```ts const baseBeliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'support', writeScope: 'thread', }) const beliefs = baseBeliefs.withThread(conversationId) ``` --- ## `beliefs.before(input?)` Get the agent's current understanding before it acts. ```ts const context = await beliefs.before('Research the AI tools market') ``` Returns: ```json { "prompt": "{\"state\":{\"goals\":[\"Determine total addressable market\"],\"claims\":[{\"text\":\"AI tools market is valued at $4.2B\",\"confidence\":0.85},{\"text\":\"GitHub Copilot has dominant market share\",\"confidence\":0.85}],\"phase\":\"researching\",\"uncertainty\":0.58},\"gaps\":[\"Missing APAC market data\"],\"contradictions\":[]}", "beliefs": [ { "id": "xK9mR2vL3pT4nW8q", "text": "AI tools market is valued at $4.2B", "confidence": 0.80, "type": "claim" }, { "id": "pT4nW8qJ5mR7vL2x", "text": "GitHub Copilot has dominant market share", "confidence": 0.85, "type": "claim" } ], "goals": ["Determine total addressable market"], "gaps": ["Missing APAC market data"], "clarity": 0.42, "moves": [ { "action": "research", "target": "xK9mR2vL3pT4nW8q", "reason": "Market size has one source; verify with a second", "value": 0.7 } ] } ``` **What you use:** Inject `context.prompt` into your agent's system prompt. Check `context.clarity` to decide whether to keep investigating or act. Follow `context.moves[0]` for the highest-value next step. `context.prompt` is a JSON-serialized belief brief. Drop it directly into your system prompt. Don't `JSON.parse` it, don't try to template fields out of it. The string itself is the artifact your agent reads. --- ## `beliefs.after(text, options?)` Feed the agent's output. Beliefs are extracted, conflicts detected, and the world model updated automatically. ```ts const delta = await beliefs.after(agentOutput) ``` For tool results, tag the tool name so the system knows the source: ```ts await beliefs.after(JSON.stringify(searchResults), { tool: 'web_search' }) ``` You can also label the source explicitly. This gets stored on each extracted belief and appears in the trace: ```ts await beliefs.after(agentOutput, { source: 'quarterly-earnings-call' }) ``` Returns: ```json { "changes": [ { "action": "created", "beliefId": "hV7bQ3kN9yU6wE4r", "text": "European market is 28% of global revenue" }, { "action": "updated", "beliefId": "xK9mR2vL3pT4nW8q", "text": "AI tools market is valued at $4.2B", "confidence": { "before": 0.80, "after": 0.75 }, "reason": "New regional data suggests original estimate may exclude segments" } ], "clarity": 0.58, "readiness": "medium", "moves": [ { "action": "gather_evidence", "target": "xK9mR2vL3pT4nW8q", "reason": "Market size estimate weakened; need authoritative source", "value": 0.8 } ], "state": { "beliefs": ["..."], "goals": ["..."], "gaps": ["..."], "clarity": 0.58, "..." : "..." } } ``` **What you use:** Check `delta.readiness` to route. `'high'` means act, `'low'` means keep investigating. `delta.changes` tells you exactly what the system learned. `delta.state` is the full world state if you need it. --- ## `beliefs.observe(envelope)` Structured-input primitive for non-agent surfaces: UI events, document edits, manual ingest, hooks, integration webhooks. Where `before()` / `after()` model an agent loop, `observe()` carries the engine's full provenance vocabulary (`surface`, `kind`, `tags`, `agentId`, `actor`) directly so non-agent consumers don't smuggle metadata through a `source` string. ```ts await beliefs.observe({ content: 'User dragged "Pricing" onto the Decisions frame.', surface: 'canvas', kind: 'block_moved', actor: 'user', tags: [`block:${blockId}`, `frame:${frameId}`], }) ``` Envelope fields: `content` (required, non-empty), `actor` (default `'assistant'`), `surface` (e.g. `'chat'`, `'canvas'`, `'document'`, `'tool'`, `'integration'`), `kind` (sub-event tag like `'block_created'`, `'doc_written'`), `tags` (free-form provenance tags), `agentId`, `depth`, `signalFocused`. Returns: ```json { "success": true, "applied": true, "extractionStatus": "ok", "beliefsExtracted": 3, "edgesCreated": 1, "contradictionsDetected": 0, "gapsResolved": 0 } ``` `extractionStatus` is `'ok'`, `'empty'` (ran but produced no beliefs), or `'error'` (with `extractionError` set). `applied` is `true` only when a non-empty delta hit the world model. --- ## `beliefs.add(text, options?)` Assert something the agent knows. Use this to seed beliefs, set goals, or flag gaps. ```ts await beliefs.add('The market is $4.2B', { confidence: 0.85, source: 'IDC Q4 2025 Tracker', }) await beliefs.add('Determine total addressable market', { type: 'goal' }) await beliefs.add('Missing APAC market data', { type: 'gap' }) ``` Options: `confidence` (0–1, default 0.5), `type` (`'claim'`, `'assumption'`, `'evidence'`, `'risk'`, `'gap'`, `'goal'`), `source` (where this belief came from: document name, URL, tool, etc.), `evidence` (source text), `supersedes` (text of a belief this replaces), `mode` (see below). ### `mode` option Three ingest paths are available; the default behavior is unchanged. | `mode` | Engine route | When to use | |--------|-------------|-------------| | omitted | `/ingest` | Default. The engine dispatches based on body shape. | | `'claims'` | `/ingest/claims` | Deterministic hot path. No LLM invocation; faster when you already know the structured shape. | | `'output'` | `/ingest/output` | LLM-gated extraction from raw text. Equivalent to `after(text)`, exposed on `add()` for symmetry. | ```ts // Default (backwards-compatible, same behavior as v0.6.0): await beliefs.add('Market is $4.2B', { confidence: 0.85 }) // Explicit deterministic path (fewer LLM calls): await beliefs.add('Market is $4.2B', { mode: 'claims', confidence: 0.85 }) // Extract beliefs from raw output: await beliefs.add(longTranscript, { mode: 'output', source: 'agent-trace' }) ``` `mode: 'output'` only works with the single-text overload; passing it to `add(items[], { mode: 'output' })` throws `TypeError`. For batch ingestion of structured items, use `mode: 'claims'` or omit `mode`. Returns `BeliefDelta`, same shape as `after()`. --- ## `beliefs.add(items)` Assert multiple items in a single request. All items are processed as one atomic delta. ```ts await beliefs.add([ { text: 'Market is $4.2B', confidence: 0.8, source: 'IDC Q4 2025 Tracker' }, { text: 'Missing APAC data', type: 'gap' }, { text: 'Determine TAM', type: 'goal' }, ]) ``` Returns `BeliefDelta`, same shape as `after()`. --- ## `beliefs.resolve(text)` Mark a gap as resolved. ```ts const delta = await beliefs.resolve('Missing APAC market data') ``` Returns `BeliefDelta`. --- ## `beliefs.retract(beliefId, reason?)` Retract a belief. The belief stays in the graph with `lifecycle: 'retracted'` so the audit trail is preserved. Use this when the agent no longer believes something. ```ts await beliefs.retract('xK9mR2vL3pT4nW8q', 'Superseded by updated market data') ``` The retracted belief remains visible in `read()` and `snapshot()` with `lifecycle: 'retracted'`. The reason appears in `trace()` as the `reasoning` field. Returns `BeliefDelta`. --- ## `beliefs.remove(beliefId)` Delete a belief from the graph entirely. A final ledger entry is recorded for traceability. Use this for cleanup of garbage or accidental beliefs. ```ts await beliefs.remove('xK9mR2vL3pT4nW8q') ``` Unlike `retract()`, the belief is gone from state after removal. Use `trace()` to see the removal in the audit trail. Returns `BeliefDelta`. --- ## `beliefs.removeWhere(filter)` Bulk-remove every belief that originated from a specific provenance reference. Used when an upstream block, document, or message is deleted and its derived beliefs should go with it. ```ts const { removed } = await beliefs.removeWhere({ source: `block:${blockId}` }) console.log(`Retracted ${removed} beliefs`) ``` The `source` filter is a `':'` string. **Currently only `'block:'` is supported.** The call throws `BeliefsError('remove_where/unsupported_source')` for any other kind. Engine support for additional kinds (`agent:`, `thread:`, `source:`) is in flight; until it lands, retract individual beliefs with `retract()` or `remove()`. Returns `{ success: true, removed: number, source: string }`. `removed` is the count of beliefs retracted (zero when nothing matched). --- ## `beliefs.reset()` Remove all beliefs, goals, gaps, and intents in this scope. Every removal is recorded in the ledger. ```ts const { removed } = await beliefs.reset() console.log(`Cleared ${removed} items`) ``` Returns `{ removed: number }`, the count of items removed. Reset clears everything in the current authoritative scope. For `writeScope: 'thread'` that means one thread. For `writeScope: 'agent'` it means one agent's durable memory. For `writeScope: 'space'` it clears the shared namespace-wide state. The audit trail is preserved in the ledger, but the state itself is wiped clean. --- ## `beliefs.read()` Full world state with clarity, moves, and a serialized prompt. ```ts const world = await beliefs.read() ``` Returns: ```json { "beliefs": [ { "id": "xK9mR2vL3pT4nW8q", "text": "AI tools market is valued at $6.8B", "confidence": 0.95, "type": "claim" }, { "id": "pT4nW8qJ5mR7vL2x", "text": "GitHub Copilot market share has declined to 32%", "confidence": 0.90, "type": "claim" } ], "goals": ["Determine total addressable market"], "gaps": ["Missing APAC market data"], "edges": [ { "type": "contradicts", "source": "xK9mR2vL3pT4nW8q", "target": "hV7bQ3kN9yU6wE4r", "confidence": 0.8 } ], "contradictions": ["AI tools market is valued at $4.2B vs AI tools market is valued at $6.8B"], "clarity": 0.72, "moves": [ { "action": "research", "target": "xK9mR2vL3pT4nW8q", "reason": "APAC data would complete the picture", "value": 0.6 } ], "prompt": "{\"state\":{\"goals\":[...],\"claims\":[...],\"phase\":\"researching\"},\"gaps\":[...],\"contradictions\":[...]}" } ``` --- ## `beliefs.snapshot()` Same as `read()` but faster: skips computing clarity, moves, and prompt. Use when you only need the raw state. ```ts const snap = await beliefs.snapshot() console.log(`${snap.beliefs.length} beliefs, ${snap.gaps.length} gaps`) ``` Returns beliefs, goals, gaps, edges, and contradictions. No clarity, moves, or prompt. --- ## `beliefs.stateAt(options?)` Replay belief state at a specific point in time. Use one of `step`, `traceId`, or `asOf` to select the replay window; pass `beliefId` and/or `agentId` to narrow the deltas considered. Returned state is the same shape as `snapshot()`. ```ts // State as of 24 hours ago: const yesterday = new Date(Date.now() - 24 * 3600_000).toISOString() const { state, appliedDeltas } = await beliefs.stateAt({ asOf: yesterday }) console.log(`Replayed ${appliedDeltas} deltas to reconstruct state`) // State after a specific trace: const replay = await beliefs.stateAt({ traceId: 'trace-abc-123' }) // State for one belief's history: const beliefHistory = await beliefs.stateAt({ beliefId: 'b-market-size' }) ``` Options: | Option | Type | What it does | |--------|------|--------------| | `beliefId` | `string` | Replay only this belief's deltas. | | `agentId` | `string` | Restrict to one agent's contributions. | | `step` | `number` | Replay every delta up to (and including) this seq number. | | `traceId` | `string` | Replay deltas in this trace plus everything before. | | `asOf` | `string` | ISO timestamp; replay deltas with `appliedAt ≤ this time`. | Returns: ```ts { state: BeliefSnapshot // same shape as snapshot() appliedDeltas: number // count of archived deltas replayed } ``` A workspace with no archive activity returns an empty state with `appliedDeltas: 0` rather than erroring. The cold-start case is honest, not a 404. --- ## `beliefs.graph(options?)` Render-focused projection of the belief graph: nodes, edges, contradictions, and aggregate stats. Use this when you need to draw the graph in a UI or analyze its shape. ```ts const projection = await beliefs.graph({ filter: { kinds: ['claim', 'goal'], minConfidence: 0.4, limit: 200 }, }) for (const node of projection.nodes) renderNode(node) for (const edge of projection.edges) renderEdge(edge) ``` Options: | Option | Type | What it does | |--------|------|--------------| | `filter.limit` | `number` | Cap on returned nodes (engine default applies if omitted). | | `filter.kinds` | `string[]` | Restrict to specific node kinds (`'claim'`, `'goal'`, `'gap'`, etc.). | | `filter.minConfidence` | `number` | Drop edges below this confidence (0–1). | | `filter.maxContradictions` | `number` | Cap on returned contradiction pairs. | | `scope` | `{ spaceId?, studioId?, sessionId? }` | Optional scope override; defaults to the scope bound at construction. | Returns: ```ts { nodes: GraphNode[] edges: GraphEdge[] contradictions: ContradictionPair[] stats: { nodeCount: number edgeCount: number contradictionCount: number edgesByLayer?: { explicit: number, ledger: number, contradiction: number, similarity: number, domain: number } } } ``` The `stats.edgesByLayer` breakdown tells you which kind of edge each one is: `explicit` are user-asserted, `ledger` are causal-history derived, `contradiction` and `similarity` are detected, `domain` are extension-supplied. --- ## `beliefs.list(options?)` Paged search over beliefs by query and filters. Supersedes the older `search(query)` method, which is now deprecated and will be removed in a future minor. ```ts const page = await beliefs.list({ query: 'market size', filter: { type: ['claim', 'goal'] }, limit: 25, }) for (const b of page.beliefs) render(b) if (page.nextCursor) // ...fetch the next page ``` Options: | Option | Type | What it does | |--------|------|--------------| | `query` | `string` | Full-text search. Falls back to a plain snapshot scan when omitted. | | `filter.type` | `string \| string[]` | Restrict to specific belief types (`'claim'`, `'goal'`, `'gap'`, etc.). | | `filter.source` | `string \| string[]` | Restrict to one or more sources. | | `filter.lifecycle` | `string \| string[]` | Restrict to specific lifecycle states. | | `limit` | `number` | Max items returned. Forwarded to the engine and re-enforced after client-side filtering. | | `cursor` | `string` | Forward-paginate via `nextCursor` from a prior response. | Returns `{ beliefs: Belief[], nextCursor?: string }`. --- ## `beliefs.get(beliefId)` Detail page for one belief: the belief itself plus inline supporting and contradicting relations, cross-belief links, history timeline, recommended next move, and an optional clarity sidebar. One method, one round-trip from the consumer's perspective; internally the SDK fans out to three engine endpoints in parallel. ```ts const detail = await beliefs.get('belief-abc123') renderHeader(detail.belief.text, detail.belief.clarity) for (const n of detail.relations.supporting) renderSupport(n) for (const n of detail.relations.contradicting) renderContradiction(n) if (detail.thinkingMove) renderRecommendedMove(detail.thinkingMove) ``` Returns: ```ts { success: boolean belief: { id, title?, text, category, kind, clarity, source, provenanceType?, updatedAt?, sourceId?, childEvidence, } relations: { supporting: GraphNeighbor[] contradicting: GraphNeighbor[] } links: BeliefDetailLink[] // cross-belief links shown in "Linked beliefs" history: BeliefDetailHistoryItem[] // most-recent-first thinkingMove: ThinkingMove | null // engine's recommended next move clarity?: BeliefDetailClarity // optional sidebar with insights + readiness durationMs: number } ``` Designed so a detail page can render entirely from one response. No follow-up calls needed for evidence, contradictions, or history. --- ## `beliefs.trace(beliefId?)` Audit trail. See every transition: what changed, when, why, and who changed it. ```ts const history = await beliefs.trace() ``` Returns: ```json [ { "action": "updated", "beliefId": "xK9mR2vL3pT4nW8q", "confidence": { "before": 0.80, "after": 0.95 }, "agent": "research-agent", "source": "IDC Q4 2025 Tracker", "timestamp": "2026-04-08T14:23:01Z", "reason": "IDC Q4 2025 tracker provided authoritative $6.8B figure" }, { "action": "created", "beliefId": "hV7bQ3kN9yU6wE4r", "agent": "research-agent", "source": "agent-output", "timestamp": "2026-04-08T14:22:45Z", "reason": "Extracted from European market analysis" } ] ``` Trace a single belief's history: ```ts const oneBeliefHistory = await beliefs.trace('xK9mR2vL3pT4nW8q') ``` --- ## Errors ```ts import Beliefs, { BetaAccessError, BeliefsError } from 'beliefs' ``` **`BetaAccessError`**: API key missing, invalid, or account lacks access (401/403). ```ts try { await beliefs.before(input) } catch (err) { if (err instanceof BetaAccessError) { console.log(err.signupUrl) // 'https://thinkn.ai/waitlist' } } ``` **`BeliefsError`**: server errors with structured codes and retry guidance. The SDK auto-retries transient errors (429, 5xx) with exponential backoff, so you only see these after retries are exhausted. ```ts try { await beliefs.after(result.text) } catch (err) { if (err instanceof BeliefsError) { console.log(err.code) // 'rate_limit/exceeded' console.log(err.retryable) // true } } ``` | HTTP | Error | Retryable | Example codes | |------|-------|-----------|---------------| | 400 | `BeliefsError` | No | `validation/invalid_json` | | 401/403 | `BetaAccessError` | No | `auth/missing_key` | | 429 | `BeliefsError` | Yes | `rate_limit/exceeded` | | 5xx | `BeliefsError` | Yes | `internal/error` | For the full code catalog, retry semantics, and a complete handling pattern, see [Errors](/dev/sdk/errors). --- ## Types ### Belief The core unit. Every claim, assumption, and risk is a belief with a confidence score. ```ts { id: string text: string confidence: number // 0–1 type: string // 'claim', 'assumption', 'evidence', 'risk', 'gap', 'goal' createdAt: string } ```
Additional fields These are present when the server provides richer data. You don't need them to get started. | Field | Type | What it tells you | |-------|------|-------------------| | `label` | `string` | Semantic label: `'limiting-belief'`, `'load-bearing'`, etc. | | `evidenceWeight` | `number` | How much evidence backs this belief. `0` = uninvestigated prior. | | `distribution` | `string` | `'claim'` (true/false), `'category'` (multinomial), `'measurement'` (numeric) | | `lifecycle` | `string` | `'active'`, `'retracted'`, `'invalidated'`, `'expired'`, `'resolved'` | | `provenance` | `string` | `'user-created'`, `'research-discovered'`, `'chat-extracted'`, `'agent-generated'` | | `source` | `string` | Where this belief came from: document name, URL, tool, agent output label. | | `updatedAt` | `string` | Last modification timestamp | `confidence` alone doesn't tell you how well-founded a belief is. Confidence `0.5` with `evidenceWeight: 0` means no one has looked. Confidence `0.5` with `evidenceWeight: 40` means extensive evidence but genuine uncertainty. Use `evidenceWeight` to distinguish "unknown" from "uncertain."
### Move A suggested next action, ranked by expected information gain. ```ts { action: string // 'research', 'gather_evidence', 'clarify', 'validate_assumption', etc. target: string // which belief this move targets reason: string // why this is the best next step value: number // expected information gain (0–1) executor?: string // 'agent', 'user', or 'both' } ``` ### Edge A relationship between two beliefs. ```ts { type: string // 'supports', 'contradicts', 'supersedes', 'derived_from', 'depends_on' source: string target: string confidence: number } ``` ### DeltaChange What happened to a single belief during a mutation. ```ts { action: string // 'created', 'updated', 'removed', 'resolved' beliefId: string text: string confidence?: { before?: number, after?: number } reason?: string source?: string // where this change originated from } ```
Clarity channels When available, clarity breaks down into four dimensions you can access via `channels` on `BeliefContext`, `BeliefDelta`, or `WorldState`: ```ts const context = await beliefs.before(input) if (context.channels) { console.log(context.channels.knowledgeCertainty) // how confident in current knowledge console.log(context.channels.coverage) // how much of the goal space is covered console.log(context.channels.coherence) // consistency across beliefs console.log(context.channels.decisionResolution) // how well decisions are resolved } ```
## Patterns Source: https://thinkn.ai/dev/sdk/patterns Summary: How to structure your agent loop with beliefs: single-turn, multi-turn, streaming, tool-aware, multi-agent, and the smaller patterns that compose with them. Every agent using beliefs follows the same `before → act → after` cycle. The difference is how you arrange that cycle for your use case. These patterns assume you already chose an appropriate scope. For copy-paste examples, `writeScope: 'space'` is the simplest starting point. For chat apps, bind `writeScope: 'thread'` with `thread` or `beliefs.withThread(threadId)`. See [Scoping](/dev/sdk/scoping). Snippets below use `callLLM(systemPrompt, userMessage)` as a stand-in for your model. Replace it with whichever framework you ship on (Vercel AI, Anthropic SDK, OpenAI, plain fetch; see the [Hack Guide](/dev/tutorial/hack-guide) for working recipes). The point of these examples is the belief flow. ## Choosing a loop pattern ``` ┌─ Is this a single request/response? ──→ Single-turn │ ├─ Does the agent use tools? ──→ Tool-aware │ ├─ Does the agent stream output? ──→ Streaming │ ├─ Should the agent loop until confident? ──→ Multi-turn │ └─ Do multiple agents collaborate? ──→ Multi-agent ``` Most production agents combine patterns: a multi-turn loop with streaming and tool use. Start with the simplest pattern that fits, then layer in complexity. --- ## Single-Turn The simplest integration. One `before`, one agent call, one `after`. ```ts async function answer(question: string) { const context = await beliefs.before(question) const result = await callLLM(context.prompt, question) const delta = await beliefs.after(result) return result } ``` **When to use:** Chatbots, Q&A, any request/response flow where you want to accumulate knowledge across interactions but don't need to loop within a single request. **What you get:** Beliefs accumulate across calls within the same scope (the same `thread` if you're thread-scoped, or the same `namespace` if you're space-scoped). The second time the user asks about a topic, `before()` returns richer context with existing beliefs, gaps, and moves. --- ## Multi-Turn (Clarity-Driven) Loop until the agent has enough confidence to act. Use `clarity` as the stopping condition. ```ts async function research(question: string) { await beliefs.add(question, { type: 'goal' }) for (let turn = 0; turn < 10; turn++) { const context = await beliefs.before(question) // Stop when clarity is high enough if (context.clarity > 0.7) { return { beliefs: context.beliefs, clarity: context.clarity, gaps: context.gaps, } } // Follow the highest-value move const focus = context.moves[0]?.target ?? question const result = await callLLM(context.prompt, focus) const delta = await beliefs.after(result) console.log( `Turn ${turn + 1}: clarity ${delta.clarity.toFixed(2)}, ` + `${delta.changes.length} changes` ) } // Hit turn limit: return what we have return await beliefs.read() } ``` **When to use:** Research agents, fact-checkers, decision support: any task where the agent should investigate until it has enough information. **Key decisions:** - **Clarity threshold:** `0.7` is a good starting point. Lower for exploratory tasks, higher for critical decisions. - **Turn limit:** always set a hard cap to prevent infinite loops. - **Move routing:** use `context.moves[0]` to direct the next investigation. The move with the highest `value` has the most expected information gain. --- ## Streaming Accumulate the full response, then call `after()` once when the stream completes. ```ts import { streamText } from 'ai' import { anthropic } from '@ai-sdk/anthropic' async function researchStream(question: string) { const context = await beliefs.before(question) const result = streamText({ model: anthropic('claude-sonnet-4-20250514'), system: context.prompt, prompt: question, }) let fullText = '' for await (const chunk of result.textStream) { process.stdout.write(chunk) fullText += chunk } // Call after() once with the complete text const delta = await beliefs.after(fullText) return { text: fullText, delta } } ``` In a Next.js route handler, use `onFinish`: ```ts export async function POST(req: Request) { const { messages } = await req.json() const lastMessage = messages[messages.length - 1]?.content ?? '' const context = await beliefs.before(lastMessage) const result = streamText({ model: anthropic('claude-sonnet-4-20250514'), system: context.prompt, messages, onFinish: async ({ text }) => { await beliefs.after(text) }, }) return result.toDataStreamResponse() } ``` Call `after()` exactly once per turn, after the stream completes. Do not call it on partial chunks. Each `after()` triggers full extraction and fusion against incomplete text, which produces duplicate beliefs, spurious contradictions, and ledger churn that's hard to clean up later. If you need live UI feedback during a stream, use `subscribe()` for projection updates instead. Let the final `after()` do the actual extraction. --- ## Tool-Aware When your agent uses tools, feed each tool result separately so beliefs update as evidence arrives mid-turn. ```ts const context = await beliefs.before(question) const message = await client.messages.create({ model: 'claude-sonnet-4-20250514', system: context.prompt, messages: [{ role: 'user', content: question }], tools: myTools, }) // Feed each tool result. Source is tracked per-belief for traceability. for (const block of message.content) { if (block.type === 'tool_use') { const result = await executeTool(block.name, block.input) await beliefs.after(JSON.stringify(result), { tool: block.name, source: `tool:${block.name}`, }) } else if (block.type === 'text') { await beliefs.after(block.text) } } ``` With the Vercel AI SDK and `maxSteps`: ```ts const { text, toolResults } = await generateText({ model: anthropic('claude-sonnet-4-20250514'), system: context.prompt, prompt: question, tools: myTools, maxSteps: 5, }) // Feed tool results individually for (const result of toolResults) { await beliefs.after(JSON.stringify(result.result), { tool: result.toolName }) } // Then feed the final text await beliefs.after(text) ``` **When to use:** Agents that call external APIs, search the web, query databases, or use any tools that return factual data. **Why per-tool?** Each tool result is a distinct observation with its own provenance. Feeding them individually lets the system attribute claims to the right tool, detect when a tool result *contradicts* an existing belief, and notice when a tool result *resolves* a gap. If you concatenate everything into one `after()` call, those per-source contradictions and gap-resolutions get smeared together and the relationships are missed. --- ## Multi-Agent Multiple agents contribute to the same shared belief state. They share a `namespace` and `writeScope: 'space'`, but use different `agent` identifiers so contributions are attributed. ```ts const researcher = new Beliefs({ apiKey, agent: 'researcher', namespace: 'market-analysis', writeScope: 'space', }) const critic = new Beliefs({ apiKey, agent: 'critic', namespace: 'market-analysis', writeScope: 'space', }) // Researcher gathers evidence const researchContext = await researcher.before('AI tools market size') const findings = await callLLM(researchContext.prompt, 'Research AI tools market') await researcher.after(findings) // Critic challenges the findings const criticContext = await critic.before('Challenge these market findings') const critique = await callLLM(criticContext.prompt, 'Find weaknesses') await critic.after(critique) // Both see the same world state const world = await researcher.read() console.log(`Contradictions: ${world.contradictions.length}`) console.log(`Total beliefs: ${world.beliefs.length}`) ``` **When to use:** Debate systems, red-team/blue-team, supervisor/worker patterns, any architecture with multiple agents reasoning about the same domain. **How it works:** All agents in the same namespace with `writeScope: 'space'` share one authoritative state. When the critic adds beliefs that contradict the researcher's findings, the system detects the contradiction automatically. If you want private agent memory plus shared background, switch to `writeScope: 'agent'`. --- ## Combining Patterns Most production agents combine patterns. Here's a multi-turn streaming agent with tool use: ```ts async function deepResearch(question: string) { await beliefs.add(question, { type: 'goal' }) for (let turn = 0; turn < 5; turn++) { const context = await beliefs.before(question) if (context.clarity > 0.8) break const { text, toolResults } = await generateText({ model: anthropic('claude-sonnet-4-20250514'), system: context.prompt, prompt: context.moves[0]?.target ?? question, tools: myTools, maxSteps: 3, }) for (const result of toolResults) { await beliefs.after(JSON.stringify(result.result), { tool: result.toolName }) } await beliefs.after(text) } return await beliefs.read() } ``` --- ## Smaller patterns Once the loop is in place, these are the moves and accessors you'll reach for most. ### Clarity-driven routing Branch on `context.clarity` to decide what to do next: ```ts const context = await beliefs.before(input) if (context.clarity < 0.3) { await runResearch(context.gaps) } else if (context.clarity > 0.7) { await draftRecommendations(context.beliefs) } else { await investigateGaps(context.gaps) } ``` For coarser routing, `delta.readiness` returns `'low' | 'medium' | 'high'`: a categorical projection of the underlying 0–1 clarity score, useful when you want simple branching without picking your own thresholds. ### Confidence gating Only act on beliefs above a confidence threshold: ```ts const world = await beliefs.read() const strong = world.beliefs.filter(b => b.confidence > 0.7) const weak = world.beliefs.filter(b => b.confidence <= 0.7) // Use strong beliefs in the response // Flag weak beliefs for further investigation ``` ### Gap-driven research Read open gaps and use them to drive the next research action. The agent's next move is shaped by what it doesn't know, rather than only what the user asked: ```ts const context = await beliefs.before(input) for (const gap of context.gaps) { const result = await searchTool.run(gap) await beliefs.after(result, { tool: 'search' }) } ``` ### Custom assertion with evidence When you have domain-specific knowledge, assert it directly with evidence and supersession: ```ts await beliefs.add('Market is $6.8B', { confidence: 0.92, evidence: 'IDC Q4 2025 tracker, 2400 enterprise survey', supersedes: 'Market is $4.2B', }) ``` Explicit assertions take precedence over auto-extracted beliefs when they conflict. ### Inspecting the trace Use the trace to debug belief transitions: ```ts const history = await beliefs.trace() for (const entry of history) { console.log(`${entry.timestamp} | ${entry.action}`) if (entry.confidence) { console.log(` ${entry.confidence.before} → ${entry.confidence.after}`) } if (entry.reason) console.log(` reason: ${entry.reason}`) } ``` For replay-shaped reads ("what did the world look like at time T?"), use [`beliefs.stateAt({ asOf })`](/dev/sdk/core-api) instead. ## Scope reads Source: https://thinkn.ai/dev/sdk/reads Summary: Plain-English summaries of gaps, decisions, goals, risks, insights, evidence, intents, and contradictions. Eight top-level methods give you focused projections of the current belief state. Each returns a flat `Summary[]`: pre-shaped lists ready to render in a UI without further processing or normalization. Raw underlying scores live under each summary's optional `internals` field for power users; the default shape stays free of engine jargon. All eight share the same option base (`agentId`, `limit`, `since`), plus per-method filters where relevant. | Method | Returns | Per-method filter | |--------|---------|-------------------| | `beliefs.gaps(opts?)` | `GapSummary[]` | `priority?: 'low' \| 'medium' \| 'high'` | | `beliefs.decisions(opts?)` | `DecisionSummary[]` | None | | `beliefs.goals(opts?)` | `GoalSummary[]` | None | | `beliefs.risks(opts?)` | `RiskSummary[]` | None | | `beliefs.insights(opts?)` | `InsightSummary[]` | None | | `beliefs.evidence(opts?)` | `EvidenceSummary[]` | `beliefId?: string` | | `beliefs.intents(opts?)` | `IntentSummary[]` | None | | `beliefs.contradictions(opts?)` | `ContradictionSummary[]` | `severity?: 'low' \| 'medium' \| 'high'` | ## Quickstart ```ts const gaps = await beliefs.gaps({ priority: 'high', limit: 5 }) const risks = await beliefs.risks() const recentEvidence = await beliefs.evidence({ since: '2026-04-01T00:00:00Z' }) for (const gap of gaps) { console.log(`[${gap.priority}] ${gap.summary}`) if (gap.suggestion) console.log(` → ${gap.suggestion}`) } ``` ## Common options | Option | Type | What it does | |--------|------|--------------| | `agentId` | `string` | Restrict to a single agent's contributions. Defaults to the SDK's bound `agent`. | | `limit` | `number` | Cap returned items. Server applies its own cap if you don't specify. | | `since` | `string` | ISO timestamp. Filter items at-or-after this time. | | `signal` | `AbortSignal` | Abort the request mid-flight. | ## Summary shapes Every summary follows the `Summary` template: `id`, plain-English `summary`, optional `suggestion`, optional `relatedBeliefs`, plus type-specific fields. Internals are opt-in. ### `GapSummary` ```ts { id: string summary: string priority: 'low' | 'medium' | 'high' openSince: string // ISO timestamp suggestion?: string relatedBeliefs?: string[] internals?: { rawConfidence?: number; evidenceWeight?: number } } ``` The priority inversion is intentional. A *low*-confidence gap means the agent has little evidence either way, so the question is wide open and worth investigating. That gets `priority: 'high'`. A *high*-confidence gap means the agent is close to resolving it on its own, so the priority drops. ### `DecisionSummary` ```ts { id: string summary: string status: 'tentative' | 'committed' | 'reversed' decidedAt: string commitment: 'loose' | 'firm' | 'revoked' suggestion?: string relatedGoals?: string[] relatedBeliefs?: string[] } ``` Distinct from `moves.list()` (recommendations) and `trace()` (audit log). Decisions are committed intent: "I will do X." ### `EvidenceSummary` ```ts { id: string summary: string observedAt: string source: string // agent or source identifier direction: 'supports' | 'contradicts' | 'neutral' strength: 'weak' | 'medium' | 'strong' relatedBeliefs?: string[] } ``` `strength` is derived from how much this observation shifted the belief: the engine's reading of "how meaningful was this observation." ### `IntentSummary` ```ts { id: string summary: string kind: 'goal' | 'decision' | 'constraint' status: 'active' | 'completed' | 'abandoned' | 'tentative' | 'committed' | 'reversed' | 'relaxed' | 'removed' activeSince: string progress?: number // 0–1, mostly for goals confidence?: 'low' | 'medium' | 'high' // mostly for decisions relatedBeliefs?: string[] relatedGoals?: string[] } ``` The unified shape across all three intent kinds. Filter by `kind` client-side, or use `goals()`/`decisions()` for kind-specific shapes. ### `GoalSummary` ```ts { id: string summary: string status: 'active' | 'completed' | 'abandoned' activeSince: string progress?: number // 1 if completed, 0 if abandoned relatedBeliefs?: string[] } ``` ### `RiskSummary` ```ts { id: string summary: string severity: 'low' | 'medium' | 'high' // impact if it occurs likelihood: 'low' | 'medium' | 'high' | 'certain' identifiedAt: string suggestion?: string relatedBeliefs?: string[] } ``` For an expected-impact ranking, map both labels to numbers (e.g. `low: 1, medium: 2, high: 3, certain: 4`) and sort by their product. Both fields are categorical labels in the summary shape; the `internals` field carries the underlying numeric scores if you need them. ### `InsightSummary` ```ts { id: string summary: string kind: 'contradiction' | 'missing_evidence' | 'ambiguity' | 'leap' status: 'active' | 'acknowledged' | 'dismissed' relatedBeliefs: string[] severity: 'low' | 'medium' | 'high' createdAt: string suggestion?: string } ``` Output from the clarity detector. These are *meta*-observations about the belief state itself, not beliefs. ### `ContradictionSummary` ```ts { id: string summary: string severity: 'low' | 'medium' | 'high' beliefs: [string, string] // pair of belief IDs in conflict suggestion?: string detectedAt?: string } ``` The pair is unordered semantically. `beliefs[0]` and `beliefs[1]` are both implicated. All eight methods require `apiKey` or `scopeToken` auth. `serviceToken` callers are rejected. See [Auth](/dev/sdk/auth). ## Moves: SDK Source: https://thinkn.ai/dev/sdk/moves Summary: List, generate, and act on recommended next moves. A move is an engine-recommended next action: the answer to "given what the agent currently believes, what should it investigate next?", ranked by expected information gain. `beliefs.moves.*` wraps the engine's recommender. See [Moves (concept)](/dev/core/moves) for the model behind the surface; this page covers the SDK methods. `forecast(action)` and `cascade(action)` look similar but answer different questions. `forecast` projects the action's value on **the current agent's** belief state: "how much will this clarify *my* picture?" `cascade` projects the same action across **other agents' beliefs** via the influence matrix: "if I do this, how much churn does it create for the rest of the swarm?" Use forecast for self-directed planning; use cascade when you're coordinating multi-agent work. ## `beliefs.moves.list(options?)` Get the currently-ranked moves for the bound scope. Moves come back highest-priority first. ```ts const moves = await beliefs.moves.list({ topN: 3 }) for (const m of moves) { console.log(m.action, m.rationale, m.expectedDeltaH) } ``` Options: | Option | Type | What it does | |--------|------|--------------| | `topN` | `number` | Cap the returned slice. Server-side ranking is unchanged; this is a client-side trim. | Returns `ThinkingMove[]`. ## `beliefs.moves.generate(options)` Ask the recommender for a fresh move targeting a specific belief. Use this when you want a move *now* (e.g., the user just opened a belief detail page) rather than waiting for one to appear in `list()`. ```ts const result = await beliefs.moves.generate({ beliefId: 'belief-abc123', includeJustification: true, }) if (result.move) { showRecommendation(result.move, result.move.justification) } else if (result.reason === 'belief_complete') { showDoneState() } ``` Options: | Option | Type | What it does | |--------|------|--------------| | `beliefId` | `string` | **Required.** Belief to target. | | `targetId` | `string` | Alias for `beliefId`. `beliefId` wins if both are set. | | `includeJustification` | `boolean` | Attach the engine's full justification payload to the move. | | `sessionId` | `string` | Bind to a specific session for analytics. | Returns: ```ts { success: true move: ThinkingMoveWithJustification | null target: ResolvedCanonicalTarget reason?: 'belief_complete' // present when no move was generated durationMs: number } ``` `move: null` with `reason: 'belief_complete'` is the engine signaling "this belief is in good shape, nothing to recommend right now." ## `beliefs.moves.act(moveId, action, options?)` Record a user action on a move. The engine learns from accept/snooze/dismiss signals to improve future ranking. ```ts await beliefs.moves.act(move.id, 'accept') await beliefs.moves.act(move.id, 'snooze') await beliefs.moves.act(move.id, 'dismiss') ``` `action` is one of `'accept'`, `'snooze'`, `'dismiss'`. Any other value throws `TypeError`. Returns: ```ts { success: true move: ThinkingMove // updated with new status / resolvedAt durationMs: number } ``` ## `beliefs.moves.rank(options?)` Engine-ranked next-best moves over the current scope. Each entry surfaces the composite ranking score, expected info-gain, cost, and a cost-normalized ratio so callers can budget-cap on any axis. ```ts const ranked = await beliefs.moves.rank({ topN: 3, budget: 0.05 }) for (const m of ranked) { console.log(`${m.action}/${m.subType} → q=${m.qValue} cost=${m.cost} voi=${m.valueOfInformation}`) } ``` Options: `topN?` (default 5, max 50), `budget?` (filters out moves whose `cost` exceeds budget before ranking), `agentId?`, `signal?`. Returns `MoveRankingSummary[]`: ```ts { id, summary, targetId targetKind: 'claim' | 'goal' | 'gap' action: string // 'gather_evidence', 'clarify', ... subType: string // 'design_test', 'tradeoff_mapping', ... qValue: number // composite ranking score (higher = better) expectedInfoGain: number // expected info-gain from executing this move cost: number // USD / tokens / effort units valueOfInformation: number // info-gain / max(cost, 0.01) executor: 'agent' | 'user' | 'both' confidence: 'low' | 'medium' | 'high' } ``` ## `beliefs.moves.forecast(action, options?)` Project the expected value of a candidate action on the current belief state. The engine runs its predictive model forward from the current state and returns one summary. ```ts const summary = await beliefs.moves.forecast('gather_evidence', { depth: 3, rollouts: 50 }) console.log(`score=${summary.score} confidence=${summary.confidence}`) console.log(`will sharpen: ${summary.willAnswer.join(', ')}`) ``` Options: `depth?` (max 5), `rollouts?` (max 200), `maxTopics?`, `agentId?`, `signal?`. Returns `ForecastSummary`, same shape as `beliefs.forecast.predict` documented below. ## `beliefs.moves.cascade(action, options?)` Predict how a candidate action will ripple through *other* agents' beliefs via the fit influence matrix. Use this for multi-agent coordination: knowing whether your move will cause downstream churn before you make it. ```ts const cascade = await beliefs.moves.cascade('gather_evidence', { targetBeliefId: 'b-market-size', magnitude: 0.3, }) for (const shift of cascade.willShift) { if (shift.severity !== 'none') { console.warn(`Agent ${shift.agent}: ${shift.summary}`) } } ``` Options: `targetBeliefId?` (defaults to most-uncertain active belief), `magnitude?` (0–1, default 0.2), `maxAgents?`, `agentId?`, `signal?`. Returns `CascadeSummary`: ```ts { id, summary /** Aggregate cascade risk: 0 = isolated, 1 = every known agent feels it. */ score: number willShift: Array<{ agent: string summary: string severity: 'none' | 'low' | 'medium' | 'high' affectedBeliefs?: string[] }> confidence: 'low' | 'medium' | 'high' why: string } ``` Cold-start workspaces return `score: 0` with `confidence: 'low'`. The influence matrix has no co-observation evidence yet. --- ## `beliefs.forecast.predict(actions, options?)` Free-form action forecasting. Where `moves.forecast(action)` evaluates one action against the engine's recommended-move vocabulary, `forecast.predict(actions[])` runs the same predictive model against an arbitrary list of caller-supplied actions and returns one summary per input action, in input order. ```ts const forecasts = await beliefs.forecast.predict( ['gather_evidence_apac', 'design_test_market_size', 'reframe_question'], { horizon: 3, rollouts: 50 }, ) const ranked = [...forecasts].sort((a, b) => b.score - a.score) console.log(`Best action: ${ranked[0].summary} (score=${ranked[0].score})`) ``` Options: | Option | Default | What it does | |--------|---------|--------------| | `horizon` | `1` | Rollout depth per action (max 5). | | `rollouts` | `30` | Independent rollouts per action (max 200). | | `maxTopics` | None | Cap on belief topics surfaced in `willAnswer`. | | `agentId` | bound agent | Run the forecast as a different agent. | | `signal` | None | `AbortSignal` for cancellation. | Returns `ForecastSummary[]`: ```ts { id: string summary: string /** 0–1 expected value. Higher = more useful. */ score: number /** Plain-English belief topics most likely to sharpen under this action. */ willAnswer: string[] /** Confidence in the forecast itself, not the action. */ confidence: 'low' | 'medium' | 'high' /** Short human explanation. */ why: string suggestion?: string relatedBeliefs?: string[] } ``` `confidence` reflects how much evidence the engine's predictive model has accumulated for similar actions in this workspace, distinct from `score`. A high-`score` action with `confidence: 'low'` means "this looks great, but we haven't seen this action before, so the score is extrapolation rather than a track record." On a fresh workspace with no archived deltas, every forecast comes back with `confidence: 'low'` and a low `score`. That's the honest answer. The model has no evidence yet. Forecasts typically reach `confidence: 'medium'` after roughly 5–10 `after()` calls in the workspace, and `'high'` once dozens of similar actions have been observed. --- ## `ThinkingMove` shape ```ts { id: string targetId: string targetEntityType?: 'claim' | 'goal' | 'gap' | 'risk' | string targetEntityId?: string action: 'clarify' | 'gather_evidence' | 'resolve_uncertainty' | 'compare_paths' | string rationale: string expectedDeltaH: number // expected uncertainty reduction from acting on this move status: 'suggested' | 'accepted' | 'snoozed' | 'dismissed' | string suggestedModality?: string // hint about how to surface (e.g., 'inline', 'banner') qValue?: number // recommender's internal score, when available executor?: 'agent' | 'user' | 'both' createdAt: string updatedAt?: string resolvedAt?: string } ``` `expectedDeltaH` is the recommender's estimate of how much uncertainty this move reduces if accepted. It's the `value` field in the [concept doc](/dev/core/moves). The moves namespace requires `apiKey` or `scopeToken` auth. `serviceToken` callers cannot invoke `moves.*`. See [Auth](/dev/sdk/auth). ## Trust & tool reliability Source: https://thinkn.ai/dev/sdk/trust Summary: Override agent and source trust at runtime, and track which tools produce useful evidence. `beliefs.trust.*` lets the user adjust how much weight an agent or evidence source carries during fusion. Every override is a stated rating `(confidence, strength)` that the engine applies at fusion time. See [behavioral contracts](/dev/internals/contracts) for the predictability guarantee. ## When to use it - A user disables an agent: set `confidence: 0, strength: 100, lock: true`. - A user trusts a domain expert agent above the default: `confidence: 0.95, strength: 50`. - A source category (e.g. social media) should attenuate weight: set on `{ kind: 'source', id: 'social' }`. Without an override, agents start from the engine's calibrated prior. Overrides replace that prior for the targeted entity only. Every other agent and source is unaffected. ## `beliefs.trust.set(target, options)` Idempotent upsert. ```ts await beliefs.trust.set( { kind: 'agent', id: 'risk-bot' }, { confidence: 0.4, strength: 25 }, ) // Hard-disable an unreliable source (locked overrides never drift): await beliefs.trust.set( { kind: 'source', id: 'rumor-mill' }, { confidence: 0.0, strength: 100, lock: true }, ) ``` Parameters: | Field | Type | What it does | |-------|------|--------------| | `target.kind` | `'agent' \| 'source'` | Which entity type. | | `target.id` | `string` | Entity identifier. | | `options.confidence` | `number` | Mean of the user prior, in `[0, 1]`. | | `options.strength` | `number` | How sure you are in this rating. Higher = harder for learned data to drift the override. Use **10** for a weak preference (the engine can still adjust based on evidence), **100** for a confident rating, **500** for an immovable position you don't want learning to override. | | `options.lock` | `boolean` | When `true`, the engine never drifts this rating with newly-learned data. | Returns the persisted `TrustOverride`. ## `beliefs.trust.list(options?)` ```ts const all = await beliefs.trust.list() const agentsOnly = await beliefs.trust.list({ kind: 'agent' }) ``` Returns `TrustOverride[]`. ## `beliefs.trust.get(target)` ```ts const override = await beliefs.trust.get({ kind: 'agent', id: 'risk-bot' }) if (override) console.log(override.confidence, override.strength) ``` Returns `TrustOverride | null` (null when no override exists). ## `beliefs.trust.unset(target)` Remove an override. The entity reverts to the engine's calibrated prior at the next fusion step. ```ts const { removed } = await beliefs.trust.unset({ kind: 'agent', id: 'risk-bot' }) ``` Returns `{ removed: boolean }`. ## `TrustEntity` and `TrustOverride` shapes ```ts interface TrustEntity { kind: 'agent' | 'source' id: string } interface TrustOverride { entity: TrustEntity confidence: number // [0, 1] strength: number // ≥ 0 locked: boolean updatedAt: string } ``` The trust namespace requires `apiKey` or `scopeToken` auth. `serviceToken` callers cannot mutate user-scoped trust. See [Auth](/dev/sdk/auth). `set()` validates inputs synchronously. `confidence` must be in `[0, 1]`, `strength` must be non-negative, and `target.kind` must be `'agent'` or `'source'`. Invalid inputs throw `TypeError` before any network call. --- ## Tool reliability priors `beliefs.tools.*` records and reads running estimates of *tool* reliability, distinct from the agent/source trust above. Where trust overrides are user-stated ratings the engine applies at fusion, tool priors are *learned* estimates: the engine tracks, per `(tool, contextClass)` pair, how often each tool produces useful evidence, so the agent can pick the right tool for the job. `beliefs.tools.observe(envelope)` is **not** the same as the top-level `beliefs.observe(envelope)`. The top-level method runs the full belief-extraction pipeline on free-form content. `tools.observe` records a single success/failure outcome. Orders of magnitude lighter, and only for tool-reliability tracking. ### `beliefs.tools.observe(envelope)` Record a single tool outcome. Updates the running estimate in place and returns the new summary. ```ts const prior = await beliefs.tools.observe({ tool: 'web_search', success: true, contextClass: 'market-research', weight: 1.0, }) console.log(`web_search rate now ${prior.rate} (${prior.confidence})`) ``` Envelope: | Field | Type | What it does | |-------|------|--------------| | `tool` | `string` | **Required.** Tool identifier. | | `success` | `boolean` | **Required.** Did the tool produce useful evidence? | | `contextClass` | `string` | Optional context label (e.g. `'exploratory-research'`). | | `weight` | `number` | Optional weight (default 1.0). | | `agentId` | `string` | Override the bound agent. | | `signal` | `AbortSignal` | Cancellation. | Returns `ToolPriorSummary`. ### `beliefs.tools.priors(options?)` List current priors in scope. Filter to narrow. ```ts // Every prior in scope: const all = await beliefs.tools.priors() // Just one tool: const search = await beliefs.tools.priors({ tool: 'web_search' }) // Tool + context combo: const filtered = await beliefs.tools.priors({ tool: 'github_search', contextClass: 'code-review', }) ``` Options: `tool?`, `contextClass?`, `limit?`, `agentId?`, `signal?`. Returns `ToolPriorSummary[]`. ### `ToolPriorSummary` ```ts { id: string summary: string tool: string contextClass: string // empty string when uncategorized /** Mean success rate, 0–1. */ rate: number confidence: 'low' | 'medium' | 'high' | 'certain' /** 90% uncertainty interval on the mean. */ credibleInterval: { low: number; high: number } /** Total observations accumulated. */ observations: number suggestion?: string } ``` `rate` is "on average, this tool produces useful evidence `rate × 100`% of the time." `confidence` reflects how many observations back the estimate: `low` below 5 observations, `medium` 5–20, `high` 20+. `credibleInterval` narrows as observations accumulate. A common pattern: before calling a tool, fetch its prior. If `confidence === 'low'` and `rate < 0.3`, consider an alternative or attach a fallback. After the call, record the outcome with `tools.observe()` so the prior keeps learning. ## Streaming Source: https://thinkn.ai/dev/sdk/streaming Summary: SSE-based live updates for belief state and extraction pipelines. The SDK exposes two streaming primitives over Server-Sent Events: a state stream (`subscribe` / `events`) and a per-request extraction stream (`streamExtraction`). Both share one transport. Retries, abort propagation, and frame validation live in `HttpTransport`, so consumer code only sees parsed event objects. ## `beliefs.subscribe(handler, options?)` Push state changes into a callback. Returns a `Subscription` with `unsubscribe()` and a `done` promise that resolves when the stream closes. ```ts const sub = beliefs.subscribe( (event) => { if (event.type === 'belief_records_updated') { renderRecords(event.beliefRecords) } else if (event.type === 'belief_records_stale') { requestFullRefresh(event.reason) } }, { onError: (err) => console.error(err), onClose: () => console.log('stream closed'), }, ) // Tear down when the React effect / job ends: sub.unsubscribe() await sub.done ``` Options: | Option | Default | What it does | |--------|---------|--------------| | `onError` | `console.error` | Called when the underlying stream errors. | | `onClose` | None | Called once after a clean close. | | `dropHeartbeats` | `true` | Filter heartbeat frames before invoking `handler`. | | `signal` | None | `AbortSignal`; aborting cancels the SSE connection and resolves `done`. | The `signal` option pairs cleanly with React effect cleanup or React Query's abort handling. Pass the same controller and you don't need a `finally` block. ## `beliefs.events(options?)` Same stream, async-iterable face. Use this when you prefer `for await` or RxJS-style pipelines over the callback shape. ```ts const ac = new AbortController() for await (const event of beliefs.events({ signal: ac.signal })) { if (event.type === 'belief_records_updated') { renderRecords(event.beliefRecords) } } ``` Aborting the signal ends the iteration cleanly. ## `BeliefStreamEvent` Three frame types come off the state stream: ```ts type BeliefStreamEvent = | { type: 'belief_records_updated' sessionId: string spaceId: string viewId: string beliefRecords: BeliefRecord[] groupIndex: number totalGroups: number timestamp: string } | { type: 'belief_records_stale' sessionId: string spaceId: string reason: string timestamp: string } | { type: 'heartbeat'; timestamp: string } ``` Heartbeats are dropped by default. Set `dropHeartbeats: false` if you need keep-alive visibility for connection health UIs. `belief_records_stale` is a hint to refresh from `read()` or `snapshot()`. The engine has detected something it cannot incrementally project. ## `beliefs.streamExtraction(request, handler?, options?)` Per-request extraction of beliefs from a content payload. Yields `BeliefExtractionStreamChunk` frames as the engine extracts; ends with a `complete` chunk or an `error` chunk. ```ts const stream = beliefs.streamExtraction({ content: longTranscript, surface: 'voice', }) for await (const chunk of stream) { if (chunk.type === 'belief_event') { appendIncrementalBelief(chunk.event) } else if (chunk.type === 'complete') { finalize(chunk.eventCount) } else if (chunk.type === 'error') { showError(chunk.message) } } // Cancel mid-extraction: stream.cancel() ``` You can pass an optional `handler` callback in addition to iterating. Both run on every frame. Useful when one consumer wants the iterable for control flow and another wants a fire-and-forget callback (e.g., an analytics hook). ## `BeliefExtractionStreamChunk` ```ts type BeliefExtractionStreamChunk = | { type: 'belief_event' index: number event: { id: string baseEvent: string semanticLabel: string text: string actor: string sourceMessageIds: string[] } } | { type: 'complete'; lastProcessedMessageId: string; eventCount: number } | { type: 'error'; message: string } ``` Stream lifecycle: zero or more `belief_event` chunks → exactly one `complete` *or* one `error` chunk → stream ends. ## `beliefs.drift.watch(handler, options?)` SSE stream of per-agent reliability drift events. The engine snapshots a baseline at stream start, then emits a `DriftEvent` per (agent, evidence type) on each polling tick, with a `driftDetected` boolean derived from a drift threshold scaled to the baseline's own uncertainty. ```ts const sub = beliefs.drift.watch( (event) => { if (event.type === 'drift' && event.driftDetected) { alert(`${event.agentId} drift on ${event.evidenceType}: shift=${event.meanShift.toFixed(3)} > ci=${event.ciHalfWidth.toFixed(3)}`) } }, { targetAgentId: 'researcher', pollIntervalMs: 30_000 }, ) // Tear down when done: sub.unsubscribe() await sub.done ``` Options: | Option | Default | What it does | |--------|---------|--------------| | `targetAgentId` | None | Stream only this agent's events. | | `pollIntervalMs` | `10000` | Polling interval (min 1000, max 300000). | | `zThreshold` | `1.645` | Drift-threshold z-score (1.645 = 95% one-sided). | | `dropHeartbeats` | `true` | Filter heartbeat frames before invoking `handler`. | | `onError` | `console.error` | Stream-error callback. | | `onClose` | None | Called once on clean close. | | `signal` | None | `AbortSignal`. | ## `beliefs.drift.events(options?)` Same stream, async-iterable face. Same options minus `onError`/`onClose`/`handler`. ```ts for await (const event of beliefs.drift.events()) { if (event.type === 'drift' && event.driftDetected) { handleDrift(event) } } ``` ## `DriftStreamEvent` ```ts type DriftStreamEvent = | { type: 'drift' agentId: string evidenceType: string timestamp: string /** Engine-rated reliability at baseline (0–1). */ baselineMean: number /** Engine-rated reliability now (0–1). */ currentMean: number /** |currentMean - baselineMean|. */ meanShift: number /** Engine-computed 95% confidence interval half-width on the baseline. */ ciHalfWidth: number /** Engine-internal divergence metric (opaque scalar, see note below). */ klDivergence: number /** True when meanShift > ciHalfWidth: drift past baseline noise. */ driftDetected: boolean observationCount: number } | { type: 'heartbeat'; timestamp: string } ``` `driftDetected` scales the threshold to the baseline's own uncertainty, so you don't have to pick scalars yourself. Use `meanShift` for magnitude (most interpretable), `driftDetected` for routing, and `klDivergence` only for cross-agent comparison. It's an engine-internal scalar with no fixed unit. Both streams require `apiKey` or `scopeToken` auth. See [Auth](/dev/sdk/auth) for setup details. ## Scoping & Isolation Source: https://thinkn.ai/dev/sdk/scoping Summary: How namespace, writeScope, thread, and contextLayers shape belief state. The beliefs SDK has four scoping controls that determine how memory is isolated and shared: - `namespace`: the developer-facing workspace boundary - `writeScope`: the authoritative layer you mutate - `thread`: the bound thread ID for thread-scoped memory - `agent`: who contributed the mutation `contextLayers` then controls what `before()` and `read()` merge back into the prompt context. ### Quick decision tree - **Single app or prototype?** → `writeScope: 'space'`, share one namespace. - **Multi-agent collaborating on the same problem?** → `writeScope: 'space'`, distinct `agent` values, shared `namespace`. - **Per-conversation chat memory?** → `writeScope: 'thread'` (the default), bind `thread: conversationId`. - **Background worker with its own scratchpad?** → `writeScope: 'agent'`, distinct `agent` per worker. ## Namespace Namespaces are your top-level isolation boundary. Beliefs in different namespaces never interact. ```ts const projectA = new Beliefs({ apiKey, namespace: 'project-alpha', writeScope: 'space', }) const projectB = new Beliefs({ apiKey, namespace: 'project-beta', writeScope: 'space', }) ``` **Default:** `'default'` **Use for:** per-customer isolation, per-project separation, or per-environment separation. ## Authoritative Write Scopes ### `thread` Per-conversation or per-task memory. This is the SDK default. ```ts const beliefs = new Beliefs({ apiKey, namespace: 'support', thread: 'conv-a', writeScope: 'thread', }) ``` - Requires a bound `thread` - Best for chat apps, workflow runs, and task-specific reasoning - Default read layers: `['self', 'agent', 'space']`. `'self'` is the current thread, `'agent'` is this agent's durable memory, `'space'` is the shared namespace state. Reads merge all three. ### `agent` Durable per-agent memory inside a namespace. ```ts const researcher = new Beliefs({ apiKey, namespace: 'market-map', agent: 'researcher', writeScope: 'agent', }) ``` - Best for long-lived worker identity or agent-specific scratchpads - Keeps one agent's memory separate from another's - Default read layers: `['self', 'space']` ### `space` One shared memory for the whole namespace. ```ts const beliefs = new Beliefs({ apiKey, namespace: 'team-alpha', writeScope: 'space', }) ``` - Best for the simplest prototype or shared-team state - All callers in the namespace read and write the same authoritative layer - Default read layers: `['self']` ## Thread Binding If you use `writeScope: 'thread'`, bind a thread either in the constructor or later with `withThread()`. ### Bind in the constructor ```ts const beliefs = new Beliefs({ apiKey, namespace: 'support', thread: conversationId, writeScope: 'thread', }) ``` ### Bind later with `withThread()` ```ts const baseBeliefs = new Beliefs({ apiKey, namespace: 'support', writeScope: 'thread', }) const beliefs = baseBeliefs.withThread(conversationId) ``` This is useful when the framework gives you the thread or session ID at request time. ## Agent Identity `agent` answers "who said this?" It affects attribution and how contributions are weighted when sources disagree. It does **not** by itself decide whether memory is shared. ```ts const researcher = new Beliefs({ apiKey, namespace: 'team-alpha', agent: 'researcher', writeScope: 'space', }) const reviewer = new Beliefs({ apiKey, namespace: 'team-alpha', agent: 'reviewer', writeScope: 'space', }) ``` These two agents share the same authoritative state because they share the same `namespace` and `writeScope: 'space'`. ## Context Layers `contextLayers` controls what `before()` and `read()` merge together. | Write scope | Default read layers | What gets merged | |-------------|---------------------|------------------| | `thread` | `['self', 'agent', 'space']` | Current thread + this agent's memory + namespace-wide state | | `agent` | `['self', 'space']` | This agent's durable memory + namespace-wide state | | `space` | `['self']` | Just the namespace-wide shared state | The available layer values are `'self'`, `'agent'`, `'space'`, `'studio'`, and `'org'`. `'studio'` and `'org'` are reserved for hosted-platform integrations and don't apply to most app-builder consumers. You can override the defaults when you need a narrower or wider context: ```ts const beliefs = new Beliefs({ apiKey, namespace: 'support', thread: conversationId, writeScope: 'thread', contextLayers: ['self', 'space'], }) ``` ## Design Patterns ### Fastest prototype Use one shared namespace-wide state. ```ts const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'prototype', writeScope: 'space', }) ``` ### Chat application Use per-conversation memory. ```ts function createBeliefs(userId: string, conversationId: string) { return new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: userId, thread: conversationId, writeScope: 'thread', }) } ``` ### Durable per-agent memory with shared background ```ts const beliefs = new Beliefs({ apiKey, namespace: 'research-team', agent: 'analyst', writeScope: 'agent', }) ``` This keeps one agent's working memory separate while still reading shared namespace context. ### Shared workspace or debate ```ts const optimist = new Beliefs({ apiKey, namespace: 'market-debate', agent: 'optimist', writeScope: 'space', }) const skeptic = new Beliefs({ apiKey, namespace: 'market-debate', agent: 'skeptic', writeScope: 'space', }) ``` All participants write into the same shared state, so contradictions and supports are visible to everyone. ### Environment isolation ```ts const ENV = process.env.NODE_ENV ?? 'development' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: `${ENV}-${projectId}`, writeScope: 'space', }) ``` ## Rules of Thumb | Question | Recommendation | |----------|----------------| | Do I want separate memory per conversation? | Use `writeScope: 'thread'` and bind a thread | | Do I want one shared state for a whole project or team? | Use `writeScope: 'space'` | | Do I want each agent to keep its own durable memory? | Use `writeScope: 'agent'` with distinct `agent` values | | Do I need to scope one project away from another? | Use different `namespace` values | | Do I need broader background context than the current write scope? | Override `contextLayers` | ## Auth Source: https://thinkn.ai/dev/sdk/auth Summary: API keys and short-lived scope tokens. The SDK supports two authentication modes for app builders: long-lived `apiKey` (the default) and short-lived `scopeToken` (per-request HS256 JWT, intended for browser and edge runtimes). ## `apiKey`: server-side Use this from any trusted server runtime: Node, Workers, container backends, agent runtimes. The key is a `bel_live_…` token tied to your account. ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'project-alpha', writeScope: 'space', }) ``` The key is sent as a `Bearer` header on every request. Get it from **Profile > API Keys** in the Studio dashboard. See [Install](/dev/start/install) for the full setup walkthrough. Treat `apiKey` like any account credential: never embed in client bundles, never commit to source control, rotate immediately if leaked. Use `scopeToken` for browser/edge contexts. ## `scopeToken`: browser, edge, untrusted runtimes When you cannot put an `apiKey` on the device (browsers, edge functions, third-party plugins), mint a short-lived HS256 JWT on your server and hand it to the client. The SDK signs a fresh token from the configured claims on every request, so you never need to refresh tokens manually. ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ scopeToken: { secret: process.env.BELIEFS_SCOPE_TOKEN_SECRET!, claims: { scopeType: 'space', scopeId: currentWorkspace.id, actorUserId: currentUser.id, // optional: who is acting sessionId: currentSession.id, // optional: for session pinning }, }, namespace: 'project-alpha', }) await beliefs.before('What did the user just say?') ``` Claims: | Field | Required | What it does | |-------|----------|--------------| | `scopeType` | yes | The scope kind: `'space'`, `'studio'`, `'org'`, or `'user'`. | | `scopeId` | yes | The id of that scope (e.g. workspace id when `scopeType: 'space'`). | | `actorUserId` | no | Who is acting. Pass when you want every change attributed to a specific user. | | `sessionId` | no | Pin to a session for analytics and cross-session isolation. | | `visibleSpaceIds` | no | Array of additional space ids the actor can read across. | | `exp` | no | Per-token expiry override (Unix seconds). The SDK applies a sensible default if omitted. | Two optional fields on the outer `scopeToken` config tune token lifetime: - **`audience`**: restricts the minted token to a specific API endpoint. The engine rejects the token if the audience doesn't match. Use this to narrow tokens by deployment. - **`ttlSeconds`**: overrides the default token lifetime (the engine sets a sensible default if omitted). Shorten this for high-sensitivity sessions; lengthen if you're seeing churn from repeated mints. How it works: 1. Your server provisions a `secret` (32+ bytes of random) and stores it alongside any per-user/per-session claims. 2. The SDK accepts the secret and claims at construction time. 3. On each request, the SDK mints a fresh HS256 JWT from those claims and sends it as a `Bearer` token. 4. The engine verifies the signature against the shared secret and applies the claims as the request's scope. The secret only stays safe when the SDK client is constructed on your server. If you instantiate the client in a browser, the secret is embedded in the bundle and effectively public. Provision a session-scoped secret you can revoke on logout, or proxy SDK calls through a server endpoint that holds the long-lived secret. **Mode switching is automatic.** When you provide `scopeToken`, the SDK ignores any `apiKey` for that instance. ## When to use which | Runtime | Mode | Why | |---------|------|-----| | Node server, agent worker, container | `apiKey` | Long-lived credential, simplest. | | Next.js Route Handler, Cloudflare Worker, Vercel Function | `apiKey` | Same as above; the runtime is trusted. | | Browser (React, Vue, Svelte) | `scopeToken` | Avoids embedding a long-lived account credential. | | Edge functions invoked by an untrusted client | `scopeToken` | Per-request scope narrowing via claims. | | Third-party plugin / extension | `scopeToken` | Scope and revoke per session. | ## Errors `BetaAccessError` (HTTP 401/403): key missing, invalid, or revoked. The SDK surfaces a `signupUrl` for self-service requests: ```ts import Beliefs, { BetaAccessError } from 'beliefs' try { await beliefs.before(input) } catch (err) { if (err instanceof BetaAccessError) { console.log(err.signupUrl) } } ``` `BeliefsError` with code `auth/missing_key`: the SDK was constructed without any auth at all. The SDK has a third internal mode (`serviceToken`) used by Studio's BFF. It is not part of the public app-builder surface and should not be used by external integrators. ## Errors Source: https://thinkn.ai/dev/sdk/errors Summary: Every error class the SDK throws, when it fires, and how to handle each one. The SDK throws two error classes. Auth failures are their own class so you can handle them up front; everything else is `BeliefsError` with a structured code. ```ts import Beliefs, { BetaAccessError, BeliefsError } from 'beliefs' ``` ## BetaAccessError The API key is missing, invalid, or revoked, or your account isn't on the beta allowlist (HTTP 401/403). Surfaces a `signupUrl` for self-service requests. ```ts try { await beliefs.before(input) } catch (err) { if (err instanceof BetaAccessError) { console.log(err.signupUrl) // 'https://thinkn.ai/waitlist' return redirect(err.signupUrl) } throw err } ``` | Property | Type | Notes | |---|---|---| | `name` | `string` | `'BetaAccessError'` | | `message` | `string` | Human-readable reason | | `status` | `401 \| 403` | HTTP status from the engine | | `signupUrl` | `string` | Where to request access | **Not retryable.** Either the key works or it doesn't. Retrying won't change the answer. ## BeliefsError Everything else. Carries a structured `code`, an HTTP status, and a `retryable` flag. The SDK auto-retries transient errors (429, 5xx) with exponential backoff, so you only see these after retries are exhausted. ```ts try { await beliefs.after(result.text) } catch (err) { if (err instanceof BeliefsError) { console.log(err.code) // 'rate_limit/exceeded' console.log(err.status) // 429 console.log(err.retryable) // true (already retried, exhausted) } throw err } ``` | Property | Type | Notes | |---|---|---| | `name` | `string` | `'BeliefsError'` | | `message` | `string` | Human-readable reason | | `code` | `string` | Stable, namespaced identifier (see catalog) | | `status` | `number` | HTTP status from the engine | | `retryable` | `boolean` | `true` if the SDK retried before throwing | | `details` | `Record` | Optional structured context (request id, validation issues, etc.) | ## Code catalog Error codes are namespaced as `/`. Categories are stable; reasons may grow. ### `auth/*` | Code | HTTP | Retryable | Meaning | Action | |---|---|---|---|---| | `auth/missing_key` | 401 | No | Constructor was called without `apiKey` or `scopeToken` | Provide one. See [Auth](/dev/sdk/auth). | | `auth/invalid_key` | 401 | No | Key format is malformed or unknown to the server | Verify the value of `BELIEFS_KEY`. | | `auth/revoked_key` | 403 | No | Key was valid but revoked (rotated, deleted, or org disabled) | Issue a new key from your profile. | | `auth/scope_token_expired` | 401 | No | A short-lived `scopeToken` passed its `exp` claim | Mint a fresh token server-side. | For 401/403 from the beta allowlist specifically, expect [`BetaAccessError`](#betaaccesserror), not `BeliefsError`. ### `validation/*` | Code | HTTP | Retryable | Meaning | Action | |---|---|---|---|---| | `validation/invalid_json` | 400 | No | Request body failed JSON parsing | Check serialization. Usually a non-stringifiable value in `add()` or `after()` payload. | | `validation/invalid_shape` | 400 | No | Request shape failed engine schema validation | `err.details.issues` lists the Zod issues. | | `validation/missing_field` | 400 | No | A required field was omitted | `err.details.field` names the field. | ### `rate_limit/*` | Code | HTTP | Retryable | Meaning | Action | |---|---|---|---|---| | `rate_limit/exceeded` | 429 | Yes (auto-retried) | Per-key or per-namespace quota exceeded | Inspect `err.details.retryAfter`. Drop call rate or upgrade. | ### `internal/*` | Code | HTTP | Retryable | Meaning | Action | |---|---|---|---|---| | `internal/error` | 5xx | Yes (auto-retried) | Engine error, retried with backoff before surfacing | If repeated, check status page; file a P1 with `err.details.requestId`. | | `internal/timeout` | 504 | Yes (auto-retried) | Engine took longer than the configured timeout | Retry, or split into smaller observations. | ### Operation-specific | Code | HTTP | Retryable | Meaning | Action | |---|---|---|---|---| | `remove_where/unsupported_source` | 400 | No | `removeWhere({ source })` was called with a kind other than `block:` | Use `retract()` or `remove()`. Engine support for `agent:`, `thread:`, `source:` is in flight. | ## Retry & backoff The SDK auto-retries `429` and `5xx` responses with exponential backoff before throwing. By the time you see a `BeliefsError` with `retryable: true`, the SDK has already exhausted its retry budget. You don't need to wrap it in a retry loop yourself. `retryable: false` errors (auth, validation) are never retried. Don't retry them in your handler. Fix the input. ## Handling pattern A single try/catch covers both classes. Branch on instance, then on code if you need fine-grained handling. ```ts import Beliefs, { BetaAccessError, BeliefsError } from 'beliefs' try { const context = await beliefs.before(userMessage) // ... agent run, after, etc. } catch (err) { if (err instanceof BetaAccessError) { // Send the user to the waitlist return { error: 'beta_access_required', signupUrl: err.signupUrl } } if (err instanceof BeliefsError) { if (err.code.startsWith('auth/')) { // Auth misconfiguration: log and surface to ops console.error('beliefs auth failure', err.code, err.message) return { error: 'auth_misconfigured' } } if (err.code === 'rate_limit/exceeded') { // Already auto-retried; back off in your application loop return { error: 'rate_limited', retryAfter: err.details?.retryAfter } } if (err.code.startsWith('validation/')) { // Bug in the calling code: log and fix console.error('beliefs validation failure', err.details) return { error: 'invalid_request' } } // internal/*: already retried, surface as transient return { error: 'beliefs_unavailable' } } // Not a beliefs error: re-throw for upstream handling throw err } ``` ## Where to next --- # Adapters ## Claude Agent SDK Source: https://thinkn.ai/dev/adapters/claude-agent-sdk Summary: Use beliefs with the Anthropic Claude Agent SDK. Automatic belief extraction from agent turns. ## Hooks Adapter (recommended) ```bash npm i beliefs ``` The adapter integrates with the Claude Agent SDK's hook system. It captures tool results via `PostToolUse` hooks and injects belief context at `SessionStart`: ```ts import { query } from '@anthropic-ai/claude-agent-sdk' import Beliefs from 'beliefs' import { beliefsHooks } from 'beliefs/claude-agent-sdk' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'research-agent', namespace: 'claude-sdk', writeScope: 'thread', }) // Pass hooks to the Claude Agent SDK query options const result = await query({ prompt: 'Research the competitive landscape for AI dev tools', options: { hooks: beliefsHooks(beliefs), }, }) ``` `beliefsHooks` registers: - **`SessionStart`:** calls `beliefs.before()` and injects context via `additionalContext` - **`PostToolUse`:** calls `beliefs.after(toolResult, {tool})` for each tool invocation If the client is thread-scoped, `beliefsHooks()` resolves the thread automatically from Claude's `session_id` by default. ### Capture Modes ```ts beliefsHooks(beliefs, { capture: 'tools' }) // each tool call result (default) beliefsHooks(beliefs, { capture: 'all' }) // tool results + text responses ``` ### Configuration ```ts beliefsHooks(beliefs, { capture: 'all', includeContext: true, toolFilter: 'search|Read', // regex to filter which tools trigger extraction }) ``` | Option | Default | Description | |--------|---------|-------------| | `capture` | `'tools'` | What to extract beliefs from | | `includeContext` | `true` | Inject belief context at session start | | `toolFilter` | none | Regex matched against tool names. Example: `'search\|Read'` extracts only from search and Read; internal tools like `Bash` would be skipped. | | `resolveThreadId` | `input.session_id` | Override how thread IDs are derived. Pass a `(input) => threadId` function when your thread keying differs from Claude's session id (e.g., per-user threads). | When you use `beliefsHooks(...)`, the adapter owns the lifecycle: `SessionStart` calls `before()`, `PostToolUse` calls `after()`. Calling `beliefs.before()` or `beliefs.after()` yourself in the same query produces duplicate extraction and double-counts evidence. Use one path or the other, not both. Use `writeScope: 'thread'` when each Claude session should keep its own memory. Use `writeScope: 'space'` when all sessions in a namespace should share one state. --- ## Without the adapter (manual `before`/`after`) If you're not using the Claude Agent SDK's hook system (for example, you're using `@anthropic-ai/sdk` directly), wrap your calls manually: ```ts import Anthropic from '@anthropic-ai/sdk' import Beliefs from 'beliefs' const client = new Anthropic() const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'research-agent', namespace: 'claude-sdk', writeScope: 'space', }) async function research(question: string) { const context = await beliefs.before(question) const message = await client.messages.create({ model: 'claude-sonnet-4-20250514', max_tokens: 4096, system: context.prompt, messages: [{ role: 'user', content: question }], }) const text = message.content .filter(b => b.type === 'text') .map(b => b.text) .join('') const delta = await beliefs.after(text) return { text, delta } } ``` ### With tool results Feed each tool result separately so beliefs update mid-turn: ```ts const context = await beliefs.before(question) const message = await client.messages.create({ model: 'claude-sonnet-4-20250514', max_tokens: 4096, system: context.prompt, messages: [{ role: 'user', content: question }], tools: myTools, }) for (const block of message.content) { if (block.type === 'tool_use') { const result = await executeTool(block.name, block.input) await beliefs.after(JSON.stringify(result), { tool: block.name }) } else if (block.type === 'text') { await beliefs.after(block.text) } } ``` ### Multi-turn loop Run multiple turns and let clarity drive when to stop: ```ts async function deepResearch(question: string) { for (let turn = 0; turn < 10; turn++) { const context = await beliefs.before(question) if (context.clarity > 0.8) { return context.beliefs } const message = await client.messages.create({ model: 'claude-sonnet-4-20250514', max_tokens: 4096, system: context.prompt, messages: [{ role: 'user', content: question }], }) const text = message.content .filter(b => b.type === 'text') .map(b => b.text) .join('') await beliefs.after(text) } } ``` ## Vercel AI SDK Source: https://thinkn.ai/dev/adapters/vercel-ai Summary: Use beliefs with the Vercel AI SDK. Middleware-based integration for streamText and generateText. ## Use Today with the Core SDK The core `beliefs` package works with the Vercel AI SDK right now. Wrap your `generateText` or `streamText` calls with `before`/`after`: ```bash npm i beliefs ai @ai-sdk/anthropic ``` ### With generateText ```ts import { generateText } from 'ai' import { anthropic } from '@ai-sdk/anthropic' import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'research-agent', namespace: 'vercel-ai', writeScope: 'space', }) async function research(question: string) { const context = await beliefs.before(question) const { text } = await generateText({ model: anthropic('claude-sonnet-4-20250514'), system: context.prompt, prompt: question, }) const delta = await beliefs.after(text) console.log(`clarity: ${delta.clarity}, changes: ${delta.changes.length}`) return text } ``` ### With streamText ```ts import { streamText } from 'ai' import { anthropic } from '@ai-sdk/anthropic' import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'research-agent', namespace: 'vercel-ai', writeScope: 'space', }) async function researchStream(question: string) { const context = await beliefs.before(question) const result = streamText({ model: anthropic('claude-sonnet-4-20250514'), system: context.prompt, prompt: question, }) let fullText = '' for await (const chunk of result.textStream) { process.stdout.write(chunk) fullText += chunk } const delta = await beliefs.after(fullText) return { text: fullText, delta } } ``` Call `after()` exactly once per turn, after the stream completes. Do not call it on partial chunks. Each call triggers extraction and fusion. Calling per-chunk creates duplicate beliefs from incomplete text. For Next.js route handlers, use the `onFinish` callback shown below. ### With Tool Results Feed tool results individually so beliefs update as evidence arrives: ```ts import { generateText, tool } from 'ai' import { anthropic } from '@ai-sdk/anthropic' import { z } from 'zod' const context = await beliefs.before(question) const { text, toolResults } = await generateText({ model: anthropic('claude-sonnet-4-20250514'), system: context.prompt, prompt: question, tools: { search: tool({ description: 'Search the web', parameters: z.object({ query: z.string() }), execute: async ({ query }) => searchWeb(query), }), }, maxSteps: 5, }) for (const result of toolResults) { await beliefs.after(JSON.stringify(result.result), { tool: result.toolName }) } await beliefs.after(text) ``` ### In a Next.js Route Handler ```ts import { streamText } from 'ai' import { anthropic } from '@ai-sdk/anthropic' import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'chat-agent', namespace: 'chat', writeScope: 'space', }) export async function POST(req: Request) { const { messages } = await req.json() const lastMessage = messages[messages.length - 1]?.content ?? '' const context = await beliefs.before(lastMessage) const result = streamText({ model: anthropic('claude-sonnet-4-20250514'), system: context.prompt, messages, onFinish: async ({ text }) => { await beliefs.after(text) }, }) return result.toDataStreamResponse() } ``` --- ## Middleware Adapter ```bash npm i beliefs ``` The adapter integrates through the Vercel AI SDK's middleware system. Wrap your model with `beliefsMiddleware` for automatic belief extraction: ```ts import { generateText, wrapLanguageModel } from 'ai' import { anthropic } from '@ai-sdk/anthropic' import Beliefs from 'beliefs' import { beliefsMiddleware } from 'beliefs/vercel-ai' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'research-agent', namespace: 'vercel-ai', writeScope: 'space', }) const { text } = await generateText({ model: wrapLanguageModel({ model: anthropic('claude-sonnet-4-20250514'), middleware: beliefsMiddleware(beliefs), }), prompt: 'Research the competitive landscape for AI dev tools', }) ``` ### Capture Modes The `capture` option controls what the middleware feeds back to `after()`: - **`'response'`** (default): extract beliefs only from the model's final text response. Lightest mode; one `after()` call per turn. - **`'tools'`:** extract beliefs from each tool result as it returns. Best when tools fetch external data (web search, DB queries) you want tracked individually. - **`'all'`:** extract from both tool results *and* the final response. Most comprehensive; one `after()` call per tool result plus one for the final text. ```ts beliefsMiddleware(beliefs, { capture: 'response' }) // final response (default) beliefsMiddleware(beliefs, { capture: 'tools' }) // each tool call result beliefsMiddleware(beliefs, { capture: 'all' }) // both ``` ### Configuration ```ts beliefsMiddleware(beliefs, { capture: 'all', includeContext: true, }) ``` | Option | Default | Description | |--------|---------|-------------| | `capture` | `'response'` | What to extract beliefs from: `'response'`, `'tools'`, or `'all'` | | `includeContext` | `true` | Inject belief context into system prompt via `before()` | | `resolveThreadId` | none | Required when `beliefs` uses `writeScope: 'thread'` and no thread is already bound | If you keep the SDK default `writeScope: 'thread'`, either bind the thread ahead of time with `beliefs.withThread(threadId)` or pass `resolveThreadId` so the middleware can scope each invocation correctly. ```ts import { streamText, wrapLanguageModel } from 'ai' import { anthropic } from '@ai-sdk/anthropic' import Beliefs from 'beliefs' import { beliefsMiddleware } from 'beliefs/vercel-ai' export async function POST(req: Request) { const { messages, conversationId } = await req.json() const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, namespace: 'support', writeScope: 'thread', }) const model = wrapLanguageModel({ model: anthropic('claude-sonnet-4-20250514'), middleware: beliefsMiddleware(beliefs, { resolveThreadId: () => conversationId, }), }) const result = streamText({ model, messages }) return result.toDataStreamResponse() } ``` ## React Source: https://thinkn.ai/dev/adapters/react Summary: React hooks for building belief-aware interfaces. @beliefs/react is in development. This page describes the planned API. ## What It Provides React hooks for reading belief state in your components. Subscribe to claims, track clarity, and display confidence, with real-time updates as beliefs change. ## Planned Hooks ### useBeliefs Returns the current belief snapshot. Auto-updates when beliefs change. ```tsx function Dashboard() { const { claims, gaps, clarity } = useBeliefs() return (

Clarity: {(clarity * 100).toFixed(0)}%

{claims.length} claims, {gaps.length} gaps

) } ``` ### useClaim Subscribe to a specific claim by ID. Returns confidence, evidence count, and last updated timestamp. ```tsx function ClaimBadge({ claimId }: { claimId: string }) { const { text, confidence, evidenceCount } = useClaim(claimId) return ( {text} - {(confidence * 100).toFixed(0)}% ) } ``` ### useClarity Returns the current clarity score and its components. ```tsx function ClarityIndicator() { const { score, readiness } = useClarity() return (
Clarity: {(score * 100).toFixed(0)}% Readiness: {readiness}
) } ``` ## Request Early Access If you are building a belief-aware UI and want early access to the React hooks, [request access](/dev/beta). ## DevTools Source: https://thinkn.ai/dev/adapters/devtools Summary: A visual inspector for your agent's belief state. @beliefs/devtools is in development. This page describes the planned tool. ## What It Provides A browser-based inspector for your agent's belief state. See claims, confidence, evidence, contradictions, the ledger, and clarity in real time as your agent runs. ## Planned Features - **Claim timeline.** Watch claims appear and update across turns. - **Confidence history.** See how confidence changes as evidence accumulates. - **Ledger viewer.** Trace any belief back to its origin. - **Clarity breakdown.** Inspect each component of the clarity score. - **Contradiction highlighter.** See conflicts as they are detected. ## Integration One line in your development setup: ```ts import { BeliefDevTools } from '@beliefs/devtools' // Wrap your beliefs instance const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'research-agent', namespace: 'devtools-demo', writeScope: 'space', }) BeliefDevTools.attach(beliefs) ``` The inspector opens in a separate browser panel. It does not affect your agent's runtime. ## Request Early Access If you want to try DevTools before the public release, [request access](/dev/beta). --- # Use Cases ## Enterprise Source: https://thinkn.ai/dev/cases/enterprise Summary: The shared operating state every function's agents read, act, and simulate against. Across a company, the world model is the shared operating state every function works from. It holds what is true right now about deals, cash, incidents, and commitments, and how sure the business is of each. Take a B2B software company in the last week of its quarter. The deal that would close the gap shows won in the CRM and has no invoice in billing. Each team is right about its own slice, and no one, agent or person, sees the company whole. That is the trap at scale, and one shared belief state outside the model dissolves it. Every operating agent reads, acts, and simulates against the same picture, so the company can scale work without scaling management. The seven worlds in this section are slices of this one, and a company runs all of them at once. ## The world The **environment** is the operating company: customers and accounts, deals and pipeline, products and inventory, incidents, cash and forecast, commitments and headcount. Entities span functions. Relations cross them: this renewal depends on that open incident, this forecast rests on that deal closing, this shipment blocks that customer's go-live. Ground truth is scattered across systems of record (CRM, ERP, ticketing, the data warehouse, finance, source control), each authoritative for its own slice and silent about the rest. The world model is the layer that reconciles them into one current picture instead of leaving every agent to stitch it together per turn. ## What streams in **Observations** arrive from every operational surface the company already runs: - CRM updates, deal-stage changes, and renewal signals - support tickets, chat transcripts, and incident channels - ERP and inventory movements, fulfillment and logistics events - finance and billing events, and the numbers behind the forecast - the actions of the company's own agents, in every function That last channel is what makes it one world and not many. When the finance agent revises the forecast or the support agent escalates a churn risk, that becomes evidence the other agents read. The picture each one acts from already carries what the others just learned. ## The belief state The company's operating picture is a set of claims drawn from every function, each carrying confidence, provenance, and decay, alongside the gaps and conflicts no single team can see alone. ``` ┌──────────────────────────────────────────────────────────────┐ │ Q3 OPERATING PICTURE │ │ │ │ ● "Q3 revenue lands within 2% of plan" 71% │ finance+pipe │ │ ● "Enterprise churn rising in EU" 64% │ support+CRM │ │ ● "Fulfillment SLA at risk on line 3" 58% │ ops+sensors │ │ ● "SOC2 controls intact" 88% │ eng+audit │ │ └─ ⚠ Decayed: last audit was 70 days ago │ │ │ │ Gap: "No demand forecast for the new SKU" │ │ Contradiction: sales says the deal closed; billing has none │ └──────────────────────────────────────────────────────────────┘ ``` Every claim carries its source function, so a forecast built on finance plus pipeline reads differently from one a single dashboard asserts. The sales-versus-billing contradiction is the kind that costs real money. It is invisible inside either system and obvious the moment both feed one world. The agent that surfaces it reconciles every function at once, which is the one thing no single team does. ## What we're after The **intent** is the company's, not one function's: hit the plan and keep the commitments behind it. Success means the quarter lands where it was promised, the obligations the business made are met, and every operating decision traces to evidence a leader can inspect. The limiting factor is the cross-functional unknown most likely to move the outcome, which here is the unreconciled deal, not the security posture. Moves are ranked against that intent, not against raw uncertainty, so the company's attention lands where being wrong is most expensive. | | | |---|---| | **Goal** | Hit the plan and keep the commitments behind it. | | **Success** | Quarter on plan, obligations met, every decision traceable to evidence. | | **Limiting factor** | The cross-functional unknown most likely to move the outcome. | ## The policy A company runs under rules that outrank any single team's convenience. You encode them once, and every agent inherits them: | Policy bucket | Across the business | |---|---| | **Invariants** | Respect compliance, data-residency, and privilege boundaries. Stay within each role's spend and commitment authority. | | **Source hierarchy** | The system of record outranks a Slack message; a signed contract outranks a verbal "we agreed"; a posted number outranks a projected one. | | **Conventions** | How functions hand off, how an incident is escalated, how a forecast is revised. | | **Avoid** | Letting one team's optimistic read inflate a company-level belief; acting on a number another system already moved. | ## The actions Group what an agent may do by what it costs to be wrong, the same banding every function uses: | Action | Safety class | Effect | |---|---|---| | `reconcile`, `summarize-state`, `flag-risk` | **info** | Read-only. Surfaces the picture; changes nothing. | | `update-forecast`, `open-ticket`, `adjust-inventory` | **mutates** | Changes a function's recorded state. | | `issue-credit`, `reallocate-budget`, `notify-customer` | **needs-approval** | Gated on a human with the authority. | Today, policy and actions are a modeling lens you encode, not a contract the engine runs for you. The invariants, the source hierarchy, and these safety classes live in how each agent observes and acts. The engine does not yet take a registered policy and refuse the calls that violate it; a declarative config surface for that is on the roadmap. Read the tables as the contract your agents uphold. What the engine guarantees is the rest of the loop: it fuses the signals into one picture, surfaces the conflicts, and keeps the trail. ## Plan & act An operating agent does not start from the thread in front of it. It orients on the whole company picture, reads the cross-functional move most at risk, and acts, with the result folding back in for every other agent: ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'ops-orchestrator', namespace: 'org:acme', writeScope: 'space', }) // 1. Orient: read the company picture; the ranked next moves ride back on it const context = await beliefs.before('Are we on track for Q3, and what is most at risk?') const [move] = context.moves // top-ranked, already in hand; no extra call // → { action: 'gather_evidence', subType: 'reconcile-deal-billing', valueOfInformation: 0.8, ... } // 2. Act: your agent dispatches the tool the move points to const recon = await runTool(move.subType) // 'reconcile-deal-billing' → your CRM + billing reconciler // 3. Fold the result back in; it becomes the next observation for every function await beliefs.after(JSON.stringify(recon), { tool: 'reconciler', source: 'crm+billing' }) ``` The verdict is the product. A stack of dashboards hands a leader twelve views and leaves the synthesis to a Monday meeting. The company world model renders the call: the quarter is at risk on the unreconciled deal, confirm that before trusting the forecast, and here is the confidence on each claim and the function it came from. Because every belief traces to the system it came off, a `needs-approval` move reaches a human with its evidence attached, not as a recommendation to take on faith. As the business accumulates action-to-outcome history, an agent can simulate a cross-functional move forward before committing to it (`beliefs.forecast.predict([...])`): if we pull budget from here, what happens to the SLA there. It reports low confidence until enough real outcomes have accrued to calibrate, so a simulation is never mistaken for a guarantee. [Moves](/dev/core/moves) covers the forecasting layer. ## Engineering Source: https://thinkn.ai/dev/cases/engineering Summary: A coding agent that knows what is true before it ships a change. In a codebase, the world model is the agent's live read on what is true about the system. Which invariants hold, where the risk sits, and how sure it is, checked against tests that actually run. A codebase checks itself. The tests pass or they don't, the build is green or red, the diff applies or it conflicts, which makes engineering the world where a shared, auditable belief state is easiest to prove. Picture a payments company whose platform agent is about to refactor the export flow. Before it touches code it needs the truth about auth coverage now, not a guess from training. That belief state lives outside the model, so the agent reads what is true now instead of re-deriving it every turn. ## The world The **environment** is the system under change: services, modules, endpoints, dependencies, and the trust boundaries between them. Entities are the components. Relations are the calls, imports, and ownership edges that bind them. Ground truth lives in the tests, the build, and the diff, so a belief about this world gets confirmed or refuted against a check that runs, never just asserted into the record. ## What streams in The agent's **observations** are the artifacts that report how the system actually behaves: - diffs and PRs (what changed) - CI runs and test output (what passed) - dependency audits and CVE advisories (what's exposed) - logs, traces, and incident reports (what broke) Each one folds into the belief state as evidence for or against what the agent currently holds true. A green CI run on the auth suite raises confidence in "all routes require auth." A new CVE advisory drops confidence in "no critical CVEs in dependencies." The agent doesn't store these as facts to recall later; it updates a posterior it can defend. ## The belief state The agent's read on the system's security posture is a set of claims. Each one carries an explicit posterior, the evidence behind it, and a decay schedule, alongside the gaps it knows it hasn't closed. ``` ┌──────────────────────────────────────────────────────────────┐ │ SECURITY POSTURE │ │ │ │ ● "No critical CVEs in dependencies" 74% │ audit 30d ago │ │ ● "All API routes require auth" 81% │ middleware scan │ │ ● "SQL injection mitigated" 92% │ parameterized │ │ ● "No secrets in source control" 65% │ 3mo old scan │ │ └─ ⚠ Decayed - last scanned 90 days ago │ │ │ │ Gap: "No SSRF analysis on new webhook handler" │ │ Gap: "Rate limiting untested on file upload endpoint" │ └──────────────────────────────────────────────────────────────┘ ``` These usually live as unspoken assumptions. "All inputs are sanitized." "The auth middleware covers every state-changing endpoint." Once they are explicit, each becomes something the agent gathers evidence for. A contradiction then surfaces as a flagged conflict instead of a silent gamble: when a fresh CVE advisory lands against "no known vulnerabilities," the engine raises it rather than letting the newer artifact quietly win. ## What we're after A belief state without a goal is just a status board. The **intent** is what turns it into a plan. Here the goal is to ship the export refactor without regressing the system, and the engine ranks moves toward closing the distance to that goal, not toward shrinking raw uncertainty wherever it happens to be highest. An ambiguous belief in a quiet corner of the codebase stays low-priority; the one blocking the refactor rises to the top. | | | |---|---| | **Goal** | Ship the export refactor with no regression to the system. | | **Success** | Tests green, auth coverage intact across state-changing routes, no new CVEs introduced. | | **Limiting factor** | The one unverified security belief in the path of the refactor: "No secrets in source control," last scanned 90 days ago and decayed. | That limiting belief is why the top move points at evidence-gathering before code. Until it's re-verified, the refactor is a gamble against a stale scan, and the engine ranks the audit ahead of the diff. ## The policy A world has rules the agent should respect, independent of what it currently believes. In a codebase those are the **invariants**, the **source hierarchy** for resolving disagreement, the **conventions**, and the **anti-patterns to avoid**. You encode them in how you observe and act. | Policy bucket | In a codebase | |---|---| | **Invariants** | Auth is enforced at the middleware layer. All DB access is parameterized. No secrets in source control. | | **Source hierarchy** | A green test outranks a doc; a doc outranks a comment; running code outranks intent. | | **Conventions** | How this repo does logging, error handling, migrations. | | **Avoid** | Known anti-patterns this team has already ruled out. | Two beliefs from different sources should not carry equal weight. When a stale comment claims one thing and a passing test shows another, the source hierarchy you've encoded is how your agent decides which to trust, and the comment loses. ## The actions The world also defines what the agent *can do* in it. Each action carries a safety class so the surface stays honest about blast radius: | Action | Safety class | Effect | |---|---|---| | `run-tests`, `audit-deps` | **info** | Read-only. Gathers evidence; changes nothing. | | `refactor`, `add-test` | **mutates** | Changes the working tree. | | `open-PR`, `deploy` | **needs-approval** | Gated on a human before it fires. | The engine never takes these actions for you. Your agent does. What the engine provides is the picture the agent reads to *choose* among them, and the record of what each action changed. Today, policy and actions are a modeling lens you encode. The invariants, source hierarchy, and safety classes live in *how* you observe and act, not in a policy the engine enforces. Read the tables above as the contract your agent upholds, not one the engine guarantees. A declarative config surface to register policy directly is on the roadmap. ## Plan & act With the frame in place, the loop is short. The agent orients on the current picture, reads the highest-value next move (ranked by expected information gain and already in hand), runs the matching action, and folds the result back in as the next observation: ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'security-agent', namespace: 'security-review', writeScope: 'space', }) // 1. Orient: read the current picture; the ranked next moves ride back on it const context = await beliefs.before('Review auth coverage before the export refactor') const [move] = context.moves // top-ranked, already in hand; no extra call // → { action: 'gather_evidence', subType: 'audit-deps', valueOfInformation: 0.8, ... } // 2. Act: your agent dispatches the tool the move points to const audit = await runTool(move.subType) // 'audit-deps' → your npm-audit runner // 3. Fold the result back in; it becomes the next observation await beliefs.after(JSON.stringify(audit), { tool: 'npm_audit', source: 'ci' }) ``` The payoff is judgment, not just recall. A new CVE in that audit contradicts "no critical CVEs," and the engine flags the conflict instead of quietly overwriting the old claim. A RAG layer would hand the agent both strings and let the prompt sort them out. Here the conflict is a first-class object the agent has to address. Every belief traces back to the scan or test that produced it, so a reviewer can inspect *why* the agent thinks auth is covered before trusting that near the export flow. Replay the same observations and you rebuild the same picture. Once a repo accumulates action→outcome history, the agent can project a move's value several steps forward instead of one: `beliefs.forecast.predict(['run-tests', 'add-route-auth-test', 'refactor-export'])`. On a fresh repo it returns `confidence: 'low'` until it has seen enough resolved outcomes to calibrate against. That is the honest answer rather than a guess dressed up as a forecast. ## Research Source: https://thinkn.ai/dev/cases/research Summary: Many agents working one body of evidence, their disagreements held open instead of averaged away. In research, the world model is one shared body of evidence about a single question: competing hypotheses held side by side with their posteriors, every claim traced to its source, and disagreement between agents recorded and worked through instead of averaged away. A research agent that only retrieves drifts toward the answer it already favored, the top results echoing a single press release reworded five ways until the prompt mistakes that chorus for confirmation. Run several such agents and it gets worse. Each builds its own private notebook and you stitch the conflict together by hand at the end. A belief state that lives outside the model fixes that, holding the disagreement as a property of the world the agents share rather than something each one reconciles in its own head. ## The world The **environment** is one question and the evolving body of evidence about it. Entities are claims, the sources behind them, and the sub-questions the question breaks into. Relations are *supports*, *contradicts*, and *derives-from*: a paper supports a claim, a primary document contradicts another, a sub-question derives from the one above it. Ground truth lives in the sources themselves, ranked by how close each one sits to the thing being studied. The world is not settled when the agents start. The agents are converging on it as they work. ## What streams in The agents' **observations** are everything they pull in while exploring the question: - search results and the snippets that rank for a query - papers, preprints, and the primary documents they cite - primary sources: filings, datasets, transcripts, raw records - tool outputs: a calculation, an extraction, a scrape - the findings of *other agents* working the same question That last channel is the point. One agent's recorded finding is another agent's next observation. When an agent writes a result, it becomes evidence the rest of the world reads, so the model learns how the question responds to each line of inquiry and where the team is pulling apart. ## The belief state The state holds competing hypotheses at once, each with its own posterior, rather than collapsing early to one storyline. Contradictions are kept as first-class objects: when sources disagree, uncertainty widens and the conflict is surfaced, never averaged into a comfortable middle. Gaps are the open sub-questions still load-bearing on the answer. ``` ┌──────────────────────────────────────────────────────────────┐ │ QUESTION: HOW LARGE IS THE AI-TOOLS TAM? │ │ │ │ H1 "TAM > $50B by 2027" 58% │ 4 sources │ │ H2 "TAM in the $20-30B band" 46% │ 3 sources │ │ └─ ⚠ contradicts H1 - kept open, not averaged │ │ │ │ ● "Top-down sizing assumes 80% seat conversion" 39% │ │ └─ agent-A flags as the weakest load-bearing claim │ │ │ │ Gap: "No primary spend data, only vendor estimates" │ │ Gap: "Seat-to-revenue ratio unverified outside one report" │ │ │ │ Two analysts disagree on H1 vs H2. The world holds both, │ │ widens the band, and ranks the move that splits them. │ └──────────────────────────────────────────────────────────────┘ ``` Many agents write to one fused world under a shared namespace. When analyst A's reading of a vendor report lifts H1 and analyst B's reading of a primary filing lifts H2, the engine does not pick a winner or blend the two into a single dull number. It keeps both hypotheses live, widens the uncertainty to reflect the real disagreement, and marks the contradiction for resolution by evidence. A source restated five times across five secondary outlets still counts as one source, so a loud consensus cannot masquerade as independent confirmation. ## What we're after The **intent** is to converge on a defensible answer to the question. Success has a concrete shape: the key contradictions are resolved or explicitly scoped, the load-bearing sub-questions are closed, and every surviving claim is tied to the evidence that earns it. The limiting factor is the handful of sub-questions whose answers actually move the conclusion, rarely the raw volume of search. Moves are ranked against that intent, not against raw uncertainty. A wide-open sub-question that does not change the answer ranks below a narrow one that decides between two live hypotheses. | | | |---|---| | **Goal** | Converge on a defensible answer to the question. | | **Success** | Key contradictions resolved or scoped, the load-bearing sub-questions closed, every surviving claim tied to its evidence. | | **Limiting factor** | The few sub-questions whose answers actually move the conclusion. | ## The policy A research effort runs under rules that outrank any single attractive result. You encode them as the structure the agents work within: | Policy bucket | In a research effort | |---|---| | **Invariants** | Every claim cites the evidence behind it. No bare assertions enter the record. | | **Source hierarchy** | Primary source over peer-reviewed; peer-reviewed over preprint; preprint over secondary; secondary over rumor. | | **Conventions** | How a finding gets recorded, when a hypothesis goes "contested," how sub-questions are split. | | **Avoid** | Confirmation bias toward a favored answer. Counting one restated source as many. | ## The actions Group what an agent may do by what it costs to be wrong: | Action | Safety class | Effect | |---|---|---| | `search`, `read`, `synthesize` | **info** | Gathers and digests evidence. Changes nothing. | | `record-finding` | **mutates** | Writes a result into the shared world. | | `commission-experiment`, `escalate-to-human` | **needs-approval** | Spends real effort or a person's time; gated. | Today, policy and actions are a modeling lens you encode, not a contract the engine runs for you. The source hierarchy, the cite-or-it-does-not-count invariant, and the safety classes live in *how* your agents observe and act. Read the tables as the contract your agents uphold. A declarative config surface to register policy directly is on the roadmap. What the engine guarantees is the rest of the loop: it fuses the observations, holds the disagreement, surfaces the trail, and ranks what to chase next. ## Plan & act The next move is the sub-question whose answer most reduces uncertainty toward the goal, already ranked and in hand. Each agent orients on the fused world, takes the top move, runs it, and folds the result back in, where it becomes the next observation for every other agent on the question: ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'researcher', namespace: 'topic:tam', writeScope: 'space', }) // 1. Orient: read the fused world; the ranked next moves ride back on it const context = await beliefs.before('Where is the AI-tools TAM estimate weakest?') const [move] = context.moves // top-ranked, already in hand; no extra call // → { action: 'gather_evidence', subType: 'verify-claim', valueOfInformation: 0.82, ... } // 2. Act: your agent dispatches the tool the move points to const finding = await runTool(move.subType) // 'verify-claim' → your search+read tool // 3. Fold the result back in; it becomes the next observation await beliefs.after(finding.text, { source: 'BLS primary spend table' }) ``` The payoff is a verdict, not a pile of quotes. Analyst A and analyst B both write to `topic:tam`, and where they disagree on the TAM band the engine holds H1 and H2 open with a widened uncertainty instead of letting the last writer overwrite the first. When analyst B's primary spend table outranks the vendor estimates behind H1, fusion moves the posterior, the contradiction resolves on the evidence rather than on who wrote last, and the next ranked move is the sub-question that still separates the two views. A recall layer would hand every agent the same bag of snippets and leave the debate unsettled in each one's prompt. Here the debate is a property of the shared world, and every step of its resolution traces back to the exact source that moved it. As a question accumulates resolved sub-questions, the agents can project which line of inquiry sharpens the answer most before spending the effort: `beliefs.forecast.predict(['verify-claim', 'pull-primary-data', 'cross-check-vendor'])`. It returns low confidence until enough findings have actually resolved, so a forecast is never mistaken for a settled result. See [Moves](/dev/core/moves) for how ranking and forecasting work. ## Investing Source: https://thinkn.ai/dev/cases/investing Summary: A diligence fleet that gathers evidence to size its conviction in a deal. In investing, the world model is the live conviction on a deal. The claim "is this a good bet at this price?" is carried as a posterior that the diligence fleet drives up or down as evidence lands, and that conviction is what sizes the check. Take a Series B infrastructure startup under review in the namespace `deal:target`. The pitch says net retention is strong and the design win at a marquee account is locked. Diligence exists to test exactly those load-bearing claims by pulling the cohort data, calling the references, and reading the contract. A belief state that lives outside any one workstream holds the conviction as it actually stands, so the partner reads where the evidence has moved the thesis before sizing the check. ## The world The **environment** is the target and the deal: the company, the round, and the price you are being asked to pay. The entities are the company and its product, the market it sells into, the customers and the design wins, the cohorts behind the revenue, the founders and the team, and the terms of the round. The relations are the load-bearing structure of the thesis: a customer *validates* a use case, a cohort *supports* a retention claim, a competitor *threatens* a moat, an expert call *corroborates* or *refutes* the technical story, a term *prices* the risk. Ground truth is uneven, and saying where it lives is half the discipline. The data room holds management's numbers, which are a starting point, not the record. Primary evidence outranks them: the raw cohort export reconciled to the billing ledger, the signed contract behind a claimed design win, the customer who actually renewed, the expert who has shipped the same architecture. Management's framing is a claim of lower rank waiting on primary evidence to confirm or kill it. ## What streams in The fleet runs the deal as separate workstreams, each an agent that gathers its own evidence and fuses its findings into the one shared conviction. The **observations** are the channels real diligence runs on: - **Market**: TAM build, competitive map, pricing power, and where the category is heading - **Financial / cohort**: the data-room financials, but resolved against raw cohort and retention exports reconciled to the billing ledger - **Technical / product**: architecture review, security posture, and a read on whether the build matches the story - **Customer & expert references**: renewal-stage customers and independent operators who have run the same playbook, weighted by independence: a reference the founder supplied is an advocate and ranks below one sourced off-list - **Team / background**: founder track record, key-person risk, and reference checks on the people The workstreams are not siloed. When the financial agent finds that the raw cohorts undercut the stated retention, that finding becomes an observation the customer agent acts on next, steering its reference calls at the churned accounts. One agent's evidence is the next agent's starting question, and every finding folds into the same conviction with a receipt back to the source that produced it. ## The belief state The conviction here is not a static memo written at first meeting. It is a central claim carrying an explicit posterior that moves as each workstream reports, with every step traceable to the evidence behind it. Supporting evidence lifts it, refuting evidence pulls it down, a genuine contradiction is surfaced rather than averaged into a comfortable middle, and an open gap is named as work still to do. ``` ┌──────────────────────────────────────────────────────────────┐ │ THESIS: THE TARGET IS A GOOD BET AT THE SERIES B PRICE │ │ │ │ Conviction lean invest, 55-65% band │ 5 streams │ │ │ │ ● Marquee design win is contracted + │ signed MSA │ │ ● Expansion revenue in top cohorts + │ raw export │ │ ● Founder shipped this before + │ 3 off-list refs │ │ ✗ Net retention 130% (pitch) vs 108% raw: definition gap │ │ │ │ Gap: "Does the #2 logo renew at Q3 term?" UNKNOWN │ │ ⚠ Stale: expert call on the moat is 7 months old │ └──────────────────────────────────────────────────────────────┘ ``` Read the conviction as a band rather than a point. The fleet reports a range it can defend, and that band is what gates the call and bounds the check. Each row is a piece of evidence tied to a source a partner can pull. The crossed-out line is the point of the exercise: the founder's stated 130% net retention does not sit beside the 108% the raw cohort export shows as an equal. Often the gap is definitional before it is dishonest, two different cohort windows or a different treatment of contraction, so the engine surfaces the conflict and pins the question rather than blending the two into a number that buries it. A partner adjudicates which definition the thesis should underwrite. The stale flag works the other way: a moat read from an expert call seven months ago has decayed enough that it should be re-confirmed before the thesis leans on it. When a second agent works the same deal, the pictures fuse into one conviction. The market agent's optimism and the cohort agent's caution cannot both ride forward as equals; where they disagree, the disagreement is shown, not smoothed. The engine compiles each workstream's observations into the belief state and projects the conviction the partner acts from. ## What we're after The **intent** is a defensible invest-or-pass decision and, if invest, the right check size for the conviction the evidence supports. Success has a precise shape: the deal's load-bearing assumptions are tested against primary evidence, the conviction is calibrated against the claims that actually resolved during diligence, and every assertion in the memo traces to a source a partner can name. The limiting factor is the assumption doing the most work with the least support behind it: the claim that, if it cracked, would collapse the thesis. For this deal, that is whether the retention story survives the raw cohorts and references sourced off the founder's list. Moves are ranked against that intent, not against raw uncertainty. The fleet does not chase the most uncertain fact on the board. It chases the gap whose resolution most moves the conviction the partner has to defend in the IC memo. | | | |---|---| | **Goal** | A defensible invest-or-pass call and a check sized to the conviction the evidence supports. | | **Success** | Load-bearing assumptions tested against primary evidence, conviction calibrated on what resolved in diligence, every assertion traceable. | | **Limiting factor** | The least-supported assumption the thesis leans on hardest. | The honest boundary matters here. Venture outcomes resolve over years, so the engine does not predict the target's exit or claim to know whether the bet pays off. It calibrates against the assumptions that resolve *during* diligence: did the cohort data confirm the retention claim, did the reference confirm the design win. That calibration accrues across the fund's deal history, not just this one deal, so a partner can ask how often a thesis that looked this well-supported held up when the resolvable checks came back. It surfaces a conviction with its evidence; the partner and the IC make the call and set the check. ## The policy A diligence process runs under rules that outrank any single attractive narrative. You encode them as the structure the fleet works within: | Policy bucket | In a diligence process | |---|---| | **Invariants** | Stay inside the investment mandate and stage. Every assertion in the memo cites evidence. The check is never sized by software. Data-room material stays in `deal:target` and never leaks across deals or to an unwalled model. | | **Source hierarchy** | Primary evidence (raw cohorts, signed contracts, references) leads management's data-room numbers; the data room leads the pitch narrative. | | **Conventions** | How conviction maps to a sizing band, the stage gates a deal clears before it advances (screen, partner review, IC), when a deal goes "on watch," how a contested claim is phrased in the memo. | | **Avoid** | Letting a preference for the deal inflate the posterior on its thesis. This is the is/ought firewall. | When two sources disagree, the hierarchy decides. The reconciled cohort export outranks the founder's headline retention number, every time. ## The actions What the agent may do in this world, grouped by what it costs to be wrong: | Action | Safety class | Effect | |---|---|---| | `summarize-data-room`, `build-cohort`, `screen-comps` | **info** | Read-only analysis. | | `flag-risk`, `update-conviction`, `set-watch` | **mutates** | Changes the recorded view. | | `decide-invest`, `set-check-size` | **needs-approval** | Gated on the partner and the IC. No capital is committed without a signed human decision. | Today, policy and actions are a modeling lens you encode, not a contract the engine runs for you. You express the invariants, the source hierarchy, and the safety classes in *how* your fleet observes and acts; a declarative config surface that registers and enforces them is on the roadmap. So read these tables as the discipline your fleet honors today. What the engine supplies is the conviction each decision rests on and a record of what every action changed, so the IC can audit the chain before a dollar moves and an LP can later ask why the call was made. ## Plan & act ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'diligence-agent', namespace: 'deal:target', writeScope: 'space', }) // 1. Orient: read the current conviction; the ranked next moves ride back on it const context = await beliefs.before('What is the biggest open risk to the thesis?') const [move] = context.moves // top-ranked, already in hand; no extra call // → { action: 'gather_evidence', subType: 'verify-cohort-retention', valueOfInformation: 0.8, ... } // 2. Act: your agent dispatches the tool the move points to const cohort = await runTool(move.subType) // 'verify-cohort-retention' → your data-room and analytics tool // 3. Fold the result back in; it becomes the next observation await beliefs.after(cohort.text, { source: 'Raw cohort export, reconciled to the billing ledger' }) ``` The verdict the world model renders is the conviction on the deal as the evidence stands today, and that is what a recall layer cannot do. A RAG store over the data room would hand the partner the pitch deck, the management retention slide, and the raw cohort export with equal footing, and let an agent write a memo on the 130% number the cohorts already undercut. thinkⁿ ranks `verify-cohort-retention` above `update-conviction`, because resolving the load-bearing retention claim moves the thesis more than another paragraph built on management's framing. Once the export folds in, the contradiction surfaces, the conviction settles where the primary evidence supports it, and the partner can walk every line of the memo back to its source. The human stays on the loop for the decision and the check. As a fund builds resolved history across deals, the agent can forecast which diligence move most sharpens conviction before doing the work: `beliefs.forecast.predict(['verify-cohort-retention', 'call-design-win-reference', 'check-key-person-risk'])`. Forecasting stays deliberately low-confidence until the workspace has enough resolved diligence outcomes to calibrate against what those checks actually turned up, so a forecast is never mistaken for a track record. See [Moves](/dev/core/moves). ## Legal Source: https://thinkn.ai/dev/cases/legal Summary: An agent that drafts from where the record stands now, not the version three filings ago. In a legal matter, the world model is the current state of the case: what is established, what is still contested, what each side has proven, and what the record no longer supports. In one matter a ruling last week struck one of the plaintiff's allegations, a deposition moved what counts as contested, and a filing deadline shifted when the court reset the schedule. An agent answering from its context window answers from a snapshot and cites the fact the ruling already overturned. A belief state that lives outside the model carries the matter as it stands now, so the lawyer's agent reads where the record actually is before it drafts a word. ## The world The **environment** is a matter. The entities are the parties, the claims and counterclaims, the filings on the docket, the deadlines on the calendar, the evidence in the record, and the rulings that decide what any of it means. The relations are the connective tissue of the dispute: one piece of evidence *supports* a claim, a deposition *contradicts* an allegation, a ruling *supersedes* an earlier assumption, a motion *depends_on* a fact still in discovery. The state of this world changes under you. A ruling that grants summary judgment moves a claim from contested to decided. A document produced in discovery flips a "we believe" into a "the record shows," or knocks one down. Ground truth lives in the record and the court's orders. Everything else (the internal memo, the associate's read, the opposing counsel's framing) is a claim of lower rank waiting on the record to confirm or kill it. ## What streams in The agent's **observations** are the events that move a case: - filings and the docket entries that track them - discovery documents and the productions they arrive in - deposition transcripts and witness statements - correspondence with opposing counsel - opposing motions and the exhibits attached to them - court rulings and orders Each one revises the case state. A produced document can establish a fact that was contested; a ruling can supersede a position the team had been building on. The agent's own work folds back in too: a memo it drafts, a deadline it logs, a citation it pulls becomes the next observation, so the model accrues a faithful record of where the matter actually stands. ## The belief state The case state is a set of claims. Each one carries a status (established, contested, or unknown), a confidence, and a provenance the agent can name. A later ruling supersedes an earlier belief rather than quietly coexisting with it, and a fact that has gone stale loses certainty on its own. ``` ┌──────────────────────────────────────────────────────────────┐ │ MATTER: PLAINTIFF v. DEFENDANT │ │ │ │ ● Plaintiff owns the patent ESTABLISHED 92% │ rule │ │ ● Defendant shipped pre-priority CONTESTED 54% │ depo │ │ ● Damages exceed the statutory cap UNKNOWN ? │ gap │ │ ✗ "License lapsed in 2021" SUPERSEDED by 6/14 ruling │ │ │ │ Gap: "Is the prototype admissible?" │ │ ⚠ Stale: "Witness available" depo is 9 months old │ └──────────────────────────────────────────────────────────────┘ ``` Read the status as where the claim stands and the percentage as how sure the agent is, each tied to a source it can cite. Ownership of the patent is established at 92% because a ruling decided it. The priority-date claim sits at 54% and contested because a deposition cuts against the filing. The crossed-out license line is the point: an earlier belief the team relied on, now superseded by the June 14 ruling, so the agent will not cite it again. The stale flag works the other way. A witness-availability fact from nine months ago has decayed enough that the agent should re-confirm it before it builds a deadline around it. When a second associate's agent works the same matter, their pictures fuse into one. "The prototype is admissible" and "the court excluded the prototype" cannot sit side by side as equals; the contradiction surfaces for a lawyer to resolve before either one reaches a brief. You declare your world (environment, intent, policy, actions); the engine compiles observations into the belief state and projects the worldview your agent acts from. ## What we're after The **intent** is to support the lawyer's current position. Success has a precise shape: the case state is accurate and current, every deadline is tracked, and every assertion the agent makes traces to admissible evidence. The limiting factor is the discovery gap. What strengthens the position most is closing the open question whose resolution would move a contested claim to established, or expose a weakness before opposing counsel does. More argument rarely moves it. Moves are ranked against that intent, not against raw uncertainty. The agent does not chase the most uncertain fact on the board. It chases the gap whose resolution most strengthens the position it has to defend. | | | |---|---| | **Goal** | Support the lawyer's current position. | | **Success** | Case state accurate and current, every deadline tracked, every assertion traceable to admissible evidence. | | **Limiting factor** | The discovery gap whose resolution moves a contested claim to established, or exposes a weakness before opposing counsel does. | ## The policy A matter runs under rules that outrank any single draft the agent might want to produce: | Policy bucket | In a legal matter | |---|---| | **Invariants** | Respect privilege. Every assertion cites evidence. Never act on a superseded fact. | | **Source hierarchy** | The record and a ruling lead a filing; a filing leads an internal memo; a memo leads a rumor. | | **Conventions** | Citation form, when to flag for the partner, how a contested claim is phrased in a draft. | | **Avoid** | Asserting a fact still in discovery as established. Drafting around a privileged document. | When two sources disagree, the hierarchy decides. A signed order outranks the associate's optimistic read of it, every time. ## The actions What the agent may do in this world, grouped by what it costs to be wrong: | Action | Safety class | Effect | |---|---|---| | `summarize`, `cite`, `check-deadline` | **info** | Read-only. | | `draft` | **mutates** | Produces a work product the team will review. | | `file`, `serve` | **needs-approval** | Gated on the lawyer. Nothing reaches the court unsigned. | Today, policy and actions are a modeling lens you encode, not a contract the engine runs for you. You express the invariants, the source hierarchy, and the safety classes in *how* your agent observes and acts; the engine does not yet take a registered policy and enforce it, and a declarative config surface for this is on the roadmap. So read these tables as the contract your agent honors. What the engine supplies is the picture each decision rests on and a record of what every action changed, so a partner can audit the chain before anything is signed. ## Plan & act ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'legal-agent', namespace: 'matter:case-001', writeScope: 'space', }) // 1. Orient: read the current case state; the ranked next moves ride back on it const context = await beliefs.before('What is the current state of the matter?') const [move] = context.moves // top-ranked, already in hand; no extra call // → { action: 'gather_evidence', subType: 'pull-latest-ruling', valueOfInformation: 0.8, ... } // 2. Act: your agent dispatches the tool the move points to const ruling = await runTool(move.subType) // 'pull-latest-ruling' → your docket fetch // 3. Fold the result back in; it becomes the next observation await beliefs.after(ruling.text, { source: 'Order on MSJ, 6/14' }) ``` The verdict the world model renders is the state of the case as the record stands today, and that is what a recall layer cannot do. A RAG store over the matter file would hand the agent the original complaint, the superseded license memo, and the June 14 order with equal footing. It would let the agent draft a motion citing a fact the court already threw out. thinkⁿ marks the license belief superseded, so the agent will not reach for it; it ranks `pull-latest-ruling` above `draft`, because confirming the current record strengthens the position more than another paragraph built on a stale assumption. Once the ruling folds in, the agent drafts from what is established, cites only what is admissible, and the partner can walk every assertion back to the line that supports it. The lawyer stays on the loop for anything filed or served. As a matter accumulates history, the agent can forecast which move most strengthens the position before it acts: `beliefs.forecast.predict(['depose-witness', 'move-to-compel', 'pull-latest-ruling'])`. These forecasts stay deliberately low-confidence until the workspace has enough resolved outcomes to calibrate them against what motions and rulings actually produced. See [Moves](/dev/core/moves). ## Support Source: https://thinkn.ai/dev/cases/support Summary: A support agent that answers from what was promised and shipped, not the thread in front of it. In support, the world model is one current picture per account: what the customer is entitled to, what is broken for them, what has been promised in writing, and what the agent must not say. When a support agent picks up a ticket from an enterprise customer, the thread in front of it (or a CRM card someone pasted in) is rarely the account's actual situation. A belief state that lives outside the model holds that situation instead, so the agent reads the account's real state before every reply rather than answering from the thread. ## The world The **environment** is the account. The customer, their plan and entitlements, their open and historical tickets, and the relationships that tie those together. This ticket is the third recurrence of that export bug. This requester is the org admin, so what they ask for actually moves billing. Ground truth is split across the helpdesk, the CRM, and the running product, which is precisely why no single message holds it. ## What streams in The agent's **observations** arrive from everywhere the relationship lives: - support tickets and their status changes - chat and email transcripts - internal Slack threads about the account - CRM fields, billing events, and entitlement changes Each one revises what the agent believes about the account: its current state, and what it is allowed to do next. ## The belief state The account's live state is a set of claims. Each carries an explicit posterior and its provenance, sitting next to the gaps the agent knows it hasn't filled. ``` ┌──────────────────────────────────────────────────────────────┐ │ ACCOUNT STATE - Enterprise customer │ │ │ │ ● "On the Enterprise plan (SSO + priority SLA)" 98% │ CRM │ │ ● "Export to CSV times out on >50k rows" 88% │ 3 tix│ │ └─ recurrence of BUG-4471 (supersedes "one-off") │ │ ● "Promised a fix by Friday in writing" 80% │ email│ │ ● "Churn risk: renewal in 40d, owner went quiet" 61% │ Slack│ │ │ │ Gap: "Has the export fix shipped to their region?" │ │ ⚠ Contradiction: agent A said "fix shipped"; the build │ │ record shows the account is still on the old build │ └──────────────────────────────────────────────────────────────┘ ``` Read the percentages as the agent's confidence, each tied to a source it can name. The plan is near-certain because it comes straight from CRM. The churn read sits at 61% because it rests on one quiet account owner and a months-old outage, not a hard signal. The agent knows the difference, so it leans hard on the plan and treats the churn read as a hypothesis worth a call. When two agents touch the same account, their pictures fuse into one. "We already told them it's fixed" and "it isn't fixed for them yet" cannot coexist quietly. They surface as a flagged contradiction before they become a second broken promise. ## What we're after The **intent** is to resolve the customer's issue without over-promising. Success is a correct answer that stays inside the account's entitlements and SLA, with nothing committed that engineering hasn't. The limiting factor is the unverified account fact most in the way of that answer, here whether the export fix has actually shipped to their region. | | | |---|---| | **Goal** | Resolve the issue without over-promising. | | **Success** | A correct, in-entitlement, in-SLA answer; no uncommitted promises. | | **Limiting factor** | "Has the export fix shipped to their region?" | The engine ranks the agent's next moves against this intent, not against raw uncertainty. A fact the agent is unsure about only rises to the top when closing it actually moves the goal. ## The policy A support world has firm rules that hold no matter what any single reply wants to say: | Policy bucket | In a support account | |---|---| | **Invariants** | Never promise an unshipped feature or a date engineering hasn't committed. Honor the plan's entitlements, no more. | | **Source hierarchy** | Billing and CRM state lead a chat claim. A shipped build leads a "should be fixed soon." | | **Conventions** | Tone, escalation path, when to loop in the account owner. | | **Avoid** | Issuing credits beyond authority. Speculating about root cause on the record. | When two sources disagree, the hierarchy decides which one the agent trusts. The build record beats the optimistic chat note every time. ## The actions What the agent may do in this world, classified by blast radius: | Action | Safety class | Effect | |---|---|---| | `tag`, `summarize`, `lookup-entitlement` | **info** | Read-only. | | `reply`, `escalate`, `update-ticket` | **mutates** | Changes the account's record or the customer's experience. | | `issue-refund`, `grant-exception` | **needs-approval** | Gated on a human with the authority. | Today, policy and actions are a modeling lens you encode. You express the invariants, the source hierarchy, and the safety classes in *how* your agent observes and acts. The engine doesn't yet take a registered policy and enforce it for you; a declarative config surface is on the roadmap. So treat these tables as the contract your agent enforces, not one the engine enforces on its behalf. What the engine supplies is the picture each decision rests on, plus a record of what every action changed, so a supervisor can audit it after the fact. ## Plan & act ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'support-agent', namespace: 'acct:enterprise', writeScope: 'space', }) // 1. Orient: read the current picture; the ranked next moves ride back on it const context = await beliefs.before('Customer asks if the export bug is fixed for them') const [move] = context.moves // top-ranked, already in hand; no extra call // → { action: 'gather_evidence', subType: 'check-deploy-region', valueOfInformation: 0.8, ... } // 2. Act: your agent dispatches the tool the move points to const deploy = await runTool(move.subType) // 'check-deploy-region' → your deploy-status lookup // 3. Fold the result back in; it becomes the next observation await beliefs.after(JSON.stringify(deploy), { tool: 'deploy_api', source: 'release' }) ``` Here the move the engine ranks first is `check-deploy-region`, not `reply`. The agent holds an 80% promise and a build record that disagrees with a teammate, so the highest-value thing it can do is close the gap before it answers. That is the judgment a recall layer can't make. A RAG store would hand the agent the cheery chat note and the promise email with equal weight and let it confirm a fix that never shipped to that region. thinkⁿ ranks the lookup above the reply, and once the deploy result folds back in, the agent answers from what is true for that account. The audit trail comes with it: every belief the agent leaned on, and what each action changed, is on the record for a supervisor to walk back. As an account accumulates history, the agent can forecast which move most reduces churn risk before it acts: `beliefs.forecast.predict(['escalate', 'offer-workaround', 'schedule-call'])`. These forecasts stay deliberately low-confidence until the workspace has enough resolved outcomes to calibrate them against what actually happened. ## Operations Source: https://thinkn.ai/dev/cases/operations Summary: An ops agent that schedules the right work when supplier backlogs, plant reality, and ERP data keep drifting. In manufacturing, the world model is the shared operating state of the plant: what is actually true right now about demand, capacity, supply, and the schedule, and how sure the business is of each. A manufacturer racing to meet demand is working blind in slow motion. A supplier slips a ship date in an email, one plant runs behind while another sits idle, the ERP shows inventory the floor cannot find, and the real schedule lives in a planner's spreadsheet no one else can see. Each system and each person is right about a slice, and no one holds the whole line. A belief state that lives outside the model reconciles those fragments into one current picture, bounded by the safety and commitment limits you set, so the ops agent schedules the right work instead of the work some stale screen implies. ## The world The **environment** is the manufacturing operation, often across more than one plant: customer orders and due dates, production and work orders, work centers and machines, the bill of materials, inventory and work in progress, suppliers and open POs, and the projects that tie long-lead jobs together. Entities are orders, parts, machines, suppliers, and plants. Relations cross all of them. This order *depends-on* that casting, this subassembly *feeds* final assembly, this supplier backlog *blocks* a job, this rush order *competes-for* the same work center as three others. Ground truth is scattered across the ERP and MRP, the MES on the floor, supplier emails, Teams threads, and the side spreadsheets people actually run the day on. The world model is the layer that reconciles them into one current picture instead of leaving every agent and planner to stitch it together by hand each morning. ## What streams in **Observations** arrive from every system and surface the operation already runs on: - ERP and MRP data: production orders, inventory, purchase orders, and quoted lead times - supplier emails and portal updates, where a ship date slips or a backlog first shows - shop-floor signals: machine status, completed operations, scrap and rework, and what a supervisor reports at shift change - project and change-order updates: milestones, customer commitments, and revised due dates - the spreadsheets and trackers people keep on the side because the system of record never quite fit That last channel is what makes it one picture. When the planner reschedules a job or a buyer confirms a new reship date, that becomes evidence the other agents read, so the schedule each one acts from already carries what the others just learned. ## The belief state The plant's operating picture is a set of claims drawn from every system and shift, each carrying confidence, provenance, and decay, alongside the gaps and conflicts no single screen shows. ``` ┌──────────────────────────────────────────────────────────────┐ │ PRODUCTION OUTLOOK - PLANT 2 │ │ │ │ ● "Order #4812 ships on time" 58% │ ERP + floor │ │ ● "Castings short by ~200 units" 82% │ supplier │ │ ● "Line 2 effective capacity ~70%" 74% │ MES live │ │ ● "Rush job can slot Thursday" 69% │ planner │ │ └─ ⚠ Decayed: floor count last confirmed 3 days ago │ │ │ │ Gap: "No firm reship date from the casting supplier" │ │ Contradiction: ERP shows 200 in stock; floor count says 40 │ └──────────────────────────────────────────────────────────────┘ ``` Every claim carries its source, so a ship date a buyer confirmed reads differently from one the ERP still assumes. The ERP-versus-floor contradiction is the kind that quietly breaks a schedule. It is invisible inside either system and obvious the moment both feed one picture. The agent that surfaces it reconciles every source at once, which is the one thing no single screen or person does. ## What we're after The **intent** is the operation's: meet demand on the dates the business committed to, without breaching safety, quality, or capacity. Success means the schedule reflects real capacity and real supply, the right jobs run in the right order, the commitments the business made are kept, and every scheduling call traces to evidence a manager can inspect. The limiting factor is the cross-system unknown most likely to break the plan, which here is the supplier's true reship date and the real floor count, not the machine telemetry. Moves are ranked against that intent, not against raw uncertainty, so attention lands where being wrong is most expensive. | | | |---|---| | **Goal** | Meet committed demand within safety, quality, and capacity. | | **Success** | Schedule reflects real capacity and supply, the right jobs run in order, commitments kept, every call traceable. | | **Limiting factor** | The cross-system unknown most likely to break the plan. | ## The policy A plant runs under rules that outrank any single shift's convenience. You encode them once, and every agent inherits them: | Policy bucket | Across the operation | |---|---| | **Invariants** | Safety, quality, and regulatory limits hold without exception. Honor customer commitments and contractual due dates. Stay within plant capacity and labor limits. | | **Source hierarchy** | A confirmed floor count outranks an ERP quantity; a signed supplier commitment outranks a verbal "should ship"; a live machine status outranks a stale log. | | **Conventions** | How jobs are sequenced, how a hot order is expedited, how a schedule change is communicated across plants. | | **Avoid** | Scheduling against inventory the floor cannot find; promising a date the line cannot hit; letting one plant's optimistic ETA inflate a company-level commitment. | ## The actions Group what an agent may do by what it costs to be wrong: | Action | Safety class | Effect | |---|---|---| | `reconcile-inventory`, `summarize-schedule`, `flag-shortage` | **info** | Read-only. Surfaces the picture; changes nothing. | | `reschedule-job`, `reorder-part`, `update-eta` | **mutates** | Changes the recorded plan or inventory. | | `commit-customer-date`, `expedite-at-premium`, `reallocate-across-plants` | **needs-approval** | Gated on a human with the authority. | Today, policy and actions are a modeling lens you encode, not a contract the engine runs for you. The safety limits, the source hierarchy, and these safety classes live in how each agent observes and acts. The engine does not yet take a registered policy and refuse the calls that violate it; a declarative config surface for that is on the roadmap. Read the tables as the contract your agents uphold. What the engine guarantees is the rest of the loop: it fuses the signals into one picture, surfaces the conflicts, and keeps the trail, and a qualified human stays on the loop for anything that commits the business. ## Plan & act An operating agent does not start from the work order in front of it. It orients on the whole production picture, reads the cross-system move most at risk, and acts, with the result folding back in for every other plant and planner: ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'production-planner', namespace: 'plant:2', writeScope: 'space', }) // 1. Orient: read the production picture; the ranked next moves ride back on it const context = await beliefs.before('Can we hit the committed dates this week, and what is most at risk?') const [move] = context.moves // top-ranked, already in hand; no extra call // → { action: 'gather_evidence', subType: 'confirm-reship-date', valueOfInformation: 0.8, ... } // 2. Act: your agent dispatches the tool the move points to const supply = await runTool(move.subType) // 'confirm-reship-date' → your supplier portal + email reconciler // 3. Fold the result back in; it becomes the next observation for every plant and planner await beliefs.after(JSON.stringify(supply), { tool: 'supplier-portal', source: 'po+email' }) ``` The verdict is the product. A stack of ERP screens and side spreadsheets hands a planner a dozen views and leaves the reconciliation to a morning standup. The production world model renders the call: order #4812 is at risk on the casting shortage, confirm the reship date and the true floor count before promising the date, and here is the confidence on each claim and the system it came from. Because every belief traces to the email, count, or work order it came off, a `needs-approval` expedite reaches a manager with its evidence attached. The picture every agent and planner acts from stays current, so the operation can scale work without scaling the morning standup. As the operation accumulates action-to-outcome history, an agent can simulate a scheduling move across plants before committing to it (`beliefs.forecast.predict([...])`): if we expedite this casting and slot the rush job Thursday, what happens to the other due dates. It reports low confidence until enough real outcomes have accrued to calibrate, so a simulation is never mistaken for a guarantee. [Moves](/dev/core/moves) covers the forecasting layer. ## Finance Source: https://thinkn.ai/dev/cases/finance Summary: An analyst agent whose thesis tracks the evidence, not the original pitch. In finance, the world model is the live thesis. A claim carries an explicit posterior that every filing, print, and headline strengthens, weakens, or contradicts, so the position tracks the evidence instead of the original pitch. A stale thesis is capital sized to a story the evidence stopped supporting. The pitch said margins would expand. Two quarters later a competitor cuts prices, an input shock lands, and the position is still carried at the original weight because nothing forced a rerate. A belief state that lives outside the model keeps the thesis live, so the analyst agent acts from where it stands today rather than where the pitch left it. ## The world The **environment** is the portfolio and the theses behind it: the names, the sectors they sit in, and the positions held against each view. Entities are companies and instruments. Relations are *competes-with*, *supplies*, and *correlates-with*. Ground truth is the public record (filings, prints, disclosures) and the market's own pricing. ## What streams in **Observations** are the raw material a thesis lives or dies on. The desk feeds in: - 10-Qs, 10-Ks, 8-Ks, and the earnings-call transcript - the print itself: revenue, gross margin, guidance, and the buy/sell-side reaction - sell-side notes, downgrades, and headline flow on the name - macro and rate prints, and regulatory changes that touch the sector Each one moves the posterior on a specific claim, and the engine logs it as the evidence that moved it. The number always carries a receipt back to the line that produced it. ## The belief state A thesis here is not a static memo filed at initiation. It is a claim carrying an explicit posterior that moves as evidence arrives, with every step traceable to the event behind it. ``` ┌──────────────────────────────────────────────────────────────┐ │ THESIS: GROSS-MARGIN EXPANSION │ │ │ │ Q1 Initiated at pitch margins 88% │ 2 sources │ │ Q2 Competitor cuts list prices → 71% │ -17% │ │ Q3 Input costs rise on the print → 58% │ -13% │ │ Q4 Rule change favors incumbents → 67% │ +9% │ │ │ │ ● Each step records which filing or print moved the │ │ posterior and by how much. The position traces to the │ │ events that moved it, not to the day-one pitch. │ └──────────────────────────────────────────────────────────────┘ ``` When a bullish sell-side note and a bearish 8-K land in the same week, the engine does not split the difference into a comfortable middle. It flags the contradiction and surfaces the two claims that conflict, so the PM adjudicates the disagreement rather than inheriting a blended number that buries it. Risk ratings carry an explicit decay schedule. A six-month-old credit or covenant read loses certainty on its own and starts pressuring a reassessment instead of riding forward unchallenged. ## What we're after The goal is plain: keep the position sized to what the evidence currently supports, not to the conviction the pitch started with. Success looks like a thesis that is current rather than remembered, risk that stays inside the mandate, and every move tracing back to a filing or a print. The limiting factor is the claim doing the most work with the least support behind it: the load-bearing assumption that, if it cracked, would force the whole position to rerate. The engine ranks the next move against that intent, not against raw uncertainty, so the desk spends its attention where being wrong costs the most rather than where the posterior happens to be widest. | | | |---|---| | **Goal** | Position sized to what the evidence supports today. | | **Success** | Thesis current, risk within mandate, every move traces to a filing or print. | | **Limiting factor** | The least-supported claim the thesis leans on hardest. | ## The policy A portfolio runs under hard rules that outrank any single attractive narrative. You encode them as the structure the desk works within: | Policy bucket | In a portfolio | |---|---| | **Invariants** | Stay within mandate and position limits. Respect compliance and disclosure constraints. | | **Source hierarchy** | An audited filing ranks over news; news ranks over a rumor or a single-desk note. | | **Conventions** | How the desk sizes positions, when a thesis goes "on watch." | | **Avoid** | Letting a preference for a name inflate the posterior on its thesis. This is the is/ought firewall. | ## The actions Group what the agent may do by what it costs to be wrong: | Action | Safety class | Effect | |---|---|---| | `run-scenario`, `screen`, `summarize-filing` | **info** | Read-only analysis. | | `flag-risk`, `update-thesis`, `set-watch` | **mutates** | Changes the recorded view. | | `rebalance`, `place-order` | **needs-approval** | Gated on a human with the mandate. | Today, policy and actions are a modeling lens you hold, not a contract the engine runs for you: you encode invariants, source hierarchy, and safety classes in *how* you observe and act, the engine doesn't yet take a registered policy, and a declarative config surface for this is on the roadmap. Read the tables as the contract your agent enforces. What the engine guarantees is the rest of the loop: it surfaces the picture and the trail, the agent reasons over it, and capital only moves with a human's sign-off. ## Plan & act ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'equity-research', namespace: 'tech-sector-review', writeScope: 'space', }) // 1. Orient: read the current picture; the ranked next moves ride back on it const context = await beliefs.before('Is the margin-expansion thesis still intact?') const [move] = context.moves // top-ranked, already in hand; no extra call // → { action: 'gather_evidence', subType: 'confirm-margin-trend', valueOfInformation: 0.8, ... } // 2. Act: your agent dispatches the tool the move points to const filing = await runTool(move.subType) // 'confirm-margin-trend' → your filings fetcher // 3. Fold the result back in; it becomes the next observation await beliefs.after(filing.text, { source: 'Q3 10-Q' }) ``` Source hierarchy is policy, so the engine weighs the two notes rather than storing them side by side. The audited filing outweighs the bullish sell-side note, the thesis posterior moves, and the next ranked move is the one that most resolves what is left open. A memory store would hand you back both notes and leave you to reconcile them. Here every step of that reconciliation traces back to the exact line in the filing. As a coverage universe builds history, the agent can forecast which research action most sharpens a thesis before doing the work: `beliefs.forecast.predict(['read-10q', 'check-competitor-pricing', 'model-downside'])`. It reports low confidence until it has enough resolved history to calibrate, so a forecast is never mistaken for a track record. ## Health Source: https://thinkn.ai/dev/cases/health Summary: A clinical agent whose differential stays live until the evidence resolves it. In medicine, the world model is the differential held open. Each hypothesis carries what is wrong, how sure the agent is, and on what evidence, kept live until a test resolves it rather than snapping shut on the first plausible answer. A patient comes in with an elevated HbA1c and a normal fasting glucose: type 2 diabetes, pre-diabetes, or something else? A belief state that lives outside the model keeps that differential live, each hypothesis pinned to the findings that move it, and every step traces to evidence a clinician can inspect and overturn before anything is ordered. ## The world The **environment** is the patient's clinical picture: the presenting complaint, active conditions, the medication list, and the relations between them. Polyuria and weight loss point toward one diagnosis; metformin and a sulfonylurea interact in ways that bound what you can safely add. Ground truth is the objective record, the labs and imaging and vitals you can re-pull. The patient's stated preferences sit on the other side of a firewall, present in the world model but never weighed as evidence. ## What streams in Clinical signal arrives continuously, and each arrival is an **observation**: - lab panels and vitals - imaging and pathology reports - clinical notes and history - published studies and guideline updates Every finding strengthens some hypotheses and weakens others. The model records *which* finding did the moving, so when a posterior shifts you can name the lab that shifted it. ## The belief state A good diagnostician resists premature closure, and so does the agent. It holds the competing hypotheses side by side, each carrying an explicit posterior and its own evidence list, and it tracks the single missing test whose result would most separate the two leaders. ``` ┌──────────────────────────────────────────────────────────────┐ │ DIFFERENTIAL DIAGNOSIS │ │ │ │ ● Type 2 diabetes 72% │ 4 supporting │ │ ├─ Elevated HbA1c (measurement) │ 1 contradicting │ │ ├─ Family history (assertion) │ │ │ ├─ BMI > 30 (measurement) │ │ │ └─ Normal fasting glucose (⚠) │ │ │ │ │ ● Pre-diabetes 61% │ 3 supporting │ │ ├─ Borderline HbA1c │ │ │ ├─ Normal fasting glucose │ │ │ └─ Age and risk factors │ │ │ │ │ ● Stress response 28% │ 1 supporting │ │ └─ Recent life event (⚠) │ 2 contradicting │ │ │ │ Gap: oral glucose tolerance test not performed │ │ Resolving it would settle A vs. B. │ └──────────────────────────────────────────────────────────────┘ ``` Read it top to bottom. Type 2 diabetes leads at 72 percent, but the normal fasting glucose is flagged as contradicting it, which is exactly the kind of tension a memory store would flatten into a note and forget. Pre-diabetes sits close behind on overlapping evidence. The OGTT surfaces as the next-best test on its own, because the model can compute which missing result would most reduce uncertainty between the two leaders rather than waiting for a clinician to think of it. The percentages are independent posteriors per hypothesis, not a partition (they need not sum to 100 percent), so several can be partially supported at once. ## What we're after The agent is not chasing certainty for its own sake. The goal is to narrow the differential safely toward a diagnosis, and the engine ranks moves against that intent rather than against raw uncertainty. The most useful next test is the one that most separates the leading hypotheses, surfaced and ordered only with a clinician's approval. The limiting factor is the gap whose resolution would do the most separating, which is why the OGTT rises to the top and a low-yield repeat panel does not. | | | |---|---| | **Goal** | Narrow the differential safely toward a diagnosis. | | **Success** | The next test is the one that most separates the leaders, ordered only with clinician approval. | | **Limiting factor** | The gap whose resolution most separates the leading hypotheses. | ## The policy Clinical reasoning carries the strictest policy of any world here, and you encode it in how the agent weighs what it sees: | Policy bucket | In a clinical picture | |---|---| | **Invariants** | Do no harm. Stay within scope of practice; a human clinician owns the decision. | | **Source hierarchy** | Strength of evidence: an RCT outranks a cohort study, which outranks a case report; a larger, better-powered trial outweighs a smaller one. You express that ranking in how confidently you observe each source. | | **Conventions** | Established clinical guidelines for the presentation. | | **Avoid** | Letting a preference act as evidence. This is the **is/ought firewall**. "I want to avoid surgery" is a preference, and it must not raise confidence that surgery is unnecessary. A diagnostic finding is evidence, and it updates the picture. The firewall is how *you* model the two: preferences shape goals, only findings move posteriors. | ## The actions Sort the agent's moves by how much damage a wrong one could do: | Action | Safety class | Effect | |---|---|---| | `suggest-differential`, `summarize-history` | **info** | Read-only. Surfaces; decides nothing. | | `flag-interaction`, `update-record` | **mutates** | Annotates the record. | | `order-test`, `recommend-treatment` | **needs-approval** | Gated on a clinician. | Today, policy and actions are a modeling lens you hold rather than machinery the engine runs. You encode the invariants, the source hierarchy, and these safety classes in *how* you observe and act. The engine does not yet take a registered policy; a declarative config surface is on the roadmap. Read these tables as the contract your agent enforces, not one the engine enforces for you. The agent surfaces and ranks; a clinician acts. The verdict is the product here, and the evidence chain is what makes it trustworthy. Because every belief carries its provenance, a clinician can correct the picture *before* it informs a decision rather than discovering the error in the chart afterward. ## Plan & act ```ts import Beliefs from 'beliefs' const beliefs = new Beliefs({ apiKey: process.env.BELIEFS_KEY, agent: 'clinical-agent', namespace: 'patient-123', writeScope: 'space', }) // 1. Orient: read the current picture; the ranked next moves ride back on it. const context = await beliefs.before('What would most clarify this differential?') const [move] = context.moves // top-ranked, already in hand; no extra call // → { action: 'gather_evidence', subType: 'order-ogtt', valueOfInformation: 0.8, ... } // 2. Act. order-ogtt is needs-approval, so a human gates it. // A clinician approves; the order runs and results return as evidence. const labs = await runApprovedTool(move.subType) // 'order-ogtt' → a clinician-approved order // 3. Fold the result back in: it becomes the next observation. await beliefs.after(labs.text, { source: 'Lab panel 2026-03-15' }) ``` The OGTT result re-weights the hypotheses; the patient's stated wish never does. That is the firewall working in the loop. The trace is what backs the verdict, showing exactly which finding moved which hypothesis, so the reasoning is auditable end to end. As a workspace accumulates outcomes, the agent can forecast which workup most reduces diagnostic uncertainty across the next several steps. Call `beliefs.forecast.predict(['order-ogtt', 'repeat-hba1c', 'lipid-panel'])` and it reports low confidence honestly until it has the resolved history to calibrate against. --- # Internals ## Architecture Source: https://thinkn.ai/dev/internals/architecture Summary: Three layers, one loop. How the environment, belief state, and your agent connect, and what happens on every turn. ## The loop A world model is the full frame an agent acts from: its [environment](/dev/core/environment), the [observations](/dev/core/observations) streaming in, the [belief state](/dev/core/beliefs) over it, the [intent](/dev/core/intent), the [policy](/dev/core/policy), and the [actions](/dev/core/actions) available. This page is about the loop those parts run in; see [World model](/dev/core/world) for each part in depth. Every turn runs the same loop. ``` ┌──────────────────────────────────────────────────────────────────┐ │ ENVIRONMENT │ │ codebase · market · customer · patient · system │ └────────┬───────────────────────────────────────▲─────────────────┘ │ observe │ act ▼ │ ┌──────────────────────────────────────────────┴───────────────────┐ │ BELIEF STATE │ │ │ │ PAST PRESENT FUTURE │ │ ──── ─────── ────── │ │ ledger ───► active claims ───► ranked moves │ │ evidence confidence (next action) │ │ provenance contradictions (by info gain) │ │ │ └────────┬───────────────────────────────────────▲─────────────────┘ │ before() → context.prompt │ after(output) │ │ → extract + fuse ▼ │ ┌──────────────────────────────────────────────┴───────────────────┐ │ YOUR LLM + AGENT │ │ Claude · GPT · Gemini · any │ └──────────────────────────────────────────────────────────────────┘ ``` ## The four phases Every turn passes through four phases. Three are calls you make; one is internal. ### 1. Observe: `after(output)` New evidence enters the belief state. Tool results, agent outputs, user messages, file reads: anything you pass to `after()` is extracted into structured claims, fused with the existing state, and folded into the ledger. ```ts const delta = await beliefs.after(toolResult) // delta.changes lists every belief added, modified, or retracted ``` Internally: extraction → semantic linking → fusion. See [How it works](/dev/internals/how-it-works). ### 2. Hold: the belief state At any moment the belief state has three tenses, each surfaced through the SDK: | Tense | What | Read via | |---|---|---| | Past | Append-only ledger of every observation, with evidence type and provenance | `beliefs.trace(beliefId)` | | Present | Active claims with confidence, edges, contradictions, gaps, clarity score | `beliefs.read()` | | Future | Ranked next actions by expected information gain | `beliefs.moves.list()` | ### 3. Inject: `before(input)` The belief state is rendered into a prompt fragment your LLM call can consume. This isn't dumping everything. The engine selects relevant claims, surfaces open contradictions, includes the ranked next moves, and respects context budget. ```ts const context = await beliefs.before(userMessage) const result = await myAgent.run({ system: context.prompt }) ``` `context` also exposes the structured fields (`beliefs`, `gaps`, `moves`, `clarity`) so you can branch on state without parsing the prompt. ### 4. Act: the LLM run Your agent runs against the injected context and produces output. That output becomes the next observation. Loop. ## Multi-agent fusion When multiple agents share a `namespace` and `writeScope: 'space'`, they contribute to one fused belief state. Each agent has an `agent` identifier; the engine merges contributions trust-weighted, in timestamp order, surfacing cross-agent contradictions that no single agent could see. ```ts const researcher = new Beliefs({ apiKey, namespace: 'market-map', agent: 'researcher', writeScope: 'space' }) const critic = new Beliefs({ apiKey, namespace: 'market-map', agent: 'critic', writeScope: 'space' }) await researcher.after(researchOutput) await critic.after(criticReview) // Both see the same fused world const world = await researcher.read() console.log(world.contradictions.length) ``` See [Scoping](/dev/sdk/scoping) for namespace, writeScope, thread, and contextLayers. ## What this gets you - **Coherence across turns.** The agent doesn't drift back to its training prior because the user-correction belief has higher evidence weight. - **Coherence across agents.** Multiple agents in one namespace share one fused world; contradictions become visible. - **Auditable decisions.** Every claim has a ledger entry. `beliefs.trace(id)` returns the full provenance walk. - **Direction.** The future tense is first-class. Moves rank what to do next by information gain, not vibes. ## Where to next ## How it works Source: https://thinkn.ai/dev/internals/how-it-works Summary: The lifecycle of a belief: from observation to fused state, with audit and decay along the way. A mental model of what happens when you call `before` and `after`. The behaviors the engine is required to honor (what you build against) live on the [contracts](/dev/internals/contracts) page. ## The lifecycle Every piece of information that enters the system follows the same path: ``` observation ──▶ extraction ──▶ fusion ──▶ persistence + audit │ │ │ structured merged into ledger entry claims out world state for replay ``` You don't manage this lifecycle yourself. Calling `before` and `after` drives it. The runtime mutates state atomically and serially, so every mutation is durable and observable the moment it lands. That's what lets an agent course-correct mid-turn. If the first tool result contradicts a hypothesis, the next call already sees the updated state. The runtime processes updates on two timescales. **Real-time** updates merge as evidence arrives, so later actions in the same turn operate on the newest understanding. **Background** processing runs more thorough analysis between turns: relationship detection, contradiction analysis, reassessment of the overall picture. Both feed the same belief state. ## Fusion: combining contributions When multiple agents (or multiple turns of the same agent) submit beliefs about the same claim, the engine merges them by trust weight. Higher-trust contributors move the fused state more; lower-trust contributors still contribute but with proportionally less pull. The fused state sharpens when sources agree and stays uncertain when they disagree. Each agent and source carries a reliability weight. The engine starts with a calibrated baseline based on observed reliability and you override it at runtime via [`beliefs.trust.set()`](/dev/sdk/trust). Trust knobs behave predictably. Lowering an agent's weight attenuates its contributions proportionally without affecting any other source. Fusion is order-independent: combining the same set of contributions in any order produces the same result. Retries after a peer's write don't change the outcome. ## Decay: aging evidence Without decay, agents act on stale analyses indefinitely. A six-month-old market estimate would carry the same weight as last week's verified data. Decay closes that gap: every belief's evidence weight shrinks over time, so old claims lose influence unless refreshed. Stale claims surface for re-verification rather than silently dominating. Decay rates are configurable per workspace: - **Fast:** market sentiment, competitive intelligence, security posture. Anything where last month's analysis is probably wrong now. - **Standard** (default): market sizing, product positioning, strategic analyses. Slow-moving but not static. - **Slow:** regulatory environments, fundamental research, architectural invariants. Evidence stays relevant for quarters or years. - **None:** ground-truth observations and immutable historical facts. Use sparingly. Decay applies on read, so the runtime always works with time-adjusted values. Decayed beliefs aren't deleted. They stay in the snapshot at reduced weight, so a UI can render them as muted/needs-re-verification rather than hiding them outright. ## Evidence: types and the is/ought firewall Different evidence types carry different weight at fusion time, calibrated so quality matters more than volume. A single verified measurement moves confidence more than several inferences. | Type | Typical source path | |------|---------------------| | `measurement` | Tool results from APIs, databases, instrumentation | | `citation` | Tool results with cited sources; explicit `add(text, { source })` | | `user-assertion` | `after(userMessage)` from a user-facing surface | | `expert-judgment` | `add(text, { evidence })` with attributed reasoning | | `inference` | `after(agentOutput)` extraction (default for free-form agent text) | | `assumption` | `add(text, { type: 'assumption' })` | The engine assigns the type based on the source path during extraction. You can override with the `evidence` option on `add()` when you know better. **The is/ought firewall** is the most important design choice in evidence handling. Factual evidence updates beliefs; normative information (preferences, goals, desires) does not. | Input | Effect | |-------|--------| | "The TAM is $5B" | Updates the market size belief | | "Customer X reported a SOC2 audit failure on 2025-09-12" | Updates compliance/risk beliefs | | "I want to target enterprise" | Recorded as a goal (intent) | | "We've decided to target SOC2-compliant buyers" | Recorded as a constraint (intent) | | "Gartner reports 34% growth" | Updates the growth-rate belief | Without this separation, a user repeating "I want X" would gradually inflate the agent's confidence that X is *true*: preferences masquerading as evidence. The firewall keeps factual claims and normative intent on separate tracks. See [Intent](/dev/core/intent) for how the normative side is handled. ## Ledger: the audit trail Every belief mutation lands in an append-only ledger. There's no in-place editing, no silent overwrite, no merge that erases history. If a belief exists in any state today, the ledger says how it got there. Each entry captures what changed, who changed it, the state before and after, and a human-readable reason. Supersession is recorded as a new entry referencing the old one; deletions land as tombstone entries rather than erasing history. ```ts // Workspace-wide trail const all = await beliefs.trace() // One belief's history const history = await beliefs.trace('claim_market_size') for (const entry of history) { console.log(`${entry.timestamp} | ${entry.action}`) if (entry.confidence) { console.log(` ${entry.confidence.before} → ${entry.confidence.after}`) } if (entry.reason) console.log(` reason: ${entry.reason}`) } ``` For replay-shaped reads ("what did the world look like at time T?"), use [`beliefs.stateAt({ asOf })`](/dev/sdk/core-api#beliefsstateatoptions). It walks the ledger and rebuilds state for you. The ledger is what makes calibration analysis possible (compare stated confidence with eventual outcomes), what makes debugging confidence shifts tractable ("why did this belief drop from 85% to 72%?"), and what makes audit trails possible without reconstruction. ## Behavioral contracts Source: https://thinkn.ai/dev/internals/contracts Summary: What the engine guarantees: the behaviors you can build against. Eight guarantees about how the engine behaves under your code. Each is testable, audited, and load-bearing for the SDK above. A regression on any of these is release-blocking. The implementation behind these guarantees evolves as the engine improves. The guarantees themselves are stable. The surface you build against doesn't shift underneath you. --- ## 1. Retries and no-ops are safe Replaying the same `(idempotencyKey, scope)` on `add`, `after`, or `observe` produces the same state. Empty inputs (`after('')`, an empty `BeliefDelta`) leave state unchanged. **What this means for you:** at-least-once delivery from queues, webhooks, or flaky networks doesn't double-count evidence. Defensive patterns ("call `after()` every turn even if nothing happened") are zero-cost and zero-risk. --- ## 2. Fusion is order-independent Combining the same set of contributions in any order produces the same result. **What this means for you:** retrying a failed `after()` after a peer's write doesn't change the outcome. Multi-agent pipelines have no hidden ordering bug class. --- ## 3. Trust knobs behave predictably Lowering an agent's or source's trust attenuates its contributions proportionally without affecting any other agent or source. Locked overrides stay where you set them; the engine's learning never drifts them. **What this means for you:** `beliefs.trust.set({ kind: 'agent', id: 'unreliable-scout' }, { confidence: 0.1, strength: 50 })` reduces that scout's pull at fusion time without surprising side effects elsewhere. --- ## 4. Older evidence carries less weight Evidence is downweighted by a freshness factor as time passes, scaled to the workspace's configured decay rate. Stale claims surface for re-verification rather than silently dominating fresh ones. **What this means for you:** the system creates pressure to refresh. Old analyses lose their grip without being deleted, and new evidence wins on equal footing. --- ## 5. Confidence labels are calibrated When the SDK reports `confidence: 'high'`, those events resolve true at roughly the rate the label implies. Calibration is enforced in CI; regressions don't ship. **What this means for you:** the labels are honest. You can route on `'high' / 'medium' / 'low'` without building your own calibration layer on top. --- ## 6. Supersession is a clean cut When belief B explicitly supersedes belief A, A leaves the active candidate set. `read()` and `list()` no longer return A; `trace()` still surfaces it for audit. **What this means for you:** an agent updating its position on a claim doesn't leave the prior position competing for attention. Audit history is preserved separately from current state. --- ## 7. Belief shapes don't contaminate each other Beliefs of different shapes (binary, categorical, numeric) compose safely. Adding a categorical claim doesn't perturb a binary one. **What this means for you:** multi-modal world models are safe. Your numeric measurements aren't at risk from a new yes/no claim landing in the same workspace. --- ## 8. Confidence and evidence count are tracked separately A claim at 70% with 100 supporting observations is a different signal from a claim at 70% with 2 observations. The SDK exposes both. See [Clarity](/dev/core/clarity) for the two-channel model. **What this means for you:** you can distinguish "we haven't investigated yet" from "we've investigated extensively and the answer is genuinely close." They demand opposite next actions. --- ## What's deliberately not promised - **Extraction model choice.** The model behind `after()` and `observe()` may change between releases. Only the *shape* of the resulting `BeliefDelta` is contracted. - **Absolute confidence numbers across version bumps.** Calibration shifts when models swap; the calibration *quality* is bounded, not the exact numbers. - **Cost or token usage.** Telemetry is intentionally not part of the public SDK contract. - **Implementation details.** How fusion combines contributions, how decay scales evidence, how confidence is computed: these evolve as the engine improves. Build against the guarantees, not the implementation. If you find a case where SDK behavior appears to violate one of these contracts, file it as a P0.