Contextfulv0.23.0

Docs / Specification

contract topology · 01-topology.md · guide · card

System topology

Contextful runs connectors, stores Parquet files and serves queries under access controls. Operators use this page to choose a binary and deployment. Implementers use the component, dependency and coordination requirements below.

Deployment instructions name the executables, embedded libraries and storage services:

compose

Execution, storage, queries, access controls and the contracts these components share.

package

Rust package dependencies and the capabilities each compiled binary includes.

Each profile selects the capabilities of one contextful executable at compilation. Deployment diagrams show where that executable runs: directly on Linux or macOS, or inside a Linux container.

unsettled: Does the edge profile pull native connectors through an execution core that links no scheduler? owner: topology affects: topology.package

deploy

Deployment configurations, provider services and comparison against the reference deployment.

  • unsupported-shapetestA target profile claiming a shape its provider does not provide raises TargetShapeUnsupported, naming the shape and the missing primitive.
    WhyA-topology#deploy-observation
  • wall-clock-captestA target capping per-invocation wall clock at 15 min hosts neither a first-time backfill nor a component connector, and its target profile records both exclusions.
  • paritytestOne declaration produces byte-identical table parts on every control-plane target; contextful-ci deploy parity compares each target’s object-store parts with the reference target’s.
    WhyA-topology#deploy-observation
  • parity-divergencetestA byte difference from the reference output raises ParityDivergence, naming the target, the table and the first differing part.
    WhyA-topology#deploy-observation

Provider configurations live under spec/targets/; corpus.targets checks those files. The generated deployment tables and diagrams identify processes, embedded libraries and provider services. Diagram groups distinguish process, container and operating-system boundaries.

Native Linux deployment

The Linux reference map below places the scheduler, its run children, HTTP server and embedded libraries on one host. Each service uses the same project directory, including contextful.toml, applied control snapshots, execution files and corpus files. The directory persists across process restarts; the optional console has its own Node.js process and configured Rust HTTP endpoint.

The examples assume an existing project with an applied snapshot, readable issuer.key, independently configured issuer public-key pins, and the source credentials its declaration names. The scheduler and HTTP server inherit that environment. contextful-local is the example token audience; 4 is the example HTTP concurrency setting. From that project’s directory, two terminals or two service-manager units run:

contextful pipeline serve --issuer-key issuer.key
contextful serve --http 127.0.0.1:8080 --issuer-key issuer.key \
  --audience contextful-local --max-in-flight 4

Linux Docker Engine also runs the image and container commands below, sharing the host’s Linux kernel. Docker Desktop on macOS adds the Linux VM shown in the Docker map.

macOS without Docker

Apple Silicon uses the aarch64-apple-darwin executable; Intel uses x86_64-apple-darwin. From the repository root, a native source installation selects the full profile:

cargo install --locked --path crates/contextful-cli \
  --no-default-features --features contextful-full

The project’s two startup commands above run directly as macOS processes. Wasmtime, Tokio, DuckDB and SQLite stay embedded in those processes. For managed startup, launchd supervises the scheduler and HTTP server. Project state sits on the Mac’s filesystem. This path uses the Darwin binary and a native Rust/C++ build toolchain, with no Linux VM or Docker daemon. The optional Node.js console uses http://127.0.0.1:8080 as its configured store endpoint.

macOS with Docker

Docker Desktop supplies a Linux VM; its Docker Engine runs the same Linux image a Linux server uses. The Rust processes and embedded libraries run inside containers in that VM. The browser and optional native Node.js console reach a published localhost port.

The image build runs from the repository root. Apple Silicon selects ARM64 explicitly:

docker build --platform linux/arm64 --build-arg PLATFORM=linux/arm64 \
  --build-arg RUST_TARGET=aarch64-unknown-linux-musl -t contextful:local .

Intel Macs select AMD64; this also selects an emulated AMD64 build on Apple Silicon:

docker build --platform linux/amd64 --build-arg PLATFORM=linux/amd64 \
  --build-arg RUST_TARGET=x86_64-unknown-linux-musl -t contextful:local .

The Dockerfile’s PLATFORM and RUST_TARGET arguments select matching builder and executable architectures. --platform alone leaves the Dockerfile’s AMD64 defaults in place. Emulation adds translation cost; an ARM64 image matches an Apple Silicon Linux VM.

From the existing project directory, these commands use a shared bind mount at /data. runtime.env supplies the configured public-key pins and source credentials through Docker’s --env-file; the issuer seed stays in issuer.key. The invoking non-root user’s UID and GID keep writes owned by the project owner. Each terminal runs one container:

