openclaw
Identity
OpenClaw is a personal assistant that talks to people on messaging apps, runs scheduled and event-driven work, and keeps memory in files. It can run a conversation through another harness (the Codex app-server, the Claude Code CLI).-
Native format:
openclaw-agent-sqlite. One database per configured agent (~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite, or PostgreSQL) with sessions, windows, append-only transcript events, conversations, deliveries and participants; a shared state database (state/openclaw.sqlite) with approvals, audit events, cron jobs, tasks, the delivery queue and skills. Legacy JSONL transcripts:openclaw-session-jsonl. -
Session ID: a window’s
session_id(a UUID). No namespace. The logical conversation’ssession_key(for exampleagent:main:whatsapp:group:<id>) → headerconversationKey.agentId→ header_metaai.openclaw/agentId. -
Entity keys:
messageIdis the transcript entryid. A rewritten entry (one withsupersedesEntryId) keeps theidat the root of itssupersedesEntryIdchain as itsmessageId, withsupersedesnaming the record it replaces andassembly {mode: rewrite, sourceIds: [<new id>]}(ASR-352, ASR-522).callIdistoolCall.id; older transcripts that repeat IDs leave results unpaired withlinkage: ambiguousand nocallId, keeping the native ID intool_result.nativeCallId(ASR-262).inferenceIdis the assistant row’sresponseId, else itsturnId, else derived from the entry ID: OpenClaw’sturnIdnames one model response, not a turn.turnIdis__openclaw.runId: OpenClaw’s run is the ASR turn. -
Runs by other harnesses:
agentHarnessIdnames the runtime that ran the window. For the Codex app-server runtime, OpenClaw documents its transcript as a mirror of the Codex thread (“Primary thread state: Codex thread, plus OpenClaw transcript mirror”):authority: mirrorandexecutedBy [{session: "codex:<thread id>", evidence: native_field}]. Claude Code CLI bindings (cliSessionBindings,claudeCliSessionId) and Muse bindings →executedBywithoutmirror, because OpenClaw records its own events for them. -
Assistant rows that are not model output: rows with
provider: "openclaw"(delivery-mirror,gateway-injected,automation-result) get noinference. A delivery mirror copies a reply of the same stream:copiedFrom {record: "messageId=<sourceMessageId>", proof: native_field}andauthor: harness. A cron or subagent result written into the conversation →author: harness(cron) oragent(subagent),origin: injectedorsubagent_report. -
Who wrote a message:
provenance.kind:external_user→origin: prompt, withauthor: humanonly when the sender’s identity says it is a person (senderKind: human, or a Gateway profile);inter_session→author: agent,origin: inter_session,from: {session: <originSessionId>};internal_system→author: harness,origin: injected.custom_messageentries →author: harness; their role is decided per provider at request time and is not stored:roleabsent withcapture.role: absent.operatorMessagerows likewise have no stored role.__openclaw.modelPromptProjection→content, with the stored text indisplayContent(displayCorrespondence: native). Talk rows (provenance.kind: realtime_voice) and media withtranscribed: true→ text parts withderivation {method: transcription}. -
Triggers:
provenance.sourceTool→trigger.native. Mapping:cronorcron_execution→schedule(idfromjobId,runIdfromrunId). Useheartbeatonly when evidence from the time the job ran, or a saved receipt from the job’s setup, establishes the job’s kind for that run. A current job name or uncertain conversion time does not establish the job’s kind for that run. Without that evidence, keepscheduleand native provenance. Gmail and webhook hooks (hook_external_content_source) →eventorwebhook;main_session_restart_recovery→recovery;execandagent_harness_completion→task_completion. A session created for a cron run → headertrigger. -
Senders and conversations:
__openclaw.senderId,senderName,senderUsernameandsenderIdentity→ a participant (displayName;ids [{scheme: <channel>, value: <id>, basis: <senderIdentity.type>}];kindfromsenderKind) andmessage.from {participant};senderIsOwner→role: owner.conversationsrows → headerconversations(channel,kind,nativeId);transport.messageIdandreplyToId→platform; whether the agent was mentioned →addressed;transport.clients→via(Control UI →web, TUI →tui, CLI →cli, mobile →mobile, channels →channel). -
Approvals:
operator_approvals→permissionwithrequestIdfrom the approval ID andsubject.callIdfromsource_tool_call_id.status:pending→pending,allowedanddenied→resolved,expired→expired,cancelled→resolvedwithdecision: cancelled; aterminal_reasonother thanuserortimeout→ no normalized status unless this registry defines its meaning; preserve the literal inai.openclaw/terminalReason.decision:allow-once→allow_once,allow-always→allow_always,deny→reject_once.resolver_kindandresolver_id→decider(device→{device: resolver_id},channel→{address: resolver_id}); no resolver ID means no actor reference.approvalReviews[]→reviews(by: {address: <label>},verdictfromstatus,rationale); when the automatic reviewer itself resolved the request without a person,decidedBy: classifier. -
Questions: Gateway question records are kept in memory only. The
ask_usercall and its result are stored →input_requestwithmode: questions,callId, questions and options from the input,statusfrom the result (answered, orno_answer→timed_out) andanswers. -
Tools: MCP identity is on the result:
toolResult.details.mcpServerandmcpTool→tool_result.provider {kind: mcp, server, tool, identity: exact}(ASR-276); the call’s composite name stays as recorded. Code Mode nested calls (openclaw.nested-tool.v1) →tool_callwithrequestedBy: codeandparentCallIdfromparentToolCallId, and itstool_result. A user-run shell command (bashExecution) →tool_callwithrequestedBy: user. A background command (status: runningwith a process ID) and theprocesstool →terminalwithkind: shell. -
Memory: a file-tool write to the agent workspace’s
MEMORY.mdormemory/YYYY-MM-DD*.md(scope: agent),USER.md(scope: user) orDREAMS.md→memory_change {store: "workspace", op: add | update | delete, target.path, cause.callId}. Dreaming →memory_changewithop: consolidateandcause: {actor: harness}, and asubagentwithcapture.childSession: absent.openclaw memory forgetandmemory_session_tombstones→memory_changewithop: forgetandtarget.keythe forgotten session ID;statusstays absent until the source records the deletion. Loaded workspace files (systemPromptReport) →instructionswithkind: memory,userorproject, adigest, andtruncatedwhen the file was cut before injection. -
Notes:
compaction {summary, firstKeptEntryId, tokensBefore, tokensAfter}→compactionwithreplaces.throughId(the compaction’s native predecessor, fromparentId),keptFromIdandinsertAt.beforeId(firstKeptEntryId), andtokens.branch_summary→kind: branch_summary. A Codex-runtime compaction without a summary →kind: provider_nativewithcapture.summary: absent. Usage:inputexcludes cached tokens →input.uncached;cacheWrite1handcacheWrite5m→cacheWriteByTtl1hand5m;cost.total→cost.amountin USD withsource: provider_reportedwhentotalOriginisprovider-billed, otherwiseharness_estimated; a call whoseusageReport.stateisunavailablehas no usage (not zeros);responseModel(ormodelwhen absent) →model.id,model→model.requested.model_changeandthinking_level_change→config_change. Session settings (permissionMode,sandboxMode, queue mode) are current values only: write oneconfig_changeat the end of the capture, withsnapshot: trueandassembly: {mode: snapshot}, never backdated (ASR-034, ASR-187).SessionEntry.goal→planwithplanType: goal(assembly: snapshot); theprogress_cardtool →planwithplanType: itemsand markdowncontent.skill_workshop_changes→config_change.capability {kind: skill, name, op}(create→install,remove_fileandarchive→remove, other actions retained atai.openclaw/skillActionwith normalizedopabsent). OpenClaw records hook effects but not hook runs: nohookrecords. Provider compaction checkpoints are compactions, notcheckpointrecords; cloud workspace snapshots arecheckpointwithkind: snapshot.
OpenClaw SQLite converter supplement
-
Identity: one stream per
session_windows.session_id;session_keyremainsconversationKey. Legacy JSONL uses headersession.id. No namespace. Duplicate native entry identities are errors, not permission to merge them. -
Copies: require the recorded ancestor window and
transcript_event_identitiesmembership under the pinned window-copy contract. Matching IDs outside that contract do not prove copies. Only a contiguous prefix consisting entirely of copied records can supplyinherits.localThroughId; wrapper-owned audit facts cannot be included in that prefix. Copied run IDs never establish which run or window owns facts in a side store. -
Limits: retain unknown side rows and compressed trajectory evidence without claiming their normalized semantics. Cold archives need
not_retainedgaps when history was lost. Unavailable context, terminal lifecycle, memory origins/tombstones, lane resolution, standing grants, boards and progress history remain declared limits where the converter cannot map them. A converter’s partial coverage does not remove the broader destinations above.
Position basis
- Capture order: transcript sequence, audit sequence, skill
cursor_id, trajectory sequence, then mutable state. Independent database transactions are not one atomic capture. When state that spans several stores has changed, use a new capture (ASR-033/034). Stable combinations of table, primary key and projection identify records; positions based only on rowid are scoped to the capture.
Use only positions established by the stated format and mapping evidence. Current-state rows without transcript positions form the snapshot tail. A protocol commit boundary takes precedence over physical EOF; uncommitted bytes are not a gap.
Destinations
OpenClaw: native facts without a standard field
ai.openclaw/reaction records a reaction that the source observed: a reaction state in the Control UI, or a channel’s reaction notice. The agent’s own react action is a tool call; it gets a reaction record only when the source also records the reaction itself.
OpenClaw supplement: native facts without a standard field
These rows add destinations. Existingai.openclaw/node and ai.openclaw/goal keep their grouped shapes. Unassigned rows use the common array shape, even for one fact. Do not put a whole goal or window into those narrower grouped objects.
Links
-
Windows and lineage: OpenClaw moves a conversation to a new window for many reasons; each window is a session.
session_windows.previous_session_idandreason→lineage.continues {session, reason}with the same reason names (reset,rollover,rewind,switch,recovery,compaction, anddailyoridlewhen the window reason records them). A window with reasonfork→forkedFrom, fromSessionEntry.forkSource {sessionKey, sessionId, entryId}withrecord: "messageId=<entryId>". A new window begins with entries copied from the old one with the same IDs:inherits {mode: copied, from, localThroughId, evidence: native_structure}under the source’s copied-prefix contract. Individual copies can useproof: prefix_contract; that is not a lineage evidence value. Aresetentry inside one window (reason: new | reset | idle | daily | cron-stale) →compactionwithkind: resetandreason. Subagents:spawnedByfromSessionEntry.spawnedByandsubagent_runs.rootmay be claimed for a window with reasoninitialand no parent, fork or spawn field. -
Tree: native parent
parentId; response keyresponseId, elseturnId; active leaf: the lastleaf.targetId, falling back tosession_transcript_index_state.leaf_event_id. Injected:custom_messageentries, and user rows withsynthetic,runtimeContext, or aprovenance.kindother thanexternal_user. Aleafentry for a rewind →rewind {reason: user_rewind}; for a branch switch →rewind {reason: other}.
OpenClaw link contracts
These contracts apply to2038c84, package 2026.10.7, agent schema 37 and its shared state schema 34.
Usage
Select one report per native accounting identity only when this entry establishes that identity. Keep unjoinable or overlapping reports as native evidence. Declare incomplete semantic coverage. Schema defaults and placeholder zeros are not reported usage. Rules for what a backend includes in its reported usage require evidence; do not infer them from a model name.Lane and unassigned facts
-
Delivery: Assign
conversation_deliveriesand queue facts to a window only by an exact native window/message link or an independently owned run or queue generation. AconversationKeyalone does not identify which window a fact belongs to. Unassigned facts go once in manifest_metaai.openclaw/unassigned, with the lane’s identity (its conversation key) and provenance; declare a vendor loss only when facts are omitted, not merely unassigned. A standalone stream needs a bundle to retain these facts. When emitted,delivery.messageindependently requires a message reference that can be resolved (ASR-235). Map native outcomescreated/queuedtoqueued,sent/repliedtosent,suppressedtosuppressed,rejectedtofailed, andunknowntounknown; keep platform IDs,retry_countand recorded reasons. ANO_REPLYmessage alone is not a delivery attempt. -
Side facts and execution: copied
__openclaw.runIdvalues never establish which run or window owns facts stored outside the transcript. Without an independent original-run/window link, retain the fact as unassigned. A native nullexitCodemeans the exit code is unavailable; omit it. Keep a numericexitSignalas a number inraw, never insignal.
_meta["ai.openclaw/unassigned"] with the common shape. Selection may retain these facts with any session subset, including an empty subset, but must not imply session ownership. State the selection policy in the export report.
Losses
Declare every applicable loss under ASR-157. Missing model rendering, accounting joins, lineage or exact context boundaries remain losses when this entry does not qualify them. Unknown ownership is not itself lost data. Declare omitted unassigned facts with a documented vendor loss or report, not a new core category.Evidence
Basis: source (openclaw/openclaw at 2038c84, package 2026.10.7, agent database schema 37). The SQLite converter supplement below is pinned to a different source revision, and its evidence comes from synthetic captures made by the native writer.
Basis: source-verified at base 15e4d849d5f9a3559132454aeff90fe54781dee9, with native ASR patch 3dcc7856a4bccc8bed7a32f2a3b530511e1a1691, agent schema 37. The pinned persistence writers produced synthetic captures made without model calls (no-model captures). This evidence applies to the supplement; it does not replace the 2038c84 rules above. Compatibility with schemas 17 and 19 has authored tests only. The evidence for this converter does not cover PostgreSQL.
Evidence applies only to the named formats and explicit mappings above. A real capture covers only exercised behavior. A no-model writer probe covers persistence, not model or tool execution. Authored tests do not qualify an agent version. Unlisted formats and link mappings remain unqualified.
Observations from 2026-10-27
observed, version 2026.10.8 (dc2523e): OpenClaw 2026.10.8 (dc2523e) wrote agent schema 25 and shared schema 20. Qualify storage schemas separately from release numbers. For shared skill/cron tables without cursor_id, the observed order is timestamp then rowid. Edit, shell-test, resume and review/question runs retained a native input_request. Preserve that request without inventing an answer or approval.
The ctx conversion produced 2 streams and 132 records, with 0 errors, 0 notices, 0 gaps and 0 unidentified inputs under validator-v0.1.2. These counts describe the selected output; an empty bundle qualifies no source format. No sanitized native fixture was retained for this observation.
Whole native records use native.record or native.blob, with JSON text columns preserved as strings. The exporter’s own bookkeeping for serialization, partitioning and message keys belongs in the exporter namespace (rs.ctx for ctx), not to a provider metadata destination. These runs add no evidence for approvals, lineage links, compaction boundaries or exact request context that they did not exercise.