# Obelisk -- Schema and API Reference
Advanced reference for the obelisk database.
Read this when `search()`, `context()`, or `sql()` are not enough.
Executable schema source: `scripts/schema.sql`. This document explains that
contract for agents and humans; it is not the runtime source of truth.
---
## 1. Database Schema
Database location: `~/.claude/obelisk.sqlite`
### sessions
One row per Claude Code session.
```sql
CREATE TABLE sessions (
id TEXT PRIMARY KEY, -- session UUID (matches JSONL filename)
title TEXT, -- AI-generated session title (may be NULL)
project TEXT, -- Claude project slug (e.g. "-Users-tomiya-Code-quiet-zero")
project_path TEXT, -- absolute session cwd-derived path, with slug fallback (e.g. "/Users/tomiya/Code/quiet-zero")
started_at TEXT, -- ISO 8601 timestamp of first message
ended_at TEXT, -- ISO 8601 timestamp of last message
git_branch TEXT, -- git branch active during session (if any)
version TEXT, -- Claude Code version string
message_count INTEGER DEFAULT 0, -- total user + assistant messages
jsonl_path TEXT -- absolute path to source JSONL file
);
```
### messages
Every user and assistant message. Core table for all queries.
```sql
CREATE TABLE messages (
uuid TEXT PRIMARY KEY, -- message UUID
session_id TEXT, -- FK -> sessions.id
type TEXT, -- "user" or "assistant"
parent_uuid TEXT, -- UUID of parent message (conversation tree)
timestamp TEXT, -- ISO 8601
role TEXT, -- "user" or "assistant" (from message payload)
text TEXT, -- extracted text content (thinking + text blocks, truncated to 10k chars)
content_type TEXT, -- "text", "thinking", "tool_use", "tool_result", or "unknown"
is_meta INTEGER DEFAULT 0, -- 1 for injected/control-plane transcript messages
model TEXT, -- model name (e.g. "claude-opus-4-6-20250529"), NULL for user messages
is_sidechain INTEGER DEFAULT 0, -- 1 if this message is on a sidechain (retry/branch)
agent_id TEXT, -- subagent or workflow agent UUID (NULL for main conversation)
input_tokens INTEGER, -- token usage (assistant messages only)
output_tokens INTEGER, -- token usage (assistant messages only)
cwd TEXT, -- working directory at message time (may differ from session project_path)
skill TEXT, -- skill that generated this response (e.g. "obelisk"), NULL if none
turn_duration_ms INTEGER -- wall-clock duration of the turn ending at this message (from system turn_duration event)
);
```
Indexes: `idx_messages_session(session_id)`, `idx_messages_agent(agent_id)`, `idx_messages_ts(session_id, timestamp)`.
`content_type` preserves the top-level Claude Code content block shape for the
message row. Treat `text` as user/assistant visible language, `thinking` as
trace/debug material, and `tool_use` as a marker that the assistant message
contains tool calls. `tool_result` marks a tool-result message, but the
structured payload remains in `tool_results`. Tool-call details remain in
`tool_calls`. Messages whose top-level content is not one of these four raw
message surfaces are `unknown`. Real user input is represented by `type='user'`
and `content_type='text'`, not by a separate `user_message` content type.
`is_meta` marks transcript control-plane content: injected caveats, command
envelopes such as `/exit`, and similar messages
that may appear as user-role text but are not ordinary user intent. It is
separate from `type`, `role`, and `content_type`. Default helpers hide meta
messages from ordinary recall; use `includeMeta: true` or explicit SQL when
investigating injected context, command messages, or transcript structure.
### messages_fts
FTS5 virtual table for full-text search over message text.
```sql
CREATE VIRTUAL TABLE messages_fts USING fts5(
uuid UNINDEXED, -- not searchable, carried for JOINs
session_id UNINDEXED, -- not searchable, carried for filtering
text, -- the searchable column
content=messages, -- content-sync with messages table
content_rowid=rowid
);
```
Queried via `MATCH` syntax. The table is kept in sync by the `messages_fts_*`
triggers above; rebuild it manually only when repairing FTS state.
### memories_fts
FTS5 virtual table for ranked memory recall over registered memory summaries
and paths.
```sql
CREATE VIRTUAL TABLE memories_fts USING fts5(
id UNINDEXED, -- memory record ID, carried for inspection
path, -- searchable memory file path
summary, -- searchable compact memory summary
content=memories,
content_rowid=rowid,
tokenize='unicode61 remove_diacritics 1'
);
```
`memories({ query })` queries this table with safe tokenization and joins back to
`memories`, omitting archived rows. It is rebuilt during index finalization;
`remember()` also inserts the new memory row into FTS immediately.
### tool_calls
Every tool invocation by the assistant. One row per `tool_use` content block.
```sql
CREATE TABLE tool_calls (
id TEXT PRIMARY KEY, -- tool_use ID (from API response)
message_uuid TEXT, -- FK -> messages.uuid (the assistant message containing this call)
session_id TEXT, -- FK -> sessions.id (denormalized for fast queries)
name TEXT, -- tool name: "Read", "Edit", "Write", "Bash", "WebSearch", etc.
input_json TEXT, -- JSON-serialized tool input (truncated to 10k chars)
file_path TEXT -- extracted file_path for Read/Edit/Write/NotebookEdit (NULL otherwise)
);
```
Indexes: `idx_tc_session_name(session_id, name)`, `idx_tc_file(file_path)`.
### tool_results
The result returned for each tool call. Appears in the next user message.
```sql
CREATE TABLE tool_results (
tool_use_id TEXT PRIMARY KEY, -- FK -> tool_calls.id
message_uuid TEXT, -- FK -> messages.uuid (the user message carrying this result)
session_id TEXT, -- FK -> sessions.id (denormalized)
content TEXT, -- result text (truncated to 10k chars)
file_path TEXT, -- file path from toolUseResult metadata (if any)
is_error INTEGER DEFAULT 0 -- 1 if the tool call returned an error (from API is_error field)
);
```
### subagents
Metadata for subagent spawns (non-workflow agents).
```sql
CREATE TABLE subagents (
agent_id TEXT PRIMARY KEY, -- subagent UUID
session_id TEXT, -- FK -> sessions.id (parent session)
parent_tool_use_id TEXT, -- tool_use ID that spawned this agent
agent_type TEXT, -- e.g. "code-review", "research"
description TEXT, -- task description given to the subagent
duration_ms INTEGER, -- wall-clock duration (computed from message timestamps)
total_tokens INTEGER -- sum of input_tokens + output_tokens across all agent messages
);
```
Index: `idx_sa_session(session_id)`.
### workflows
Workflow execution records. A workflow orchestrates multiple agents.
```sql
CREATE TABLE workflows (
run_id TEXT PRIMARY KEY, -- workflow run UUID
session_id TEXT, -- FK -> sessions.id (parent session)
task_id TEXT, -- task identifier (if any)
script TEXT, -- workflow script content (truncated)
result_json TEXT, -- JSON-serialized workflow result
timestamp TEXT, -- ISO 8601 execution time
agent_count INTEGER DEFAULT 0, -- number of agents in this workflow
duration_ms INTEGER, -- wall-clock duration of the workflow run
total_tokens INTEGER, -- total tokens across all agents
status TEXT, -- "completed", "failed", etc.
workflow_name TEXT -- name from the workflow script meta
);
```
Index: `idx_wf_session(session_id)`.
### workflow_agents
Individual agents within a workflow run.
```sql
CREATE TABLE workflow_agents (
agent_id TEXT PRIMARY KEY, -- agent UUID (prefixed with "agent-")
run_id TEXT, -- FK -> workflows.run_id
session_id TEXT, -- FK -> sessions.id
agent_type TEXT, -- agent type label
description TEXT, -- task description
phase TEXT, -- workflow phase title (e.g. "Review", "Verify")
label TEXT, -- agent label from workflow script
model TEXT, -- model used (e.g. "claude-opus-4-6[1m]")
state TEXT, -- "done", "error", etc.
duration_ms INTEGER, -- wall-clock duration of this agent
tokens INTEGER, -- total tokens used by this agent
tool_calls INTEGER -- number of tool calls made
);
```
Index: `idx_wa_run(run_id)`.
### index_state
Tracks incremental indexing progress per JSONL file.
```sql
CREATE TABLE index_state (
jsonl_path TEXT PRIMARY KEY, -- absolute path to JSONL file
mtime REAL, -- file mtime at last index (milliseconds)
lines_processed INTEGER -- number of lines already processed
);
```
Sentinel rows use synthetic `jsonl_path` keys:
`__last_build__` stores the last completed build time, `__app_heartbeat__`
stores the optional app indexer's liveness heartbeat,
`__app_last_successful_build__` stores the app's last successful index build,
`__indexer_owner_app__` marks app ownership, and `__last_source_mtime__` records
the newest indexed source mtime. Skill-side lazy builds skip work only while the
app heartbeat and app successful-build marker are both fresh.
### memories
Human-approved markdown memory records registered in Obelisk. The markdown
file at `path` is the durable memory content; `summary` is the compact retrieval
surface.
```sql
CREATE TABLE memories (
id TEXT PRIMARY KEY, -- memory record ID
session_id TEXT, -- FK -> sessions.id where the memory was drawn, if known
project TEXT, -- project slug used for scoped recall
message_start TEXT, -- first relevant message UUID, if known
message_end TEXT, -- last relevant message UUID, if known
path TEXT, -- normalized absolute markdown memory file path
anchors TEXT, -- optional JSON array of recall anchors
summary TEXT, -- retrieval summary of the memory
created_at TEXT, -- ISO 8601 registration time
deleted_at TEXT, -- ISO 8601 archive time, if forgotten
deleted_reason TEXT -- human/agent deletion reason, if forgotten
);
```
Indexes: `idx_memories_project(project)`,
`idx_memories_session(session_id)`, `idx_memories_created(created_at)`.
Active memory means `deleted_at IS NULL`. Recall helpers return active memories
only. Archived memories are management/audit data, not recall data. Query recall
uses `memories_fts` joined back to `memories`; when using raw SQL for memory
recall, include `deleted_at IS NULL`.
### Key Relationships
```
sessions.id <-- messages.session_id
sessions.id <-- tool_calls.session_id
sessions.id <-- tool_results.session_id
sessions.id <-- subagents.session_id
sessions.id <-- workflows.session_id
sessions.id <-- memories.session_id
messages.uuid <-- tool_calls.message_uuid
messages.uuid <-- tool_results.message_uuid
messages.uuid <-- memories.message_start / memories.message_end
messages.agent_id --> subagents.agent_id (for subagent messages)
messages.agent_id --> workflow_agents.agent_id (for workflow agent messages)
tool_calls.id <-- tool_results.tool_use_id
workflows.run_id <-- workflow_agents.run_id
```
---
## 2. Query API Reference
Read helpers are available as globals inside `--query` scripts. Memory mutation
helpers are available only inside `--attune` scripts. Scripts run in an async
IIFE with a 30-second timeout.
### Simple Layer
#### `search(text, opts?)`
Full-text search across all message text using FTS5.
| Param | Type | Description |
|-------|------|-------------|
| `text` | `string` | FTS5 query (terms, phrases, prefix) |
| `opts.limit` | `number` | Max results (default 20) |
| `opts.sessionId` | `string` | Restrict to one session |
| `opts.project` | `string` | SQL `LIKE` pattern over `sessions.project` |
| `opts.after` | `string` | ISO 8601 lower bound on timestamp |
| `opts.before` | `string` | ISO 8601 upper bound on timestamp |
| `opts.cwd` | `string` | Filter by working directory (supports LIKE) |
| `opts.includeMeta` | `boolean` | Include injected/control-plane messages (default `false`) |
**Scope note:** `sessions.project` is the stored Claude Code project slug,
`sessions.project_path` is the absolute session path derived from message `cwd`
when available, and `messages.cwd` is the working directory at message time.
Helper `project` filters are fuzzy `LIKE` filters over `sessions.project`. For
exact project membership, use `sql()` with `s.project = ?` or
`s.project_path = ?`.
**Returns:** `Array<{ message, session, rank, context }>` where `message`
includes `{ uuid, text, content_type, is_meta, role, timestamp, model, cwd }`
and `context` is the 6 nearest non-meta messages by timestamp in the same
session unless `includeMeta: true` is passed. It is temporal neighbor context,
not a parent chain. `rank` is the FTS5 relevance score used by `ORDER BY rank`;
lower values sort earlier, so treat the returned order as the relevance order
unless you are deliberately using FTS5 ranking details.
```js
const hits = search('MCTS exploration');
return hits.map(h => ({
title: h.session.title,
content_type: h.message.content_type,
is_meta: h.message.is_meta,
text: h.message.text?.slice(0, 200),
}));
```
#### `context(uuid)`
Full context around a single message: parent chain, session metadata, subagent/workflow info.
| Param | Type | Description |
|-------|------|-------------|
| `uuid` | `string` | Message UUID |
**Returns:** `{ message, parentChain, session, subagent, workflow }` or `null`.
```js
const c = context('abc-123-def');
return { chain_length: c.parentChain.length, session_title: c.session?.title };
```
#### `sql(query, ...params)`
Read-only SQL with parameterized bindings. Returns an array of row objects.
| Param | Type | Description |
|-------|------|-------------|
| `query` | `string` | SQL SELECT/WITH statement |
| `...params` | `any` | Bind parameters (positional `?`) |
**Returns:** `Array