docker run --rm --init --name contextful-scheduler \
  --user "$(id -u):$(id -g)" --env-file runtime.env \
  --mount "type=bind,src=$PWD,dst=/data" \
  contextful:local pipeline serve --issuer-key /data/issuer.key
docker run --rm --init --name contextful-http \
  --user "$(id -u):$(id -g)" --env-file runtime.env \
  --mount "type=bind,src=$PWD,dst=/data" -p 127.0.0.1:8080:8080 \
  contextful:local serve --http 0.0.0.0:8080 --issuer-key /data/issuer.key \
  --audience contextful-local --max-in-flight 4

The container listens on 0.0.0.0:8080; Docker publishes it at 127.0.0.1:8080 on macOS. /data persists in the host project directory when --rm removes a container. A Docker named volume stores project state when the operator copies configuration and keys into that volume. Both containers mount the same volume. A project uses one writer mode at a time: switching between native and Docker stops its scheduler and run children before reusing the directory.

Declared file and component paths resolve inside the container; files outside /data need an explicit mount. A source or model endpoint running on the Mac uses host.docker.internal from Docker Desktop containers. Container localhost names the container itself; the native console uses the published Mac loopback port.

The Linux VM contains the containers; the project directory remains on macOS through Docker’s file sharing.

Flowchart diagram Flowchart, top to bottom. 7 nodes, 8 edges. macOS host: MCP or HTTP client → Rust HTTP handlers [localhost published port] macOS host: persistent project directory Docker Desktop Linux VM / contextful HTTP container: Rust HTTP handlers → DuckDB library [admit query] Docker Desktop Linux VM / contextful HTTP container: DuckDB library → persistent project directory [read /data snapshot] Docker Desktop Linux VM / contextful scheduler container: Rust scheduler → Rust run dispatcher [spawn run or fire]; → persistent project directory [lease through /data] Docker Desktop Linux VM / contextful scheduler container: Rust run dispatcher → Wasmtime component host [component invocation]; → persistent project directory [journal through /data] Docker Desktop Linux VM / contextful scheduler container: Wasmtime component host → persistent project directory [commit through /data] macOS host MCP or HTTP client persistent project directory Docker Desktop Linux VM contextful HTTP container admit query Rust HTTP handlers DuckDB library contextful schedulercontainer spawn run or fire component invocation Rust scheduler Rust run dispatcher Wasmtime component host localhost published port read /data snapshot lease through /data journal through /data commit through /data

unsettled: Which role hosts heavy compute on a provider exposing neither a container primitive nor a long-running function? owner: topology affects: topology.deploy

Deployment targets

AWS

VPC-bound and regulated deployments, and teams already operating on AWS.

The scheduled-fargate target runs every process as the native Linux contextful-full executable in Fargate containers. EventBridge Scheduler starts contextful pipeline serve --cycle as an ECS task on a fixed rate; the cycle takes the cadence lease, runs every due unit and exits. A long-running contextful serve behind an Application Load Balancer answers queries and pulls the bucket every 30 s. The query process embeds DuckDB; component execution embeds Wasmtime. RDS Postgres supplies the shared catalog, the engine journal in it makes every run durable and resumable, and S3 holds corpus objects. The fargate-daemon alternative keeps scheduling in one always-on task for sub-minute cadences.

The OpenTofu configuration under apps/aws-fargate/ provisions either shape: a private VPC with its service endpoints, RDS Postgres, the S3 corpus bucket, the SQS queue with its dead-letter queue and object-arrival notification, the ECS cluster, the cycle task or daemon service, and the serve service behind the load balancer.

Rolescheduled-fargate (default)fargate-daemon
cron tickEventBridge Schedulerin-process scheduler
reconcilerpipeline serve --cycle in the scheduled Fargate taskin-process reconcile loop
durable orchestratorengine journal in the catalogengine journal in the catalog
single-writer catalogRDS Postgres via pg-catalogRDS Postgres via pg-catalog
object storeS3S3
hot-path query faceALB → contextful serve on FargateALB → contextful serve on Fargate
heavy computein-process component host of the scheduled taskin-process component host
secretsSecrets Manager adapter through the task roleSecrets Manager adapter through the task role
realtime run projectionFargate-hosted run projection—
queueSQS standard queue; S3 event notifications to SQS for object arrivalSQS standard queue; S3 event notifications to SQS for object arrival
kind · profilesorchestrated · fulldaemon · full
wall-clock cap · excludednone · nothingnone · nothing

