Skip to main content
The observation engine is the part of Critiqor that lives between your terminal and the OpenClaw process. It creates a persistent session record the moment monitoring begins, owns the child process lifecycle, merges two independent event streams at finalize time, and drives the diagnosis engine to produce diagnosis.json. All of this happens in runtime.py and session.py — the CLI layer only routes to these functions; it does not participate in any of the runtime logic.

Session Lifecycle

Every run moves through a strict state machine. States are written into the session record at each transition so you can always inspect runs/<run_id>.json to see exactly where a session stopped.
IDLE → MONITORING → FINALIZING → COMPLETED
                               → ABORTED (on error)
StateWhen it occurs
IDLEThe implicit starting state before critiqor monitor openclaw is called. No files exist yet.
MONITORINGcreate_session() has run, the plugin is loaded, OpenClaw is running as a child process, and events are being captured into runs/<run_id>/session.json.
FINALIZINGcritiqor finalize was called. request_finalize() stamps the session FINALIZING and active_session.json transitions to the same status so nothing else can claim the active slot. Evidence is being assembled and the diagnosis is being generated.
COMPLETEDfinalize_session() has finished. diagnosis.json has been written, the session record is updated with full diagnosis data, and active_session.json is removed.
ABORTEDAn error occurred during monitoring (launch failure, unhandled OSError, or explicit call to abort_session()). The session record is stamped ABORTED and active_session.json is removed.
Only sessions in MONITORING or FINALIZING status are considered active. load_active_session() returns None for any other status, which prevents accidental double-starts.

Session File Structure

create_session() writes the initial session record to runs/<run_id>.json using write_json(), a low-level helper that writes atomically through a .tmp rename. Path resolution for every run artifact goes through paths_for(runs_dir), which returns a SessionPaths object whose methods — run_path(run_id), evidence_summary_path(run_id), and diagnosis_path(run_id) — resolve to runs/<run_id>.json, runs/<run_id>/session.json, and runs/<run_id>/diagnosis.json respectively. At finalize time write_json is called again through evidence_summary_path to update the evidence file and a second time through diagnosis_path to write the completed diagnosis artifact. The structure below reflects what is present in runs/<run_id>.json at creation time and what gets filled in at finalization:
{
  "run_id": "run_001",
  "status": "MONITORING",
  "lifecycle": [
    { "state": "IDLE", "timestamp": "2025-06-01T12:00:00.000Z" },
    { "state": "MONITORING", "timestamp": "2025-06-01T12:00:00.000Z" }
  ],
  "timestamps": {
    "created_at": "2025-06-01T12:00:00.000Z",
    "started_at": "2025-06-01T12:00:00.000Z",
    "finalized_at": null
  },
  "metadata": {
    "agent_id": "openclaw_agent",
    "tenant_id": "default",
    "framework": "openclaw",
    "visibility": "private",
    "benchmark_id": "openclaw_runtime_v1",
    "difficulty_tier": "standard"
  },
  "event_log": [],
  "diagnosis": null,
  "trust_score": null,
  "confidence_score": null,
  "causal_graph": null,
  "failure_analysis": null,
  "cost_analysis": null
}
After finalize_session() completes, status becomes "COMPLETED", finalized_at is stamped, event_log contains the merged event stream, and diagnosis, trust_score, causal_graph, failure_analysis, and cost_analysis are all populated from the diagnosis engine output.

How Events Flow Into the Session

Two independent sources are merged at finalize time. Neither source blocks the other — they run in parallel during monitoring and are joined only when critiqor finalize is called. Source 1 — Internal lifecycle events Written directly to runs/<run_id>.json by append_event_to_run() throughout the monitoring session. These include:
  • state_transition — every status change (MONITORING, FINALIZING, COMPLETED, ABORTED)
  • process_start — recorded immediately after subprocess.Popen succeeds, includes the PID and launch command
  • process_end — recorded when the child process exits, includes exit code and PID
  • error_event — recorded on non-zero exit, timeout, KeyboardInterrupt, or OSError
Source 2 — Plugin evidence events Written in real time to runs/<run_id>/session.json by the bundled OpenClaw plugin as the agent runs. These are the structured tool and extension API events described in the OpenClaw Plugin page. At finalize time, load_session_evidence_events() reads session.json, normalizes each event through normalize_plugin_event() (which maps raw plugin event types to canonical Critiqor types), and merges the result with the internal lifecycle events before passing the combined stream to the diagnosis engine.

Environment Variables Set for OpenClaw

Before spawning the child process, the runtime calls openclaw_environment() to build an enriched copy of the current environment. The following variables carry run context into the plugin:
VariablePurpose
CRITIQOR_RUN_IDThe current run identifier (e.g. run_001). The plugin uses this to construct the path to session.json.
CRITIQOR_RUNS_DIRAbsolute path to the runs directory. The plugin resolves all write paths relative to this value.
CRITIQOR_AGENT_IDAgent identifier passed from --agent-id. Stored in session metadata.
CRITIQOR_TENANT_IDTenant identifier passed from --tenant-id. Stored in session metadata.
CRITIQOR_VISIBILITYDashboard visibility state (private, public, anonymous, or shared). Applied at ingestion.
CRITIQOR_EVENT_SOURCEAlways "openclaw" for monitored sessions. Identifies the event origin in the session record.
In addition, OPENCLAW_BUNDLED_PLUGINS_DIR is set to the parent of the critiqor-openclaw plugin directory, which causes OpenClaw to auto-load the plugin without any manual configuration.
  • Architecture Overview — The two-layer design and the full session pipeline from CLI to dashboard.
  • OpenClaw Plugin — How the plugin captures events and writes them to session.json.