Skip to content

HTTP API ​

Every HTTP route the engine mounts when pensieve is running. Auth is optional — when PENSIEVE_AUTH_TOKENS is empty the role column is informational and every route accepts any caller. When auth is on, the listed minimum role is required, presented as Authorization: Bearer <token>.

The HTTP server listens on PENSIEVE_HTTP_ADDR (default 0.0.0.0:8080). Arrow Flight runs on its own port (default 0.0.0.0:9090, configurable via PENSIEVE_GRPC_ADDR); OTLP gRPC, when enabled, runs on PENSIEVE_OTLP_ADDR (off by default; conventional port 4317).

Every JSON error response uses the same shape:

json
{ "error": { "code": "...", "message": "...", "request_id": "..." } }

X-Request-ID is set on the request if the client didn't send one, and propagated back on the response. Logs and error bodies share that id.

Health and metrics ​

Always unauthenticated.

MethodPathResponseNotes
GET/health200 application/json — {status, version}Liveness probe.
GET/metrics200 text/plain — Prometheus expositionHistogram buckets tuned for _duration_seconds metrics.

Query ​

The query surface accepts SQL, KQL, or a query in any frontend the content-type registers. Results stream back as NDJSON — one JSON object per row.

MethodPathMin roleContent-TypeEffect
POST/v1/queryReadapplication/sql (default), application/x-kqlRun a query against the database in X-Database (default default).
GET/v1/catalog/schemaRead—Tree of databases → tables → columns. Cached server-side (default 5 s).

Query request headers:

HeaderDefaultEffect
X-DatabasedefaultTarget database.
Content-Typeapplication/sqlapplication/x-kql switches to KQL parsing.
X-Pensieve-Max-Wall-Clock-Msserver-sidePer-query wall-clock budget; min 10 ms.
X-Pensieve-Max-Memory-Bytesserver-sideMemory pool ceiling for the DataFusion run; min 1 MiB.
X-Pensieve-Max-Object-Store-Bytesserver-sideBytes the scan may read from object store.

Query response headers:

HeaderMeaning
Content-Typeapplication/x-ndjson; charset=utf-8.
X-Pensieve-RowsTotal row count returned.
X-Request-IDEcho of the request id.

Status codes: 200 (rows in body), 400 (parse error / empty body), 404 (database empty or unknown), 413 (body > 16 MiB), 429 (budget exceeded — X-Pensieve-Budget-Limit header included), 500 (execution error).

Ingest ​

MethodPathMin roleContent-TypeEffect
POST/v1/ingestWriteapplication/x-ndjsonAppend rows to a table. NDJSON body, max 64 MiB.
POST/v1/admin/databases/{database}/tables/{table}Write—Idempotent table provisioning.
GET/v1/admin/databases/{database}/tables/{table}Write—Return columns + row count for the table.

Ingest request headers:

HeaderDefaultEffect
X-DatabasedefaultTarget database.
X-TablerequiredTarget table.
X-Idempotency-Key—Replay-safe key. A repeated request with the same key returns the original ack with replayed: true.
X-Auto-CreatetrueCreate the database + table on first write. Set false to require pre-provisioning.
X-Schema-EvolvetrueAdd new columns mid-batch. Set false to drop unknown fields silently.

Ingest response (200 application/json):

json
{
  "snapshot_id": "...",
  "extent_count": 1,
  "rows_ingested": 123,
  "bytes_written": 4567,
  "replayed": false
}

Status codes: 200 (committed), 400 (missing X-Table, malformed NDJSON), 404 (table not found with X-Auto-Create: false), 413 (body > 64 MiB), 500 (commit failed).

Agent ​

MethodPathMin roleContent-TypeEffect
POST/v1/agent/askReadapplication/jsonRun one agent turn. Streams Server-Sent Events.
GET/v1/agent/runs/{run_id}Read—Look up a persisted run by uuid.

POST /v1/agent/ask body:

json
{
  "question": "...",
  "database": "default",
  "include_thinking": false,
  "session_id": "<uuid>",
  "source": "pensieve"
}