The scheduled-fargate runtime and process boundaries:

Flowchart diagram Flowchart, top to bottom. 7 nodes, 7 edges. MCP or HTTP client → Application Load Balancer [HTTPS] AWS target: EventBridge Scheduler → contextful cycle process [ECS RunTask] AWS target: Application Load Balancer → contextful serve process [HTTP store request] AWS target / Fargate Linux tasks: contextful cycle process → RDS Postgres [conditional write]; → S3 corpus bucket [publish parts] AWS target / Fargate Linux tasks: contextful serve process → S3 corpus bucket [pull snapshot]; → RDS Postgres [catalog read] AWS target: RDS Postgres AWS target: S3 corpus bucket AWS target Fargate Linux tasks contextful cycle process contextful serve process ECS RunTask HTTP store request conditional write publish parts pull snapshot catalog read EventBridge Scheduler Application Load Balancer RDS Postgres S3 corpus bucket HTTPS MCP or HTTP client

Cloudflare

Cloudflare deployments with a Linux Container, D1 execution metadata and R2 objects.

Cloudflare's default deployment configuration is worker-container-d1. The gateway and the reconciler run as TypeScript in Workers' V8 isolates. Cron Triggers schedule work; Workflows coordinates execution. A Durable Object controls the Linux Container's lifecycle. The Container runs the native contextful-full executable with embedded DuckDB and Wasmtime. Workers and Workflows call the Container through requests.

The Rust processes use CONTEXTFUL_STORAGE_PROFILE=cloudflare-d1. Requests to the private contextful.d1 host call a Worker binding handler. The handler reads and writes execution metadata in D1 and execution blobs in R2. A separate R2 bucket stores corpus parts and manifests through its S3-compatible API. Files inside the Container are a replaceable working copy.

Cloudflare Queues carries receive batches and object-arrival notifications. The Rust engine writes the store. A separate Durable Object manages hibernating WebSocket connections for run updates. Workers Secrets supplies credentials to configured bindings. The optional console server runs on a Node.js host. The React assets provide a static browser preview without that server.

The worker-container-d1-basin configuration adds an optional read face for exported audiences. The Container commits each audience's Iceberg export to Basin Catalog, the Iceberg REST catalog of an R2 bucket that holds that audience alone. The gateway sends a caller whose credential maps to an export namespace to Basin SQL over REST with that audience's read-only token, one Worker secret per audience bucket, and sends every other caller to the Container. Each export table runs with Basin compaction disabled; Basin snapshot expiration removes superseded exports.

The example under apps/cloudflare-execution/ exposes authenticated POST /conformance. Its Container runs contextful-conformance against the packaged Linux contextful binary. This endpoint tests execution storage; it serves no query or orchestration routes. status.md records clause implementation evidence.

Roleworker-container-d1 (default)worker-container-d1-basin
cron tickCron TriggerCron Trigger
reconcilerWorkerWorker
durable orchestratorWorkflowsWorkflows
single-writer catalogD1 through a private Worker outbound bindingD1 through a private Worker outbound binding
object storeR2R2, with one Basin Catalog bucket per exported audience
hot-path query facegateway → warm contextful-full Containergateway → warm contextful-full Container, or Basin SQL for an exported audience
heavy computeContainer with cloudflare-d1 storage profileContainer with cloudflare-d1 storage profile, which also runs Iceberg exports
secretsWorkers SecretsWorkers Secrets
realtime run projectionDurable Object with a hibernating WebSocketDurable Object with a hibernating WebSocket
queueCloudflare Queues with a pull consumer; R2 event notifications for object arrivalCloudflare Queues with a pull consumer; R2 event notifications for object arrival
kind · profilesorchestrated · fullorchestrated · full
wall-clock cap · excludednone · nothingnone · nothing

The worker-container-d1 runtime and process boundaries:

