orchestratr
reference

store schema

the SQLite schema owned by the server: its six tables and columns, key indexes, what lives as files instead of blobs, and the fields that are derived and never stored.

the store is SQLite (WAL mode) under ~/.orcr/, owned exclusively by the server (the single writer). the schema is deliberately minimal: nothing derivable is stored, and payloads live as files in the data dirs rather than as blobs. SQLite coordinates; files carry content. this page is advanced and contributor-facing; day-to-day use never touches the store directly.

this reflects src/store/schema.rs. all writes go through BEGIN IMMEDIATE transactions, and every event is written in the same transaction as the change it describes.

payloads live as files

three payloads are files in the data dirs, not columns:

filecontents
<agent data dir>/launch.jsonthe full launch payload (provider, resolved argv, prompt, model/effort, cwd, gc/timeout, the effective path and how it was derived, the injected env, never the caller's environment); versioned; written before any herdr call; for audit and recovery, not auto-relaunch
<loop data dir>/loop.jsonthe loop definition payload (argv, cadence, tz, cwd)
<run folder>/run.logthe run command's stdout/stderr as JSONL ({ts, stream, text}); orcr's own scheduler actions live in the events table, and loop logs interleaves the two

orcr writes no prompt or response files. responses are read from the provider's native transcript via agent logs; anything else an agent writes to its data dir is the agent's own doing (the file convention).

tables

agents

the primary table. uuid is a UUIDv7 primary key: the permanent identity; events, turns, and attaches reference it.

uuid PK, path,                                 -- path absolute; last segment = name
  UNIQUE (path) WHERE status NOT IN ('ended'), -- reservation: active agents only
managed (0|1), origin (run|detected),
parent_id,                                     -- uuid of the spawning context
agent (provider), model, effort, gc_mode, cwd,
herdr_session, terminal_id, pane_id,           -- current location, not identity
launch_token,                                  -- crash-recovery idempotency marker
agent_session_kind, agent_session_value,       -- transcript identity gate
status,   -- managed: queued|starting|working|idle|blocked|parked|ended|lost
          -- unmanaged: working|idle|blocked|unknown|ended
move_state (none|parking|unparking), move_token,   -- exclusive move lease
blocked_kind (question|limit|login|unknown),
input_seq, cancel_requested (0|1),
exit_reason (completed|killed|canceled|reaped|timeout|failed|lost),
transcript_locator, transcript_cursor,
queue_seq, enqueued_at, starting_at, deadline_at,  -- deadline only if --timeout
idle_since, parked_at, last_status_change_at, created_at, ended_at, updated_at

turns

one row per input/turn, keyed by (agent_uuid, input_seq). this is the completion bookkeeping (did this input's turn complete?), and it survives server restarts so an old idle can never satisfy a newer send.

agent_uuid, input_seq (PK pair),
source (orcr|external),                        -- external = typed via attach/herdr UI
delivered_at, working_seen_at, completed_at, blocked_kind, transcript_cursor

attaches

attach leases, so the GC interlock survives restarts.

agent_uuid, lease_id PK, mode (observe|takeover), connection, client_pid,
started_at, heartbeat_at, expires_at

loops

loop definitions. uuid is permanent; runs and events reference it. the active-name uniqueness is a partial index over active and paused rows.

uuid PK, name,   -- UNIQUE INDEX loops_active_name ON loops(name)
                 --   WHERE status IN ('active','paused')
cadence_kind (cron|once), cadence_value, tz, cwd,
max_concurrency, overlap, timeout_s (nullable),
status (active|paused|ended), next_fire_at, last_fire_at,
updated_at, created_at, ended_reason (removed|removed_by_run|fired)

loop_runs

loop run rows. pending runs replace the old single pending-fire marker: at most one pending scheduled run per loop (coalesced), while manual runs always allocate their own. pgid_start_time is why kills and recovery only ever signal a pgid whose start time matches; pids get reused.

uuid PK, loop_uuid, run_id (r+5 [a-z0-9]; UNIQUE per loop),
kind (scheduled|manual), due_at, created_at, timeout_at (nullable),
status (pending|running|stopping|ok|failed|timeout|stopped|canceled),
pid, pgid, pgid_start_time,                     -- signal only on start-time match
exit_code, signal, started_at, ended_at, updated_at

events

the durable event log and the subscription cursor. written in the same transaction as the change; also the source for loop logs --source orcr.

seq PK AUTOINCREMENT, ts, kind, ref_uuid, payload_json

key indexes

indexpurpose
partial unique (path) WHERE status NOT IN ('ended')active-path reservation
(status, queue_seq)FIFO queue promotion
(agent, status)per-provider capacity counting
(path), (parent_id), (pane_id)resolution, lineage, location lookup
(herdr_session, terminal_id)unmanaged-agent identity key
(agent_session_kind, agent_session_value)transcript identity
loops (status, next_fire_at)scheduler due lookup
loop_runs (loop_uuid, status)run promotion and listing
events (ref_uuid, seq)index-scoped event fetch for loop logs

glob patterns compile to anchored, segment-aware matches (see the path grammar), never a naive SQL LIKE, because _ is a LIKE wildcard and a legal name character. uuid prefixes resolve against the primary key.

derived, never stored

one definition each, so the CLI, TUI, and SDK cannot drift:

fielddefinition
namethe path's last segment
home workspacethe path's first segment when it has 2 or more segments, else default
queue_positionrank by queue_seq among status queued (recomputed per read)
age basiscreated_at for queued/starting, last_status_change_at otherwise
a run's agents countactive agents matching <loop>/<run_id>/**
data dirs$ORCR_HOME/data/<path segments>/<uuid> for agents, $ORCR_HOME/data/<loop_name> for loops, .../<run_id> for runs

the data tree is a convention, never an identity authority: rows and uuids are. future data-dir GC must be row-aware, because a run's folder contains its descendants' folders and nothing may delete a shared ancestor while a child row still has data below it. see data and file conventions and durability and recovery.

On this page