Pass session_id (a uuid returned in a prior turn's data-session SSE part) to continue a conversation. source is a free-form label stored on the session row ("claude_code", "pensieve", etc.).

The SSE response carries seven event names: run_started, thinking_delta (only when include_thinking: true), answer_delta, tool_call, tool_result, answer_final, run_error, run_finished. run_finished is always the last frame. See Agent endpoint for the full event payload reference.

Run wall-clock cap: 60 s. Per-run tool-call cap: 12. Both surface as run_error codes (timeout and tool_loop) before the stream ends.

Agent sessions ​

Multi-turn conversation history (A5). Each POST /v1/agent/ask call that includes a session_id appends to an existing session; omitting it mints a new session uuid, returned in the SSE event session as {"sessionId": "<uuid>"} in the JSON payload.

MethodPathMin roleEffect
GET/v1/agent/sessionsReadList sessions, newest first (max 200). Returns {sessions:[…]}.
GET/v1/agent/sessions/{session_id}ReadFetch session metadata (title, rolling_summary, source, turn count).
GET/v1/agent/sessions/{session_id}/turnsReadFull turn log ordered by turn_index. Returns {turns:[…]}.
DELETE/v1/agent/sessions/{session_id}ReadDelete session + cascade turns; detaches linked runs.

Session list item shape: {session_id, title, created_at, last_active, source, turn_count}.

Turn item shape: {turn_index, role, content, run_id, created_at}.

{session_id} must be a uuid; non-uuid values return 400. Missing sessions return 404.

Engine config ​

One global engine preference row drives all agent runs. See Agent engines for provider setup and model selection.

MethodPathMin roleContent-TypeEffect
GET/v1/agent/enginesRead—Available providers + their model menus, plus the active config.
GET/v1/agent/engineRead—The persisted EngineConfig (one row per installation).
PUT/v1/agent/engineReadapplication/jsonPersist a new EngineConfig. Echoes the saved value.
POST/v1/agent/engine/testReadapplication/jsonProbe a candidate config without persisting. Returns {ok, kind, model} or 502/504.

EngineConfig JSON shape:

json
{
  "kind": "anthropic",
  "model": "claude-opus-4-7",
  "credential_id": "<uuid or null>",
  "host": "<url or null>",
  "extras": {}
}

kind ∈ anthropic, openai, ollama, claude_cli. host is only relevant for ollama (the Ollama base URL). credential_id references an encrypted credential from /v1/credentials. The test endpoint fires a single-token probe (30 s timeout); the real engine+credentials are used for Anthropic/OpenAI/Ollama; the claude CLI binary is probed directly for the claude_cli kind.

Agent skills ​

MethodPathMin roleEffect
GET/v1/agent/skillsReadList all discovered skills on the host with metadata and enabled state.
GET/v1/agent/skills/enabledReadThe toggled set — just the names.
PUT/v1/agent/skills/enabledReadReplace the toggled set. Body: {skills: ["name", …]}.

Memory ​

The memory surface stores and retrieves durable agent memories. See Agent memory for knob meanings and the memory model.

MethodPathMin roleEffect
POST/v1/agent/memory/queryReadHybrid recall — semantic + keyword + graph expansion.
GET/v1/agent/memory/overviewReadAggregate stats: memory count, firehose rows, recent pipeline runs.
GET/v1/agent/memory/settingsReadCurrent tunable settings (consolidation knobs, dreaming schedule).
PUT/v1/agent/memory/settingsReadPersist tunable settings.
GET/v1/agent/memory/exportReadFull snapshot ({memory_nodes, memory_edges}) for backup or portability.
POST/v1/agent/memory/importWriteBulk-apply node/edge rows from another instance's export/changes response.
GET/v1/agent/memory/changesReadIncremental sync: nodes/edges updated strictly after ?since=<RFC3339>.

POST /v1/agent/memory/query body:

json
{
  "query": "payments latency investigation",
  "realms": [],
  "memory_type": null,
  "tags": [],
  "importance_min": null,
  "as_of": null,
  "include_invalidated": false,
  "limit": null,
  "expand_hops": null,
  "mode": "fast"
}

mode is "fast" (default, LLM-free graph-aware hybrid retrieval) or "agentic" (adds a short synthesized brief from the retrieved context). All fields except query are optional.

GET /v1/agent/memory/changes query params: since=<RFC3339> (omit for full epoch), realm=<name>. The response includes a until watermark to advance on the next poll.

POST /v1/agent/memory/import requires Role::Write even though the agent router is otherwise mounted at Role::Read. Body: {memory_nodes:[…], memory_edges:[…]}.

Dreaming ​

On-demand and scheduled agentic memory consolidation runs. See Dreaming for the full pipeline description.

MethodPathMin roleEffect
POST/v1/agent/memory/dreaming/runReadEnqueue a manual dreaming run. 202 when queued; 200 {deduped:true} when one is already in flight.
GET/v1/agent/memory/dreaming/runsReadList runs, newest first. Filters: kind=, limit= (max 200), offset=.
GET/v1/agent/memory/dreaming/runs/{id}ReadFetch one run including live progress JSONB for in-flight runs.

POST /v1/agent/memory/dreaming/run optional body:

json
{ "mode": null, "focus": null }

mode and focus are optional strings passed to the dreaming engine. Response on success: 202 {job_id: "<uuid>"}. Response when already in flight: 200 {job_id: null, deduped: true, detail: "…"}.

Run item shape includes: id, kind, status, mode, trigger, engine, model, started_at, finished_at, events_scanned, memories_written, error, job_id, session_id, agent_run_id, worker_id, stats, progress.

Dashboards ​

MethodPathMin roleEffect
GET/v1/dashboardsReadList all dashboards.
POST/v1/dashboardsWriteCreate a dashboard. Body: {name, description?, panels?}.
GET/v1/dashboards/{id}ReadFetch a dashboard with all panels.
PATCH/v1/dashboards/{id}WriteUpdate name / description, or atomically replace the panel set.
DELETE/v1/dashboards/{id}WriteDelete the dashboard and its panels.

{id} is a uuid. 404 for missing ids.

Cleanup (hard-delete GC) ​

MethodPathMin roleEffect
POST/v1/database/{db}/table/{table}/cleanupWriteHard-delete soft-deleted extents whose deleted_at is strictly before ?before=.

Required query param: before=<RFC3339> (e.g. 2025-01-01T00:00:00Z). Response body is the aggregate count: {extents_deleted, rows_freed, bytes_freed}.

Data sources ​

The data source admin surface manages periodic / scheduled data sources. The catalog (GET /v1/data-sources/catalog) is what drives the UI's source picker.

MethodPathMin roleEffect
GET/v1/data-sources/catalogWriteThe vendor catalog (available + coming-soon) that powers the add-data-source UI.
POST/v1/data-sourcesWriteCreate a data source. Body: {name, type, target_database, target_table, schedule_ms, config}.
GET/v1/data-sourcesWriteList data sources (id, name, type, enabled).
GET/v1/data-sources/{id}WriteRead a data source. Secret-shaped fields in config are scrubbed to ***.
PATCH/v1/data-sources/{id}WriteUpdate name / schedule / enabled / config.
DELETE/v1/data-sources/{id}WriteDelete the data source.
POST/v1/data-sources/{id}/pauseWriteDisable the data source (disabled_reason="manual").
POST/v1/data-sources/{id}/resumeWriteRe-enable.
POST/v1/data-sources/{id}/triggerWriteEnqueue an immediate tick. Returns 202 Accepted.

schedule_ms must be in the range [100, 86400000] (100 ms → 24 h). See Data sources for the typed config shape per data source type.

Credentials ​

Typed, encrypted secrets referenced by data sources (and other subsystems) via a credential_id in their config. List/get return a masked preview, never plaintext. Requires PENSIEVE_SECRET_KEY. See OAuth data sources.

MethodPathMin roleEffect
POST/v1/credentialsWriteCreate a credential. Body: {label, value:{kind, …}, metadata?}. Returns a summary.
GET/v1/credentialsWriteList credentials (label, kind, masked preview, metadata).
GET/v1/credentials/{id}WriteFetch one credential summary (no plaintext).
DELETE/v1/credentials/{id}WriteDelete a credential.

value.kind ∈ pat, basic, oauth2, url, aws_creds, api_key, github_app.

OAuth ​

The browser flow that mints oauth2 credentials for OAuth data sources. The callback is unauthenticated (a cross-site IdP redirect carries no bearer); the single-use state token is the trust anchor. See OAuth data sources.

MethodPathMin roleEffect
POST/v1/oauth/{provider}/startWriteBegin a flow. Body: {data_source_type, scopes?, label?, client_id?, client_secret?}. Returns {authorize_url, state}.
GET/v1/oauth/{provider}/callbacknoneIdP redirect target. Exchanges the code, mints the credential, posts the result back to the opener.
GET/v1/oauth/flows/{state}WritePoll a flow's {status, credential_id} — the fallback when postMessage can't reach the UI.

{provider} ∈ google, notion, atlassian, slack.

Worker fabric ​

The distributed compute fabric that runs data source syncs, dreaming jobs, and arbitrary scheduled work. See Workers for daemon setup and job lifecycle details.

Two auth surfaces — worker-facing endpoints authenticate with a worker token (Authorization: Bearer kyw_…); operator endpoints use the regular bearer token.

Worker-facing (Authorization: Bearer kyw_…) ​

MethodPathEffect
POST/v1/workers/registerRegister or re-register this worker. Returns {worker_id, heartbeat_interval_secs:30, lease_secs}.
POST/v1/workers/heartbeatTouch liveness. Returns {status, drain, recommended_poll_ms:1000}.
POST/v1/jobs/claimClaim up to max jobs matching kinds. Long-polls up to wait_ms (capped at 25 000 ms).
POST/v1/jobs/selfEnqueue a job pinned to this worker (e.g. a local-file source sync).
POST/v1/jobs/{id}/progressSnapshot partial progress JSON for a held job.
POST/v1/jobs/{id}/completeMark a job done and supply a result payload.
POST/v1/jobs/{id}/failMark a job failed with an error string.
POST/v1/jobs/{id}/leaseExtend the job lease (10–3 600 s). Call before the lease expires to avoid a reclaim.

Claim / lease lifecycle: POST /v1/jobs/claim atomically assigns jobs and starts their lease (PENSIEVE_FABRIC_LEASE_SECS, default 300 s). The worker must call /v1/jobs/{id}/lease before the lease expires to retain ownership; otherwise the job is re-queued. /progress accepts any JSON snapshot and stores it on the job row for operator visibility. complete and fail both return 204; 409 means the lease was lost.

POST /v1/jobs/claim body:

json
{ "kinds": ["data_source_sync"], "max": 1, "wait_ms": 5000 }

Operator (regular bearer, Role::Write) ​

MethodPathMin roleEffect
POST/v1/workersWriteCreate a worker identity and mint its token. Body: {name, capabilities?, labels?}. Returns {worker_id, token} — token shown once.
GET/v1/workersWriteList all registered workers.
DELETE/v1/workers/{id}WriteRevoke a worker (soft-delete; outstanding jobs are re-queued on next sweep).
POST/v1/jobsWriteEnqueue a job manually. Body is an EnqueueJob struct.
GET/v1/jobsWriteList jobs. Query params: kind=, status=, limit= (max 500, default 50).
GET/v1/jobs/{id}WriteFetch a single job with payload and progress.

Graph ​

Read-only property-graph surface. The built-in schema graph renders the catalog (databases → tables → inferred column references) as a property graph. Additional stored graphs are registered via the catalog and addressed by name with an X-Database header. For graph KQL queries, see the Graph guide.

All graph routes accept an optional X-Database header that selects the database context for stored-graph resolution and per-database token scope enforcement.

MethodPathMin roleEffect
GET/v1/graphReadList available graphs (name, kind, description). Include X-Database to show stored graphs in that database.
GET/v1/graph/{graph}/overviewReadNodes + edges for the graph. Query params: realm=, limit= (default 800).
GET/v1/graph/{graph}/statsReadNode/edge counts. Query param: realm=.
GET/v1/graph/{graph}/schemaReadLabel vocabulary and edge-type vocabulary.
GET/v1/graph/{graph}/nodes/{id}ReadFetch one node by id. 404 when absent.
GET/v1/graph/{graph}/nodes/{id}/subgraphReadEgo subgraph around a node. Query param: depth= (default 2).
POST/v1/graph/{graph}/neighborsReadExpand one or more nodes one hop.
POST/v1/graph/{graph}/searchReadFull-text + label search over the graph.

{graph} is "schema" for the built-in schema graph, or the registered name of a stored graph.

POST /v1/graph/{graph}/neighbors body:

json
{
  "node_ids": ["default::orders"],
  "direction": "both",
  "only_internal": false,
  "limit": 200
}

direction ∈ "forward", "backward", "both" (default).

POST /v1/graph/{graph}/search body:

json
{
  "text": "payments",
  "labels": [],
  "realm": null,
  "limit": 20,
  "offset": 0
}

MCP over HTTP ​

The Model Context Protocol surface exposes pensieve's query and memory tools as JSON-RPC 2.0 methods so any MCP-capable agent can interact with the engine.

MethodPathMin roleContent-TypeEffect
POST/mcp/v1Readapplication/jsonJSON-RPC 2.0 channel (Streamable HTTP MCP).
GET/mcp/v1Read—SSE upgrade for streaming MCP sessions.

Auth is the same bearer token as /v1/* (Role::Read). Scoped tokens (per-database) are blocked at the middleware level because MCP tools address databases internally.

For a stdio alternative, pensieve mcp (the CLI subcommand) exposes the same tools without an HTTP server. See the MCP reference for tool names, input schemas, and capability negotiation.

Arrow Flight (gRPC) ​

Separate port — default :9090, configurable via PENSIEVE_GRPC_ADDR (set to off to disable). Auth on the gRPC surface is not yet enforced; treat it as deployment-firewalled.

The Flight do_get ticket is a JSON envelope:

json
{ "database": "default", "query": "SELECT ...", "language": "sql" }

language is "sql" (default) or "kql". The server returns a streaming sequence of FlightData messages — Arrow IPC framing, zero-copy from pensieve's columnar buffers to the wire. See Arrow Flight for client examples.

handshake, get_flight_info, get_schema, do_put, do_exchange, do_action, list_actions, list_flights return Unimplemented for now.

When the engine is built with the web-ui feature, Flight is also nested under the HTTP router at /flight/* as gRPC-web (auth on, Role::Read). Browsers reach the same surface without a separate gRPC client.

OTLP gRPC (logs) ​

Separate gRPC server on PENSIEVE_OTLP_ADDR (default off; conventional port 4317). Phase A is logs-only; received batches land in PENSIEVE_OTLP_DATABASE (default default) in a fixed otel_logs table. See OTLP gRPC.

An open-source project · MIT licensed.