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.
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.
- TriggersA new or changed record, a daily or scheduled pass, a historical catch-up
- Run queueDebounced, deduplicated, one run at a time, inside the daily budget
-
Agent run
background-agentmodel with read tools over your corpus - GatesCited evidence, entailment verifier, brief judge
- StoresTime index, open loops, annotations, briefs
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:
-
Citation. An annotation must cite a document and quote it; a quote that
is not in the cited document is refused. How far a claim reasons from its quote caps its
confidence: 0.9 for a claim the quote states, 0.7 for one deduction from a single
source, 0.55 for a claim assembled across sources
(
brain.annotations.basisCeilings). A claim whose capped confidence falls below 0.25 (confidenceFloor) is refused as too weak to keep. - Entailment verifier. With the role assigned, a second model must agree that the quote supports the claim; a neutral or contradicting verdict refuses the write and tells the agent to weaken the claim. It checks annotations, dated facts that quote evidence, and the claims inside a brief. If the verifier errors or takes longer than 45 seconds, an annotation is stored marked unverified for the daily re-check, but a brief with an unverified claim is held.
- Brief judge. Every candidate brief must pass four questions: does it tell you something new, does it land before it matters, is there a real cost of doing nothing, and does it state a conclusion no single document shows? Cards that look ahead are asked whether they help you prepare instead of whether they are new. A brief that fails is never created, and the run is told not to raise it again. If an assigned judge times out or errors, the brief is held rather than shipped unreviewed. The morning digest is exempt.
- Evidence changes. When a cited document's content changes, each annotation that quotes it is re-checked against the new text; one whose quotes are all gone is withdrawn (kept for audit), and briefs and loops built on a withdrawn note are looked at again. Once a day the stalest notes are re-checked against their evidence and re-affirmed, weakened, superseded or retracted.
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.
--- 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:
-
Pause the catch-up. The Bootstrap tab's pause control sets
brain.bootstrap.enabledtofalse. Nothing already done is repeated when you resume: each document is marked once as reviewed. -
Switch a producer off. Each producer has its own
enabledkey (reference). -
Unassign the model.
omnesis model assign background-agent disabledstops all new Brain work at once. While experimental mode is on, the portal's Cognition page still shows what was written and the apps still show stored briefs, with a setup notice that opens the model picker; the apps' Loops view needs a running Brain. -
Turn experimental mode off. Restart the gateway without
OMNESIS_EXPERIMENTAL=1. Brain stops and its surfaces disappear from every client; the stored records stay in the database, unused.
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:
- Deleting a document for good (see Search → Beyond search) removes everything Brain derived from it: the loops that involve it and their briefs, the annotations and dated facts that cite it, and the claims that quote it.
-
omnesis brain notes --wipeclears the agent-notes file. It is the one direct write thebraincommand offers. -
Briefs can be dismissed in the apps' Briefs view. Nothing deletes a loop, brief or
annotation directly; the
braincommand is read-only for them.
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.
- JournalNew and changed documents, analytics rows, loops, clock ticks
- Filter and recallSource, type, people, fields; similarity or literal terms
-
JudgeOnly when needed: one bounded call on
watch-judge - StateWaits, counts, sequences, cooldowns, kept per key
- FiringA recorded row and a trace that explains it
- DeliveryOff by default; a notification or an agent wake, within daily caps
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.
{
"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. |