Experimental

Experimental

Omnesis ships two features switched off because they are not yet stable: Omnesis Brain, which reads your data in the background and keeps cited notes, loops and briefs about it, and Omnesis Watch, which tells you or an agent when a condition you describe becomes true. This page is for operators who want to run them: how each works, what it spends, how to observe it and how to stop it.

These features are experimental as they are unstable and research/exploration projects within the Omnesis code base.

Nothing here is visible unless the process was started with OMNESIS_EXPERIMENTAL=1. Set it on the gateway to reveal Brain, Watch and their controls; with it unset the gateway serves none of their routes, schedules none of their work and creates none of their files. For a gateway you run in the foreground:

❯ OMNESIS_EXPERIMENTAL=1 omnesis gateway serve

For services, add the variable to each unit. Re-running service install rewrites the unit from the flags you pass, so include the ones the service was installed with (such as its keyring flags), then restart it:

❯ omnesis service install gateway --env OMNESIS_EXPERIMENTAL=1

❯ omnesis service install collector --env OMNESIS_EXPERIMENTAL=1

❯ omnesis service restart

GET /status reports whether the gateway is in experimental mode, and the portal, the mobile apps and the CLI use it to show or hide these controls. The flag on the collector matters only for sources: two experimental sources, Plaid and Local Files, are offered in the source picker only by a collector that runs with the flag itself. They are documented with the other sources.

Omnesis Brain

Brain is a background agent. It reads your records as they arrive, and on a schedule, without anyone asking a question, and writes down what it learns in a form the rest of Omnesis can use: dated facts, unresolved commitments, cited context on documents and people, and occasional cards worth your attention. It is the one part of Omnesis that spends model tokens with nobody asking, so most of this section is about what it does with them and how to bound it.

  1. TriggersA new or changed record, a daily or scheduled pass, a historical catch-up
  2. Run queueDebounced, deduplicated, one run at a time, inside the daily budget
  3. Agent runbackground-agent model with read tools over your corpus
  4. GatesCited evidence, entailment verifier, brief judge
  5. StoresTime index, open loops, annotations, briefs
Every piece of Brain work is a queued agent run; what it writes passes evidence checks before it is stored.

What it writes

Brain writes four kinds of record, all into the gateway's own database. Each carries the id of the run that wrote it, so any row can be traced back to the transcript that produced it.

Record What it holds Where you see it
Time index Temporal annotations: dates and intervals mentioned in text (“the lease renews on 14 March”), each with the document it came from. The agent can query it. Debug → Cognition → Calendar
Open loops Unresolved actions, decisions, promises and obligations, with who owes what, the documents involved, a deadline where there is one, and a ledger of every run that changed the loop. States: open, snoozed, done, dismissed. Debug → Cognition → Loops; the mobile apps' Loops view
Annotations Durable notes about documents and people, each backed by a quote from a cited document and a confidence. The agent's memory reads the same notes. Debug → Cognition → Memory
Briefs Cited cards for things that merit attention now, including one composed morning digest a day. Only the digest sends a push notification, at most once a day (brain.digest.push). Debug → Cognition → Briefs; the mobile apps' Briefs view

It also keeps a short agent-notes file, which is injected into every run's prompt as working memory (8 KiB by default, brain.notesMaxBytes), and it can decide pending person-merge proposals that the deterministic matcher left open: merge (a reversible rule that records the model's reason), distinct (the proposal is denied and not proposed again), or unsure (left for you). Proposals that involve you are never decided by Brain.

Turning it on

Brain runs only when two things are true: the gateway runs with OMNESIS_EXPERIMENTAL=1, and a model is assigned to the background-agent role and can actually be reached. Either alone does nothing. The second step is the one that starts spending: assigning the model starts every producer described below at once, from reacting to new records to the scheduled sweeps and the morning digest. Read Bounding the spend before you assign it. The one exception is the catch-up over your history, which waits until you start it yourself (see How it runs).

❯ omnesis model assign background-agent <backend>/<model>

❯ omnesis model status

The value is anthropic/<model>, codex/<model> or <backend>/<model> for an OpenAI-compatible backend you configured (see Operating → Models). A local GGUF model cannot serve background-agent or brief-judge, which need a chat backend; it can serve entailment-verifier. The portal's Settings → Models page lists the same roles under Cognition. Two further roles are optional reviewers:

Role What it does When unassigned
background-agent Runs every Brain agent run, and compiles natural-language watches. Brain does not run. Stored records stay readable.
entailment-verifier Checks that the quoted evidence actually supports a claim before an annotation is stored. A small, fast model is enough. Claims are stored after the citation check alone, with no verification stamp.
brief-judge Decides whether a candidate brief is worth interrupting you for. Briefs are created without review.

