Mentu

Concepts

The Monitor Protocol has five parts, and each one is small. This page explains what each part is for, how the parts work together, and what version 0.1 does not do yet.

The five parts

Part What it is Where you meet it
Monitor A definition: what is watched, how often, and with what authority POST /mp/v0/monitors
Observation One thing the monitor saw, with where it came from and how much it can carry the monitor's log
State A running summary computed from the observations, including what it does not know GET /mp/v0/monitors/<id>/state
Subscription A reader with its own place in the log, called its cursor POST /mp/v0/subscriptions
Lease An exclusive, time-limited claim on one piece of work POST /mp/v0/subscriptions/<id>/leases/claim

Configuration is not a sixth part. Updating, pausing, resuming or retiring a monitor appends an observation of type ai.mentu.monitor.configured that records what changed, before and after. Months later, "why did this change?" has an answer in the same log as everything else.

Monitors

A monitor is created once and then lives on its own. Its main fields:

Field What it means
id A name of 2 to 64 letters, digits, dots, dashes or underscores
owner Who it belongs to, such as human:you or agent:ci. The owner decides how high its observations can be rated.
source What is watched: a kind (shell, ws, http, file, feed, cir, formula or log) and a ref
horizon The time scale it works on: event, minute, hour, day, week or month
capabilities What subscribers may be granted: observe, react or act
visibility private, shared or public
types The kinds of observation it emits, in reverse domain style such as com.example.ci.run
ttl_seconds How long silence is normal. After that, the state reports stale_source.
retire_after_mute_seconds How long a subscription may go without pulling before it is retired. The default is seven days.
Visibility Who can see and subscribe
private Only someone holding the owner token
shared Only someone holding the subscribe token the server mints for it
public Anyone. Subscribers get the monitor's default_grant, which is observe unless the owner says otherwise.

Capabilities are granted, not requested. Asking for act on a public monitor does not give it to you: more than the default grant needs the owner token or the subscribe token.

Observations

An observation is a CloudEvent with six extra attributes and a structured provenance.

Attribute What it says
sequence Its place in the log, zero-padded to 20 digits and strictly increasing within a monitor
tier How much the claim can carry: src, measured, derived, unverified or falsified
origin What kind of source made it: human, agent, webhook, probe or system
verified How it was checked: human_verified, machine_verified, certified, reported or unverified
horizon The monitor's time scale
actor Who saw it, such as probe:ci or agent:claude

data.provenance uses the relation names of W3C PROV. It records the actor, the person it acted for in on_behalf_of, whether the monitor's owner was attested, a source_ref, the rule that produced it, what it wasDerivedFrom, and what it supersedes.

Nothing in the log is edited. A correction is a new observation that names the one it replaces in supersedes, and the original stays as it was. A refusal is kept too: when a write breaks a rule, the server appends an ai.mentu.monitor.rejected observation with the reason and the raw input, because a dropped event looks exactly like one that never happened.

State

The state is computed from the log each time you ask for it. It is a summary, never a second source of truth.

Field What it means
as_of, as_of_seq When it was computed, and up to which observation
covers_until The last time the source was actually heard from
live Whether the monitor is running, and if not, why: paused or retired
counters Observations, deliveries, completed leases, active subscriptions and refusals
ages Seconds since the last observation, the last pull and the last contact with the source
confidence A value, its inputs split into present and missing, and its gaps

While any input is missing, confidence.value stays null. The state never fills a gap with a number. The gaps say what is uncertain:

Gap What it means
independence_unknown Nothing has checked whether the sources are independent of each other
single_actor Everything so far comes from one actor
stale_source The source has been quiet for longer than its ttl_seconds
no_subscribers Nobody is subscribed
no_event_provenance Some observations carry no provenance
unattested_origin Nobody independent vouched for the monitor's owner

Subscriptions and delivery

