---
title: Core API
description: "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` | — | **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` | — | 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. |

<Callout type="info" title="Quickstart vs chat apps">
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.
</Callout>

### `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.

<Callout type="warning" title="prompt is a serialized blob">
`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.
</Callout>

---

## `beliefs.after(text, options?)`

Feed the agent's output. Beliefs are extracted, conflicts detected, and the world state 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 state.

---

## `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 `'<kind>:<id>'` string. **Currently only `'block:<id>'` 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.

<Callout type="warning" title="Destructive operation">
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.
</Callout>

---

## `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
}
```

<details>
<summary>Additional fields</summary>

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 |

<Callout type="tip" title="The two-channel insight">
`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."
</Callout>

</details>

### 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
}
```

<details>
<summary>Clarity channels</summary>

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
}
```

</details>

<CardGroup cols={2}>
  <DocsCard title="Patterns" description="How to structure your agent loop and the smaller integration patterns." href="/dev/sdk/patterns" />
  <DocsCard title="Scoping" description="Namespace, thread, and agent isolation." href="/dev/sdk/scoping" />
</CardGroup>