Codex can serve all three through the same ChatGPT login. A reviewer runs as its own model turn even when it uses the same model as the agent waiting on its verdict, and every role shares the account's usage allowance. GET /status reports the gate as brain, with visible, enabled, modelAssigned and active; the portal's Cognition page says which of the two conditions is missing.

How it runs

Every piece of Brain work is a run: one agent session with read-only tools over your corpus and write tools for the stores above. Runs wait in a durable queue and a single drainer works through them. Runs that are not daily execute strictly one at a time, so the queue, not the model, sets the pace.

What wakes a run depends on the record's own date, not on when it was synced. A document dated within brain.recencyWindow (7 days) of now wakes the live lane: a conversation waits for an hour of quiet (brain.conversationDebounce) but never more than six (conversationMaxDefer), an edited document waits 30 minutes (documentUpdateDebounce) but never more than four hours (documentMaxDefer), and each waits up to 30 minutes (derivationBarrier) for people resolution, links and date extraction to finish with it. Older records never wake the live lane, so a large sync of history does not flood it.

History is the catch-up lane (the portal calls it Bootstrap). It walks older documents that still carry a future date, most recent first, so Brain starts with upcoming dates and open commitments rather than a blank slate. Its cost grows with the size of your history rather than with how much arrives, so it does not start when you assign the model: open Debug → Cognition → Bootstrap, which states how many documents are waiting and roughly how many days they take at the configured pace, and press Start reading history. Adding your main sources first gives better results, because a message read before the reply that settled it can open a loop that should never exist. Once started, the lane has the lowest queue priority, goes quiet when nothing is left, and reopens on a new local day or when a source is added.

Run kind What starts it
data A recent record arrived or changed (the live lane).
bootstrap The catch-up lane picked a historical document.
daily Once a day at brain.dailyRunHour (05:00 gateway local time): a batch per high-volume source whose records are reviewed together rather than one by one, and the morning digest at brain.digest.hour (07:00).
time_based A follow-up Brain scheduled for itself, or a status check on an open loop that has gone quiet (spaced out further each time, up to 30 days).
synthesis The “Noticing” pass for connections, trends and gaps across the corpus (at most once a day), and collisions between loops that share a person, document or day.
sweep A scheduled sweep came due (see Sweeps).
verification The daily re-check of the stalest annotations against their evidence.
feedback You dismissed or snoozed a brief, or a note that a brief or loop was built on was withdrawn or superseded.
merge_adjudication A person-merge proposal the matcher could not decide.
notes_compaction The agent-notes file grew past its size limit.
subscription_compile A natural-language watch was compiled (see Watch). Recorded for its transcript; it never waits in the queue.

The queue serves your feedback first, then new records, then scheduled work, and the catch-up and re-checks last. A run that fails is retried up to five times, waiting one minute and then doubling the wait up to an hour.

When a model call fails, Brain looks at what the failure blames. A malformed or oversized request is the run's fault, and the run is retired after its retries. Anything else (no credit, a rejected key, a rate limit, an unreachable backend) is the environment's fault: the run keeps its place and its attempt is refunded. After three consecutive backend failures the drainer stops claiming work and backs off from one minute to fifteen, and the Bootstrap tab shows a Model backend — Failing banner with the provider's own error. Nothing is lost while it lasts.

What it will not write

A model's output is not trusted because a model wrote it. Every write passes checks that sit outside the model:

Bounding the spend

Everything is measured in tokens and runs, never in money: no model API Brain talks to reports a price, so any currency figure would be an estimate. Convert with your provider's own pricing.

A catch-up run is a full agent session, several model round-trips rather than one completion, so the catch-up lane is where large bills come from. The Overview tab splits each day's tokens into fresh input, input re-read from the provider's cache (billed at a fraction, and a subset of input, not an addition) and output. On the catch-up lane the cached share is usually high and output is around one percent, so if spend jumps while the token total barely moves, look at the cached share first.