A subscription's cursor is the next sequence number it will be given. It starts at 0, the beginning of the log, or at the head if you subscribe with "from": "head".

  • Pull returns what comes after the cursor, oldest first, and never moves the cursor. wait holds the request open for up to 25 seconds until something arrives, and limit caps the batch. A filter sent with a pull can narrow what the subscription receives, never widen it.
  • Acknowledge moves the cursor forward, after the work is done. It is cumulative: acknowledging next commits everything before it. A lower value is refused with CURSOR_BACKWARDS, and the same value twice returns the same result.
  • Redelivery is what happens after a crash. Anything pulled but not acknowledged comes back on the next pull, marked "redelivered": true. A reader may see something twice. It never misses it.
  • Seek moves a cursor on purpose, with a reason, and the move is recorded.
  • Retention is a server's choice to delete old entries. If a cursor falls below the retention floor, a pull answers 410 CURSOR_EXPIRED with the floor and a pointer to the state, so the reader can rebuild from the state and carry on.
  • Retirement is what happens to a subscription that stops pulling. After retire_after_mute_seconds, it is retired and an ai.mentu.monitor.subscription_retired observation is recorded. Subscribing again keeps the cursor, so a reader that comes back loses nothing.

Server-sent events, MCP resource updates and the Claude Code Monitor tool are wake-ups. They tell a reader to look, and any of them can be missed. The cursor is the guarantee.

Filters

Filters have the shape of Nostr filters: every key must match, and a list matches any of its values.

{ "types": ["com.example.ci.run"], "tiers": ["measured", "src"], "#status": ["failed"] }

The keys are types, sources, subjects, actors, tiers, origins, horizons, since, until, limit and text, plus #<tag> for tags an observation carries in data.tags. An unknown key is refused with INVALID_FILTER, and the reply lists the keys that exist. A filter that silently matched nothing would look like a quiet monitor.

Leases

A lease lets several workers share one stream of work without doing the same job twice. It needs the act capability.

  1. Claim a subject. One claimant wins. Everyone else gets LEASE_HELD, naming the holder.
  2. Renew while you work. A lease lasts 15 minutes unless you ask for a different lease_duration_seconds.
  3. Complete it with the outcome and the evidence, or release it with a reason so someone else can take it.

If a worker stops, its lease expires on its own and another worker can claim the subject. A worker that tries to complete a lease it has lost gets LEASE_LOST. Every claim, renewal and completion is recorded as an observation.

Running a server on your machine

A monitor server usually runs next to a web browser, so it assumes every web page you open can reach it. Since version 0.1.3 the reference server:

  • Refuses web pages. A request that carries an Origin the operator has not allowed is refused with ORIGIN_REFUSED. Allow a dashboard with --allow-origin. Command line tools send no Origin and are unaffected.
  • Answers only to its own names. On a loopback connection, a Host other than localhost, 127.0.0.1 or ::1 is refused, which stops DNS rebinding.
  • Caps request bodies at 1 MiB and refuses larger ones with TOO_LARGE.
  • Refuses writes to a paused or retired monitor with UNAVAILABLE, and records the refusal.
  • Keeps its state safe. Each write is flushed to disk before it replaces the last one, the previous copy is kept as .prev, and a damaged file is set aside while the newest readable copy is loaded. With no readable copy, the server refuses to start and changes nothing.
  • Allows one writer per state file. A second server on the same file refuses to start and names the first one's process.

What version 0.1 does not do yet

Some fields are part of the specification but not enforced by the reference server yet. They are listed here so nobody builds on them by mistake.

  • Budgets are stored, not enforced. A monitor can declare a budget, and the error OVER_BUDGET is reserved, but nothing counts spending yet.
  • Rules are checked, not run. A monitor's rules are validated when they are written. Nothing evaluates them yet.
  • Push delivery is declared, not sent. A subscription can ask for protocol: "http" with a sink, but the server does not push to it yet. Pull, the stream and MCP notifications work today.
  • Horizon authority is recorded, not enforced. The principles say a faster horizon never writes what a slower one owns. Version 0.1 stores the horizon on every observation but does not check it.
© 2026 Mentu.