Flowchart diagram Flowchart, top to bottom. 14 nodes, 14 edges. MCP or HTTP client → gateway [HTTPS] Cloudflare target / Workers V8 isolates: gateway → Container Durable Object [store request] Cloudflare target / Workers V8 isolates: reconciler Worker → Workflows [due execution] Cloudflare target / Workers V8 isolates: execution binding handler → D1 execution database [SQL batch]; → R2 execution blobs [content-addressed objects] Cloudflare target / Workers V8 isolates: Container Durable Object → Rust engine [start and forward] Cloudflare target: Cron Trigger → reconciler Worker [scheduled event] Cloudflare target: Workflows → Container Durable Object [dispatch step] Cloudflare target / Linux Container running contextful-full: Rust engine → DuckDB library [SQL]; → Wasmtime component host [component invocation]; → ephemeral snapshot files [hydrate snapshot]; → execution binding handler [private contextful.d1 HTTP]; → R2 corpus bucket [S3 API sync] Cloudflare target / Linux Container running contextful-full: DuckDB library → ephemeral snapshot files [scan snapshot] Cloudflare target / Linux Container running contextful-full: Wasmtime component host Cloudflare target / Linux Container running contextful-full: ephemeral snapshot files Cloudflare target: D1 execution database Cloudflare target: R2 execution blobs Cloudflare target: R2 corpus bucket Cloudflare target Workers V8 isolates store request gateway reconciler Worker execution binding handler Container Durable Object Linux Container runningcontextful-full SQL component invocation hydrate snapshot scan snapshot Rust engine DuckDB library Wasmtime component host ephemeral snapshot files scheduled event due execution dispatch step start and forward private contextful.d1 HTTP SQL batch content-addressed objects S3 API sync Cron Trigger Workflows D1 execution database R2 execution blobs R2 corpus bucket HTTPS MCP or HTTP client

Linux (reference)

Self-hosted Linux servers, development, and air-gapped single-node deployments.

The contextful-full Linux executable runs natively on x86-64 or ARM64, using the corresponding x86_64-unknown-linux-musl or aarch64-unknown-linux-musl release artifact. A long-lived contextful pipeline serve process owns the scheduler and launches the same executable as contextful pipeline run or contextful job fire children. A separate contextful serve process answers MCP and HTTP requests. A service manager such as systemd supervises these processes with the same project working directory and configured environment.

Wasmtime executes WebAssembly component connectors inside a run child; its Tokio runtime is embedded there. DuckDB executes SQL inside the process serving reads. SQLite is an embedded library for execution state, with files such as machine.sqlite; Parquet parts, manifests and control snapshots live under the project's configured filesystem root. These libraries have no separate service process.

The optional operator console is a separate Node.js 22 process, node apps/console/dist/server.js --http <host:port>, and reaches the Rust HTTP server through its configured store endpoint. The browser executes the React client. The local target uses the filesystem receive spool; bucket sync adds an explicitly configured S3-compatible endpoint.

The repository's Dockerfile packages the Linux executable in a shell-free, non-root image with /data as its project volume. Separate scheduler and HTTP containers share that persistent volume. On macOS, a Darwin executable runs the same process layout directly; Docker runs the Linux image inside a Linux VM. The topology's macOS sections give both execution paths.

Roleprocess (default)
cron tickin-process scheduler
reconcilerin-process reconcile loop
durable orchestratorengine journal in the catalog
single-writer cataloglocal catalog file with embedded SQLite execution stores
object storelocal filesystem under ./.contextful/, optional S3-compatible sync
hot-path query facenative contextful serve process with embedded DuckDB
heavy computenative contextful pipeline run or job fire child; embedded Wasmtime for components
secretsenvironment and configured secret providers
realtime run projectionin-process WebSocket
queue—
kind · profilesdaemon · full
wall-clock cap · excludednone · nothing

The process runtime and process boundaries:

Flowchart diagram Flowchart, top to bottom. 10 nodes, 12 edges. browser or MCP client → console server [HTTP console]; → MCP and HTTP handlers [MCP Streamable HTTP] Linux host / optional Node.js 22 process: console server → MCP and HTTP handlers [HTTP store endpoint] Linux host / contextful serve process: MCP and HTTP handlers → DuckDB library [SQL] Linux host / contextful serve process: DuckDB library → Parquet and manifest files [read snapshot] Linux host / contextful pipeline serve process: Rust scheduler → native connector [spawn run or fire]; → SQLite execution files [lease and cadence] Linux host / contextful run or fire child: native connector → source endpoint [mediated HTTP]; → Parquet and manifest files [commit parts]; → SQLite execution files [journal and cursor] Linux host / contextful run or fire child: Wasmtime component host → source endpoint [mediated HTTP]; → Parquet and manifest files [commit parts] Linux host: SQLite execution files Linux host: Parquet and manifest files Linux host optional Node.js 22 process console server contextful serve process SQL MCP and HTTP handlers DuckDB library contextful pipeline serveprocess Rust scheduler contextful run or fire child native connector Wasmtime component host HTTP store endpoint read snapshot spawn run or fire commit parts commit parts lease and cadence journal and cursor SQLite execution files Parquet and manifest files HTTP console MCP Streamable HTTP mediated HTTP mediated HTTP browser or MCP client source endpoint