All limits are set with omnesis config set <path> <value> (or the portal's Settings → Config) and are read live, so raising one resumes work without a restart.

Key Default What it bounds
brain.budget.dailyTokens no limit All background cognition tokens per local day. Reaching it parks the queue until the next day rather than failing runs.
brain.budget.dailyRuns no limit Completed background runs per local day, parked the same way.
brain.bootstrap.maxRunsPerDay 200 Catch-up runs enqueued per local day: the catch-up pace, and the first knob to turn down.
brain.bootstrap.maxRuns 1,000,000 Lifetime catch-up runs for the install, never reset, not even by removing a source. The lane parks when it is reached.
brain.bootstrap.activeHours unset A local window, {"from": "HH:MM", "to": "HH:MM"}, in which the catch-up lane may enqueue work. A window whose end is not after its start wraps midnight.
brain.reverification.maxPerSweep 12 Re-check runs per daily sweep, the second-largest producer.

The daily ceilings count interactive agent spend as well, because a budget that chat could exhaust alongside would not bound anything. The catch-up window gates when the lane enqueues work, not when an enqueued run executes; a run already bought is finished. To turn spend down, in order: lower maxRunsPerDay, move the catch-up into off-peak activeHours, lower reverification.maxPerSweep, switch individual producers off (reference), and set budget.dailyTokens as a hard stop under all of it. For example:

❯ omnesis config set /brain/bootstrap/maxRunsPerDay 50

❯ omnesis config set /brain/bootstrap/activeHours '{"from":"01:00","to":"07:00"}'

❯ omnesis config set /brain/budget/dailyTokens 2000000

The Bootstrap tab reports the catch-up lane as one of seven states:

State Meaning
unstarted Nobody has pressed Start reading history yet.
off brain.bootstrap.enabled is false: the lane is paused.
holding Within ten minutes of a gateway start, out of the way of post-restart work.
waiting Outside activeHours, or the day's allowance is spent and nothing is queued.
running Working through history at its configured pace.
drained Nothing left; reopens on a new local day or when a source is added.
parked Stopped at maxRuns. The one state to act on: raise the ceiling to resume.

Sweeps

A sweep is a scheduled pass across many records for one kind of pattern that no single arriving record would reveal: people you are waiting on, a trend in your health data, subscriptions worth reviewing. A sweep is a name, a cadence, a local time and a paragraph of prose; the prose is the only per-sweep logic there is, and it is placed inside a fixed set of instructions it cannot override. Sweeps run under the same budget as every other run and stop when brain.sweepsEnabled is false.

Eight sweeps ship with the gateway: Day ahead and Missed calls (daily, at the daily run hour), Weekly finances, Waiting on others, Health trends and Upcoming horizon (weekly), Relationships (every two weeks) and Subscriptions (every 30 days). You can add your own, or retime, silence or rewrite a built-in one, as Markdown files in sweeps/ under the config directory; the file name is the sweep id, and a file named after a built-in sweep overrides only the keys it sets. Edits take effect without a restart, and the portal's Settings → Sweeps tab edits the same files.

sweeps/unsent-promises.md
---
name: Promises I made
cadence: 7d
at: "06:30"
enabled: true
---

Look for promises the user made to someone else and has not yet kept:
sending something, booking something, giving an answer. Surface only the
ones where a reminder would help; skip anything already done or still
comfortably within the promised time. An empty pass is a good outcome.

Every front-matter key is optional; a new sweep needs cadence (at least 24h, counted in whole days) and a body. Without at, a time is derived from the id so sweeps do not all queue at once. An unknown key or a bad value skips that one sweep and is reported on the Sweeps tab and by omnesis doctor. A sweep fires once per boundary: a gateway that was down for a month runs a missed sweep once, not once per missed period. Treat a sweep file from someone else like a script: it is handed to an agent that can read your whole corpus, so read it before enabling it.

Observing it

The portal's Debug → Cognition page is the main view. Its tabs are Overview (gate status, today's budget and spend by mechanism), Loops, Runs (the queue, each run's transcript and decisions), Briefs, Calendar (the time index), Memory (annotations), Calibration and Bootstrap (catch-up state, limits, pause control and backend health). Debug → Background jobs shows when each scheduled task last ran. The same data is available from the CLI:

Command Shows
omnesis brain loops [--state open] Tracked open loops; omnesis brain loop <id> for one in full.
omnesis brain runs [--kind bootstrap] [--status failed] The run queue, newest first; brain run <id> for one run.
omnesis brain transcript <id> One run's prompt, tool calls and final text; --json for the raw transcript.
omnesis brain decisions [--doc <id>] What Brain decided about a record, and why.
omnesis brain spend [--days 30] [--by-mechanism] Tokens by day, or by day, mechanism and model.
omnesis brain calibration How stated confidence compares with what later held up.
omnesis brain notes The agent-notes file.

Transcripts are kept for the activity retention period unless brain.transcriptRetention sets a different one. A transcript contains corpus text the run read, so treat it with the same care as the corpus.

Pausing and turning it off

There are four levels, from narrowest to widest:

Privacy and removing its records

Brain reads the same corpus the agent does and sends what a run reads to the backends assigned to its roles, and nowhere else; it has no web access. With a hosted model, the documents a run reads go to that provider. HTTP backends on a non-loopback address are refused unless inference.allowRemoteInference is on. The morning digest's title and text travel through your push setup like any other notification.

The agent reads open loops and the time index when it answers a question, so they can inform an answer through Answer, which passes your privacy review as usual. A Direct grant exposes them only when it is unrestricted. Briefs are exposed to neither.

What Brain writes stays in the gateway's database with the rest of your data and is part of backups. To remove it:

Configuration reference

All keys sit under brain in the gateway's configuration and are read live. Durations are written like 30m, 6h or 7d. The budget keys are in Bounding the spend.

Key Default Meaning
recencyWindow 7d How recent a record must be to wake the live lane.
conversationDebounce / conversationMaxDefer 1h / 6h Quiet period before a conversation's run, and the most it can be deferred.
documentUpdateDebounce / documentMaxDefer 30m / 4h The same for an edited document.
derivationBarrier 30m Longest wait for links, people and dates to be derived before a run proceeds.
workerConcurrency 1 Queue workers. Runs that are not daily stay one at a time regardless.
dailyRunHour 5 Local hour of the daily runs.
digest.enabled / hour / graceMinutes / push true / 7 / 45 / true The morning digest, the hour it composes, how long it waits for the overnight queue to drain, and whether it pushes.
notesMaxBytes 8192 Soft size of the agent-notes file (at most 32768); past it, a compaction run shrinks it.
awarenessAxis true Also ask the daily pass for connections, trends and gaps, not only obligations.
synthesis.enabled / cadenceHours / maxPerDay true / 24 / 1 The “Noticing” pass over the whole corpus.
collision.enabled / maxPerSweep / timeHorizonDays true / 3 / 60 Daily check for loops that share a person, document or day, and for dated notes up to 60 days ahead that conflict. annotationContradictions (on, 2 per sweep) looks for notes that contradict each other.
annotations.enabled true Durable notes on documents and people. basisCeilings and confidenceFloor are described in What it will not write.
reverification.enabled / intervalDays / maxPerSweep / batchSize true / 14 / 12 / 6 Daily re-check of notes last verified more than 14 days ago: runs per day, notes per run.
provenanceRecheck.enabled true Re-examine briefs and loops whose supporting note was withdrawn.
judge.enabled true Pass every candidate brief through the brief-judge role.
mergeAdjudication.enabled true Let Brain decide undecided person-merge proposals.
bootstrap.enabled / direction true / recent-first The catch-up lane, and the order it walks history (oldest-first also accepted).
bootstrap.backlogTarget / batchSize 200 / 100 Most catch-up runs queued at once, and documents taken per pass.
sweepsEnabled true Run scheduled sweeps.
transcriptRetention unset How long run transcripts are kept; unset follows activityRetention.maxAge.

Omnesis Watch

Watch tells you, or an agent, when a condition becomes true: “a renewal email has waited five working days for my reply”, “three large card payments in one week”, “every Monday at 8”. A watch is a small program in a JSON language, checked against what your gateway actually holds, that follows the stream of new and changed records from the moment it is added. Most of its work is deterministic filtering and counting; a model is asked only when a condition needs judgement, and only about records that got past the cheaper steps.

  1. JournalNew and changed documents, analytics rows, loops, clock ticks
  2. Filter and recallSource, type, people, fields; similarity or literal terms
  3. JudgeOnly when needed: one bounded call on watch-judge
  4. StateWaits, counts, sequences, cooldowns, kept per key
  5. FiringA recorded row and a trace that explains it
  6. DeliveryOff by default; a notification or an agent wake, within daily caps
A watch reads the journal from where it last stopped; a firing is always recorded, and is delivered only if the watch asks for delivery.

What a watch is

A watch is a graph of nodes. Source nodes listen to the gateway's event journal: a document created or updated, a row landing in an analytics table, an open loop changing state, or a point in time. Other nodes combine what the sources emit: wait for something unless something else cancels it, require two things for the same key, count to a threshold, hold a cooldown. The last node feeds the sink, and each time the sink receives something the watch fires.

A firing is always recorded, with a trace of which node did what and why. Whether it goes anywhere is a separate, per-watch decision: a watch with no delivery is a row and a trace and nothing else. That default is deliberate. It lets you run a watch in the background for a week, see what it would have told you, and only then let it interrupt you.

A new watch starts at the head of the journal: “tell me when” is a question about what happens next, so a watch never fires on the history that existed before it. Records a source replays on its first sync are likewise ignored unless a node asks for them.

Creating a watch

There are four ways in, and they differ in who writes the definition and where it delivers:

Way Written by Delivers to Approval
Ask the built-in agent (portal or app chat) Compiled from your sentence A notification to your devices None; the agent relays how it read your request
An OpenClaw or Hermes agent Compiled from the agent's sentence Wakes that agent with its instruction Required; see below
omnesis watch add <file> You, in the Watch language Nowhere until you run omnesis watch deliver None
POST /admin/watch/compile Compiled from your sentence Nowhere, or an agent wake if you send an instruction None

Compiling a sentence is an agent run on the background-agent role, so it needs that role assigned even when Brain is not otherwise in use. The compiler reads your sources' declared fields and people and may search the corpus to resolve a name; it writes a definition, the validator checks it, and validation errors go back to the compiler to fix.

It then replays the candidate over the last 90 days of the journal and, if the watch would never have fired, would judge more than its budget allows or would speak far more often than the request implies, it gets one revision. A compile can take minutes; a single model call within it is abandoned after gateway.watch.compileTimeoutMs (three minutes). Some conditions cannot be watched, such as a verdict on how things are going overall with no event to hang it on; the compiler refuses those with a reason rather than producing a watch that never fires.

Every compile is recorded as a run of kind subscription_compile on Debug → Cognition → Runs, with the full transcript: what the compiler looked up, what it wrote and each repair turn. An installed watch links to it from its detail page. The transcript contains corpus text the compiler read.

An agent's request needs approval before it can wake anyone. Until then it is listed at the top of the portal's Watches page as a pending request, and the watch evaluates and wakes nobody. If your privacy policy is set up, its reviewer may approve or deny the request automatically; if the reviewer fails, the request stays pending for you. An operator's own compile or definition approves itself.

To preview a compile without installing anything, send "compileOnly": true; the answer carries the definition, the compiler's reading of it and the replay's numbers, and nothing is stored:

curl -s -X POST "$OMNESIS_GATEWAY_URL/admin/watch/compile" \
  -H "Authorization: Bearer $OMNESIS_TOKEN" -H "Content-Type: application/json" \
  -d '{"request": "Tell me when a supplier invoice arrives that is more than 30 days overdue", "compileOnly": true}'

The Watch language

A watch definition is a JSON document. The example below fires when someone emails you about a contract renewal and you have not replied in the same thread within five working days. Every value in it is fictional; it validates against a Gmail source.

renewal-notice-unanswered.json
{
  "watch": {
    "name": "renewal-notice-unanswered",
    "nl_query": "Tell me when someone emails me about a contract renewal and I have not replied within five working days.",
    "firing_policy": "stays_active",
    "ontology_fingerprint": "<fingerprint from GET /admin/watch/ontology>",
    "nodes": [
      {
        "id": "renewal_notice",
        "type": "source.document_event",
        "filter": {
          "source": "gmail",
          "event": ["created"],
          "documentType": "email",
          "people": [{ "role": "sender", "isSelf": false }]
        },
        "recall": {
          "semantic": {
            "query": "contract or subscription renewal notice",
            "threshold": 0.35
          }
        },
        "judge": {
          "proposition": "The title of this email refers to renewing a contract or plan",
          "output_schema": {}
        },
        "output_map": {
          "doc_id": "$e.docId",
          "thread_id": "$e.metadata.extra.threadId"
        }
      },
      {
        "id": "my_reply",
        "type": "source.document_event",
        "filter": {
          "source": "gmail",
          "event": ["created"],
          "documentType": "email",
          "people": [{ "role": "sender", "isSelf": true }]
        },
        "output_map": { "thread_id": "$e.metadata.extra.threadId" }
      },
      {
        "id": "unanswered",
        "type": "stateful.wait",
        "inputs": {
          "renewal_notice": { "role": "arm", "key": { "thread_id": ".thread_id" } },
          "my_reply": { "role": "cancel", "key": { "thread_id": ".thread_id" } }
        },
        "on_collision": "ignore",
        "duration": "5 business_days",
        "output_map": { "doc_id": "$n.renewal_notice.doc_id" }
      }
    ],
    "sink": {
      "input": "unanswered",
      "output_map": { "evidence": "$n.unanswered.doc_id" }
    }
  }
}

Read it from the top. renewal_notice is a document source: the filter cuts the stream to new inbound email at no cost (there is no direction field on a document, so “inbound” is a sender who is not you). The recall block nominates the emails whose text is close to the query; only those reach the judge, a single model call that must confirm the proposition before the node emits anything. The proposition is written against the email's title, because the title is the evidence the judge is guaranteed to see. my_reply has no recall and no judge: it exists only to cancel, and judging every message you send would cost a model call each. unanswered is a wait keyed on the thread, so an arm and a cancel meet only if they belong to the same conversation; five business days after the arm, with no cancel, it fires. on_collision: "ignore" means a second renewal email in the same thread does not restart the clock.

The ontology_fingerprint identifies the version of your install's sources, fields and tables the watch was checked against; a definition carrying a different one is refused, and the refusal names the current value. Watches written by the compiler get it filled in. The top-level fields are:

Field Meaning
name Lowercase kebab-case; how the CLI and the portal refer to it.
nl_query The request in plain words. Optional, but it is what a reader sees.
firing_policy stays_active, or once_ever to retire the watch after its first firing.
expires_at An ISO instant after which the question has ended; the watch retires on the clock even if nothing arrives.
constants Values read once from a document (a passport expiry date, a budget), each with the provenance_doc it came from. Referenced as $const.<name>.
nodes, sink The graph, and the node whose output is the firing.
delivery Where a firing goes. Not accepted by watch add; set it with omnesis watch deliver (Delivery).

The node types:

Type What it does
source.document_event A document created or updated. Filters on source, documentType, declared metadata fields and people by role. An optional recall block (semantic query and threshold, lexical terms matched against the title, or both) must be paired with a judge.
source.analytics_row A row in an analytics table, inserted (a key the journal has not seen before) or updated, with an optional SQL predicate. backfill is ignore by default; include also takes rows a source replayed on its first sync, for running totals.
source.open_loop A Brain open loop created, updated or resolved, optionally by id, state or person.
source.time one_off (an ISO instant) or recurring (five-field cron in the install's time zone). A boundary missed while the gateway was down fires once when it comes back.
stateless.or Fires when any input fires; $fired_by names which.
stateless.transform A DuckDB expression with no FROM that projects a fires column.
stateful.wait Fires when its duration elapses after an arm, unless a cancel arrives first.
stateful.and Fires when every input has fired for the same key within its deadline.
stateful.threshold Fires when n of its inputs have fired within the deadline.
stateful.sequence Fires when inputs arrive in the declared order within the deadline.
stateful.cooldown Passes a firing on at most once per min_interval.
stateful.persistence Fires when its input fires at least min_events times within duration.
sql A DuckDB query over the analytics tables that projects fires. It can re-run on a timer, require the condition to hold for a persistence period, or fire only on the rising_edge. Time comes from the $now and $today parameters, never from now().
llm A proposition judged over the evidence its inputs carry, on the watch-judge role, firing when its output satisfies fire_when.

Stateful nodes keep one instance per key, which each input extracts from what arrives (".thread_id" above); inputs into one node must produce the same key. A keyless input such as a clock tick feeding a keyed node declares "broadcast": true and reaches every live instance. What a second arm does to a live instance is on_collision: reset restarts it, ignore keeps the first, spawn starts another beside it (with max_live_instances, and never with a cancel input), and accumulate keeps a cell that survives firing so the node can fire again. Durations are written "<amount> <unit>" with units seconds, minutes, hours, days, weeks and business_days (Monday to Friday, no holidays); deadlines may also be "infinite", which draws a warning.

Expressions read $e.<path> from the arriving event (for example $e.people[role=sender].personId), $n.<node>.<field> from an upstream node, $judge.<field> from a judge's output, $key.<component> and $const.<name>. The only functions in a key are date_trunc and coalesce. The validator checks shape, graph, references, keys, collision rules, your install's sources and fields, SQL and semantic match, and reports each problem with a stable code and a JSON path.

Trying one before it runs

Because a watch starts at the head of the journal, a condition that can never match looks exactly like a quiet week. Two commands ask the past instead:

❯ omnesis watch try renewal-notice-unanswered.json --days 90

❯ omnesis watch probe renewal-notice-unanswered --days 180

watch try replays a definition file over recent journal events (500 by default, or --days up to 365) with the same engine the runtime uses, and reports, per node, how many events matched out of how many were evaluated, with the runtime's reason for a sample of its decisions. Nothing is stored. Its judge never says yes, so it reports what would have reached a model rather than what the model would have answered, and lexical terms are matched against titles only. It refuses when a semantic recall arm has no embedder to score it, when the journal is empty, and when the definition does not validate.

watch probe scores an installed watch's recall arm against past documents (90 days and 2,000 documents by default) and reports how many would have been nominated, with the best, 95th-percentile and median scores. When nothing would have been nominated it says by how much the best document missed the threshold. It returns counts and scores, never documents, and applies only the source and document-type filters, so its count is an upper bound.

How watches run

The gateway turns its own events into an ordered, durable journal: every two seconds while events arrive (every fifteen when idle), it writes up to 500 of them, deduplicated, with the fields watches filter on. Analytics rows enter through a transactional outbox, so a restart delays them instead of losing them. Every five seconds (every thirty when idle) each active watch reads up to 200 events from its own cursor. Timers are checked on the same tick, so a wait can fire up to the idle interval late. The journal, the definitions, per-key state and traces live in watch.db in the config directory, encrypted like the other stores and included in backups; a definition exists nowhere else.

The judge runs on the watch-judge role, which must be assigned explicitly: it does not inherit the Brain's model, and until it is assigned, events that need a judgement wait, with no model call and no spend. It can be Codex, a local GGUF model, an Anthropic model or an OpenAI-compatible backend that speaks Chat Completions. It sees the document's id and title, its kind, time and participant roles; a body excerpt is shown only in a second, veto-only check after the title-based decision says yes, so a body-reviewed nomination can cost two calls.

It fails closed: an error, an unparseable reply or a spent budget produces no decision and parks the nomination. A budget-parked nomination retries the next UTC day; a provider failure retries after a one-minute cooldown. Only calls actually sent count against the caps, which are kept on disk, so a restart does not reset the day.

❯ omnesis model assign watch-judge <backend>/<model>

A watch was checked against your sources' declared fields and tables when it was added. When those change (a source starts declaring a new field, a new analytics table gets its first row), every watch is checked again. A watch whose own inputs are byte-for-byte unchanged keeps running; one whose inputs moved is paused with a note, even if it still validates, because the same definition could now select different records. After reviewing, omnesis watch restamp re-validates every watch and resumes the ones that still hold. This happens when the new data arrives, which can be hours or days after an update.

A gateway that is stopping refuses new compiles with a 503 and gives running ones a short grace period. omnesis watch report --json shows compiles.running if you want to restart at a quiet moment.

Delivery

Delivery is off by default for a watch you add or compile yourself, and is set per watch. Turning it off again keeps the watch and its history:

❯ omnesis watch deliver renewal-notice-unanswered

❯ omnesis watch deliver renewal-notice-unanswered --to agent-wake --integration openclaw --instruction "Draft a polite reply asking for the renewal terms." --binding mailbox=drafts

❯ omnesis watch deliver renewal-notice-unanswered --to none

omnesis-notify (the default) has the built-in agent open a conversation about the firing, and sends a notification to your paired devices that quotes its opening message and opens that conversation when tapped. With no agent, or if the conversation cannot be opened, a plain notification composed from the request goes out instead. A watch with no push transport configured keeps firing and recording, and says so in the log and in omnesis watch firings.

agent-wake wakes a paired OpenClaw or Hermes agent. The watch decides when; your instruction (up to 8,000 characters) decides what, and is passed to the agent verbatim. Bindings (key=value, up to 32) name the things the instruction refers to, such as a channel or a mailbox; Omnesis never interprets them. What crosses to the agent is identifiers, the instruction, the bindings and two narrowly scoped credentials, never the firing's content.

When the agent asks what caused the firing, the gateway answers through a read-only turn that starts from what the firing recorded, and the reply passes your Answer privacy review like any other. The agent can then report what it did, for up to 24 hours; omnesis watch firings --all --since 24h shows firings with their outcomes. A watch naming an integration that no paired device holds keeps firing and logs that nobody was woken.

Delivery is capped per day. Over a cap, the firing is recorded as usual and marked suppressed, with the cap named in the trace; nothing is queued for later, because a late notification about a condition is a wrong one. The allowances are kept on disk.

Key (under gateway.watch) Default What it bounds
delivery.dailyCap 20 Notifications from all watches per day
delivery.perWatchDailyCap 5 Notifications from one watch per day
wake.dailyCap 25 Agent wakes from all watches per day
wake.perWatchDailyCap 10 Agent wakes from one watch per day
judge.dailyCap 200 Judge calls from all watches per UTC day (0, or at least 2)
judge.perWatchDailyCap 50 Judge calls from one watch per UTC day (0, or at least 2)

To prove a delivery path without waiting for the condition, omnesis watch fire <name> runs the delivery for a firing made by hand, optionally carrying --doc <documentId> or a JSON --payload. Nothing is evaluated and no judge is asked. The firing is marked by hand everywhere and never counted as a catch, the daily caps still apply, and when nothing arrives the result says why. It refuses a watch that delivers nowhere, and one that is paused or retired.

OpenClaw and Hermes

Experimental mode also covers the Watch half of the OpenClaw and Hermes integrations, which are otherwise generally available (see Agents & MCP → OpenClaw and Hermes). The gateway advertises it as capabilities.subscriptions on GET /health, and omnesis connect reads that to install to match: with the gate off, the agent gets the Answer tool and no Watch tools, its skill describes only what it can do, and the subscription routes themselves are not served. With it on, the agent gets a tool to create, list, inspect, update and revoke its own watches, each a sentence plus the instruction it wants to be woken with.

Turn the gate on and reconnect (or run omnesis connect <harness> --refresh) to add Watch management and Watch-reaction delivery to an installation that already exists. Each omnesis update refreshes the plugin the same way, so an installation gains or loses its Watch tools to match the gateway. An agent's watches are listed in the portal beside your own, and omnesis watches manages them from an integration's device token.

Observing and debugging

Command Answers
omnesis watch list Every watch, its status, how often it has fired, where it started.
omnesis watch show <id> [--diff <file>] The definition a watch is actually running, or its difference from a file.
omnesis watch firings <name> What it said, and what a woken agent did about it. --all --since 7d across watches.
omnesis watch trace <id> Why it fired, or why not: which node armed, on what key, what cancelled it.
omnesis watch judge <id> [--full] What the judge was asked, and what it answered.
omnesis watch report The whole install: running and stopped watches, judge calls against the caps, evaluation timings, parked nominations, delivery tallies and journal health.

Read a trace's transitions with their detail. ignored on a source with the reason naming the arms that declined means no model was asked; held … judge declined means a model was asked and said no; held … judge budget spent; parked means no model was asked and the nomination waits for budget. Traces keep the last 2,000 records per watch (traceRetained), while firings are kept for the life of the watch.

In the portal, Watches lists every watch with pending agent requests at the top; a watch's page shows its definition, its firings and the compile that produced it. Debug → Watch draws one watch as a graph, shows what each stateful node is holding per key and every armed timer, and lights up the path an event took, including the times the watch looked and did not fire. It is read-only; actions stay in the CLI. The iOS and Android apps show watches read-only when experimental mode is on.

omnesis watch report puts every stopped watch in one bucket: drifted (your sources changed under it: rewrite it, or restamp after review), failed (a node threw), held (someone paused it) or retired (it finished, by its horizon or a once_ever firing). A failed watch names the failure class: query (its own SQL), provider (the judge or recall backend), budget (one event needed more off-host rounds than allowed) or internal (a defect). The failing event is retried on every pass, so the watch stays stopped until you act; the message itself is in the trace, not in the status note, because it can quote corpus content.

Pausing, resuming and removing

❯ omnesis watch pause renewal-notice-unanswered

❯ omnesis watch resume renewal-notice-unanswered

❯ omnesis watch resume renewal-notice-unanswered --skip

❯ omnesis watch rm renewal-notice-unanswered

resume re-validates first and refuses, with the validator's diagnostics, a watch that would be paused again on its next pass. --skip moves a failed watch past the one event or parked nomination it could not get through and records the skip in the trace; everything else is untouched. rm (or Remove watch on its portal page) deletes the definition, its state, its traces and its firings, so a new watch with the same name starts fresh. For a watch that woke an agent, the record of what was sent to that agent is kept, marked revoked, because the Answer audit log points at it.

An agent's watch has a second record, the approved request behind it. Revoking it from the portal stops it waking the agent; when the agent itself revokes it, the watch is removed too. omnesis watches purge <id> (or --all-revoked) then deletes a revoked or expired request with its revisions, firings and answers for good.

Configuration reference

Runtime keys sit under gateway.watch; the caps are in Delivery.

Key Default Meaning
drainIntervalMs / idleIntervalMs 2000 / 15000 How often the journal is written while events arrive, and when idle.
batchSize 500 Journal events written per pass (at most queueCapacity).
queueCapacity 50000 Document events that may wait in memory (at least 1000).
evaluateIntervalMs / idleEvaluateIntervalMs 5000 / 30000 How often watches are evaluated; the idle value is also how late a timer can fire.
eventsPerWatch 200 Journal events one watch reads per evaluation.
traceRetained 2000 Trace records kept per watch.
compileTimeoutMs 180000 How long one model call may take while compiling.
compileReasoningTokens unset Bound on a compile turn's reasoning tokens, for reasoning models that run out of time deciding. Not honoured by every backend; the log says when it was not.
promptPeople 200 People named in the compiler's prompt, most-contacted first; everyone stays addressable by id.