# Contextful — full documentation
> Local-first collaboration workspace for your agents. Humans and their AI
> agents co-edit shared documents; every agent sees only the context it is
> permitted to — scoped by capability, enforced on your own machines.
Home: https://www.contextful.work/ · Live demo: https://demo.contextful.work/
---
# Local-first & data ingestion
Canonical: https://www.contextful.work/docs/local-first-ingestion/ (Markdown: https://www.contextful.work/docs/local-first-ingestion.md)
Contextful is local-first in the literal sense: the authoritative copy of every
document, every synthesized memory, and every access-control key lives on a machine
**you** run — a Mac Studio in the office, a box in your rack — under `~/.contextful`.
Connectors pull your company's real surfaces (Stripe, Slack, PostHog, and more) into
that store, and the brain synthesizes them into **human-readable Markdown memory** you
can open, edit, and `git diff`. Cloud services are optional accelerators, never the
home of your data.
## Where your data lives
The host runs one binary (`sync serve`) on your own hardware. Everything it knows sits
in one directory:
```
~/.contextful/
control/ # principals, keys, policy envelopes
docs/ # per-document CRDT snapshots + oplogs
brain/ # synthesized memory — Markdown files, per topic
brain.duckdb # raw events, index, embeddings, anomalies
caps/ # issued/attenuated token records (audit trail)
```
Peers — browsers, teammates' machines, agents — reach the host over your own
[Tailscale](https://tailscale.com) network (a WireGuard mesh). There is no Contextful
cloud in the data path: the network is yours, the disk is yours.
## How ingestion works
Every source goes through one connector contract: a connector declares the **views**
it exposes (the unit of access control) and pulls raw events, each stamped with
provenance and an access tag (`acl_tag`) at the moment it enters the system.
```mermaid
flowchart LR
CON["Connectors
Stripe · Slack · PostHog · Exa"] --> ING["Ingest
raw events + provenance + acl_tag"]
ING --> EXT["Extract
atomic facts & entities"]
EXT --> SYN["Synthesize
dedupe · supersede · summarize → Markdown"]
SYN --> IDX["Index
full-text + embeddings + structured views"]
IDX --> SRV["Serve
capability-filtered retrieval"]
SYN --> ANO["Anomalies + learnings
baseline vs. period"]
ANO --> SRV
```
Three properties matter:
- **Memory is Markdown, not a vector dump.** Synthesized knowledge is a tree of
Markdown cards a human can read. Cards self-wire: typed wikilinks in the prose
become graph edges, so the brain is a navigable knowledge graph.
- **Access tags travel with the data.** A derived memory inherits the *strictest*
access requirement of its sources (taint propagation) — synthesis can never launder
a private fact into a public card.
- **Nothing is destroyed.** Stale facts are superseded with a timestamp, never
overwritten, so the brain's history is auditable.
Ingestion runs on demand (`sync ingest --source stripe`) or on a cron schedule that
keeps the brain fresh — nightly Stripe, hourly web enrichment, and an off-peak
**daydream cycle** in which the brain proposes and grounds new connections between
cards on its own.
## The world stays outside, on purpose
Agents ground answers in public knowledge — list prices, benchmarks, vendor
changelogs — via the Exa search API. Those world facts are cached locally with their
source URLs, so every external figure is cited. Outbound queries pass an **egress
firewall**: only public-tainted terms may leave the host, so a private value can never
be smuggled out inside a search string.
## What happens when the cloud goes away
Local-first is tested, not aspirational. With no cloud credentials at all, Contextful
degrades to an on-host floor — never to fakes:
- Structured brain queries and field/row redaction need **no LLM** and keep working.
- Inference falls back to a local model server (LM Studio) on the host.
- Already-fetched world knowledge serves from cache; only fresh lookups pause.
- Documents remain editable offline and merge cleanly when peers reconnect — see
[Collaboration & CRDT](/docs/collaboration-crdt/).
The boundary that protects your data — described in
[Sandbox & capability tokens](/docs/sandbox-capability-tokens/) — is deterministic
code on the host, so it holds with or without an internet connection.
---
# Sandbox & capability tokens
Canonical: https://www.contextful.work/docs/sandbox-capability-tokens/ (Markdown: https://www.contextful.work/docs/sandbox-capability-tokens.md)
Every Contextful document is paired 1:1 with an **isolated, disposable sandbox** where
that room's agents run. An agent inside holds **no ambient authority** — no filesystem,
no open network; its only door to data is the brain's MCP interface, and every call
through that door is checked against a cryptographic **capability token** before a
single field crosses. The sandbox stores nothing durable and expires with the room.
## One sandbox per document
The sandbox opens when someone enters the room and expires when the last member
leaves. Provisioning is owned by the **host** — entering a room only signals presence;
it carries no authority to spin up compute. The host mints each agent's identity at
launch.
```mermaid
flowchart TD
R["Room entered"] --> P{"Sandbox alive?"}
P -- "no / expired" --> CR["create sandbox"]
P -- yes --> RE["reuse"]
CR --> AG["agent runtime
(identity = capability token)"]
RE --> AG
AG -- "brain MCP — every call checked" --> MCP["host: brain + policy engine"]
AG -. "denied → access request" .-> OWN["owner approves → narrower token"]
OWN --> AG
```
The trust boundary is **data egress, not compute locality**: whether the sandbox is a
cloud microVM or a constrained on-host process, it only ever receives the redacted,
capability-filtered slice the host's brain returns. Findings flow back through a
taint-tracked write path; nothing private accumulates in the sandbox itself.
## Capability tokens: authority you can only narrow
Access control is built on [Biscuit](https://www.biscuitsec.org/) tokens —
cryptographically signed, **attenuable** credentials whose policy is written in
Datalog and verified on every query.
Two design rules remove the usual single point of failure:
- **No super-root.** The control plane can register people and share documents, but it
cannot mint authority over data. Each sensitive resource — say Stripe's
`finance_private` view — has its **own root key, held by its owner** (the CFO).
- **Attenuation only narrows.** Delegating appends a block to the token that can add
restrictions but never authority. An agent's capability is provably a subset of its
owner's.
A delegation that passes on finance access while redacting salary looks like this:
```datalog
check if operation($op), $op == "query";
check if resource(view("stripe", $v));
deny if field("stripe", $any, "employee_salary");
allow_field("stripe", "finance_private", "discount_tier");
allow_field("stripe", "finance_private", "credits");
```
Because blocks are append-only and checks accumulate, no downstream holder can regain
what a parent denied.
## Enforcement happens before data moves
The host's brain query layer authorizes **each field and each row** against the
caller's token — structured results are column-redacted and row-filtered; prose memory
cards are all-or-nothing against their access tag. Redactions are *signaled*, never
silent, so an agent knows it may ask for more. All of this is deterministic code with
no LLM in the loop — the boundary is a policy engine, not a model's good manners.
When an agent is denied outright, it raises a structured **access request** naming
exactly the fields, rows, and time-to-live it needs. The owner's policy envelope
auto-approves safe, in-scope requests; anything beyond it escalates to the human.
Approval mints a fresh token narrowed to exactly the requested slice.
```mermaid
flowchart LR
A["Agent denied"] --> R["access request
(fields · rows · ttl · reason)"]
R --> O{"Owner's policy envelope?"}
O -- inside --> AUTO["auto-approve"]
O -- outside --> H["human decides"]
AUTO --> M["mint narrowed token"]
H -- approve --> M
H -- deny --> X["stays blocked"]
M --> RETRY["agent retries → answers"]
```
## The salary invariant
The property the whole design defends: **no token and no approval path outside the
CFO's own root ever yields `employee_salary`.** It is enforced at every layer — the
token grammar, the query-time authorizer, the request flow (which never even renders
an approve button for it), background synthesis (derived memories inherit the
strictest tag of their parents), and the egress firewall (a salary-tainted term is
blocked before it can reach the network). It is proven by property tests, not promised
by a prompt.
Every minted token, attenuation, and access request is recorded under
`~/.contextful/caps/` — the audit trail is part of the
[local-first store](/docs/local-first-ingestion/), on your disk.
---
# Collaboration & CRDT
Canonical: https://www.contextful.work/docs/collaboration-crdt/ (Markdown: https://www.contextful.work/docs/collaboration-crdt.md)
A Contextful document is a real-time collaborative room where **humans and their
agents are equal peers**: one shared roster, live cursors, and the same edit stream
for both. Under the hood every document is a [Loro](https://loro.dev) **CRDT** —
a data structure whose edits merge without conflicts by construction — synced through
a relay on your own machines. That's what makes collaboration local-first: the
document on your device is the real document, with or without a connection.
## Rooms: the collaboration unit
A room binds together one document, its members — people *and* agents, each with a
`read` / `write` / `comment` capability — and a paired
[sandbox](/docs/sandbox-capability-tokens/) where the room's agents run. Sharing a
document never widens what anyone's agent can read from the brain: room membership and
data access are separate boundaries, and the narrower one always wins.
Agents edit through exactly the same path as humans. Every agent edit carries an
origin tag, so provenance and per-peer undo work the same for a colleague and a
copilot.
## How sync works
The host's relay is the single authoritative peer; browsers, headless clients, and
sandbox agents all sync through it over the company's own Tailscale network.
```mermaid
sequenceDiagram
participant C as Peer (browser / client / agent)
participant S as Relay (your host)
C->>S: HELLO { principal, capability token }
S->>S: verify token
S->>C: HELLO_OK
C->>S: SUBSCRIBE { doc, my version }
S->>C: SNAPSHOT { document state }
par live
C->>S: UPDATE { CRDT bytes }
S->>C: UPDATE { relayed from peers }
C-->>S: AWARENESS { cursor · selection · presence }
S-->>C: AWARENESS { ... }
end
```
Three details do the heavy lifting:
- **CRDT payloads are opaque bytes to the relay.** The relay authenticates peers and
broadcasts updates; it doesn't need to understand your content to sync it.
- **Version vectors make catch-up cheap.** A returning peer sends what it has; the
relay replies with exactly the missing delta, not the whole document.
- **Authorization rides the wire protocol.** The capability token arrives in the
handshake and revocation is re-checked continuously — a principal revoked
mid-session is dropped.
## Presence: seeing each other (and the agents) work
Who is in the room — and whether they're reading or writing — rides an ephemeral
awareness channel, separate from document edits and never persisted. Agents publish
presence too: when an agent is drafting, you see it in the roster and its cursor moves
in the document, exactly like a human collaborator. Awareness is deliberately dumb: it
carries cursors and presence, never document content or brain data.
## Offline is a feature, not a failure mode
Because the document is a CRDT, edits made offline are just updates that haven't been
delivered yet. When a peer reconnects, both sides exchange deltas and converge —
no locks, no "someone else has this open", no merge dialogs. The web editor persists
locally in the browser, the desktop client syncs documents to plain local files, and
the relay keeps a snapshot + oplog per document on the host's disk
(see [where your data lives](/docs/local-first-ingestion/)).
The live demo at [demo.contextful.work](https://demo.contextful.work) runs this exact
stack — open it in two windows and watch the cursors.