publish-hostname

Hostname access policies, deployment checks and requests through the routing service to the query container.

  • posture-mismatchtestAn unreachable hostname or response outside its declared access policy raises HostnamePostureMismatch, naming the hostname, policy and observed response. contextful-ci deploy probe exits nonzero.
    WhyA-topology#posture-probe
  • probe-tabletestBefore probing hostnames, the deploy tool compares descriptor and probe-table entries. An entry missing from either set raises ProbeTableDrift, naming the hostname and missing side.
    WhyA-topology#posture-probe
  • empty-probetestAn absent descriptor directory, absent probe table or empty hostname set raises ProbeSetEmpty, naming the path. A passing probe checks at least one hostname.
    Whybecause a probe that checks nothing and exits zero reads as a proven access policy to the pipeline that invoked it.
  • unknown-fieldtestAn unmodelled descriptor property raises DescriptorUnknownField, naming the key and contract version. The decoder rejects the descriptor.
    WhyP1
  • unconfigured-gatewaytestA gateway with no store configuration answers 503 on every route and raises GatewayUnconfigured.
    WhyP3
  • issuer-keytestThe HTTP server requires a verification key that resolves and parses. An unusable key raises IssuerKeyUnusable, naming the input, and prevents startup. The server generates no replacement key.
    WhyP3
  • container-readinesstestA retrieval container reaches readiness within 8 s of a cold start, before its snapshot-set hydration completes.

coordinate

Conditional metadata updates, writer leases and the databases that store coordination state.

unsettled: Does an encrypted store seal its rows in the Postgres catalog, or keep refusing that pairing at open? owner: topology affects: topology.coordinate

bound-application

Responsibilities of the engine and applications that use the engine.

Shapes

The workspace:

crates/
  contextful-core/         pure domain types and ports, no I/O
  contextful-fs/           exclusive-create publish the store and the journal share
  contextful-engine/       runner, journal, catalog, awakeables, cancellation
  contextful-outbound/     mediated client, credential resolution, quota metering, inference
  contextful-wasm/         sandboxed component host
  contextful-connectors/   native sources
  contextful-decode/       record decoders: JSON, JSON Lines, delimited text, workbook
  contextful-context/      table parts, query face, snapshot commit, retrieval
  contextful-memory/       deterministic memory synthesis
  contextful-sqlite/       the SQLite adapter behind the catalog and run store ports
  contextful-pg/           the Postgres adapter behind the catalog port
  contextful-d1/           the D1 adapter behind the execution store ports
  contextful-policy/       predicates, masks, redaction, zones, audit chain, token trait
  contextful-sync/         bucket push and pull
  contextful-agent/        tool server and connector scaffolder
  contextful-eval/         read-path quality harness
  contextful-snapshot/     control snapshot directory: versions, pointer, drafts, receipts
  contextful-control/      control plane; the one CRDT consumer
  contextful-cli/          the `contextful` binary; dependency injection per profile
  acceptance/              black-box suite driving the built binary
contextful.toml            the deployment declaration, identical across providers

The three profiles as feature bundles:

[features]
contextful-edge    = ["read-plane", "transport-ureq", "s3-sync"]
contextful-full    = ["data-plane", "transport-ureq", "s3-sync", "drive", "component-host", "pg-catalog", "d1-catalog", "aws-secrets-manager", "aws-sqs", "iceberg-export", "otlp"]
contextful-control = ["dep:contextful-control"]

A published-hostname descriptor:

{ "contract": 1, "worker": "contextful-docs", "hostname": "docs.example.org",
  "gate": { "_tag": "public", "acknowledged": true } }

A lease row and a fenced cursor commit, as the Catalog port issues them:

-- acquire: the catalog's clock decides expiry; the fence only grows
UPDATE leases SET holder = :me, expires_at = now() + :ttl, fence = fence + 1
 WHERE key = :key AND (holder IS NULL OR expires_at < now());

-- commit: one conditional update, predicated on version and on the holder's fence
UPDATE cursors SET position = :pos, version = version + 1
 WHERE stream = :stream AND version = :v
   AND EXISTS (SELECT 1 FROM leases WHERE key = :key AND fence = :fence);