Mentu

Quickstart

In this quickstart you run a Monitor Protocol server on your computer, create a monitor, record what it sees, and read it back the way an agent would: pull, do the work, then acknowledge. Then you crash the reader on purpose and see that nothing is lost.

It takes about ten minutes.

Before you begin

  • Node.js 20 or newer.
  • curl, and jq to pick values out of the JSON replies. Without jq, copy the values from each reply by hand.

1. Start a server

npx @mentu/monitor-protocol serve --port 8130 --state ~/.monitor-protocol/state.json

The server listens at http://127.0.0.1:8130/mp/v0. The --state file keeps your monitors and subscriptions when the server restarts. Leave this terminal running and open a second one for the next steps.

Check that it answers:

curl -s localhost:8130/mp/v0/discover | jq

The reply lists the protocol version, what the server supports and its limits.

2. Create a monitor

A monitor is a definition: what is watched, how often, and with what authority.

REPLY=$(curl -s localhost:8130/mp/v0/monitors -d '{
  "id": "ci",
  "name": "CI on main",
  "owner": "human:you",
  "horizon": "minute",
  "capabilities": ["observe"],
  "visibility": "public",
  "types": ["com.example.ci.run"]
}')
echo "$REPLY" | jq .monitor
OWNER_TOKEN=$(echo "$REPLY" | jq -r .owner_token)

The reply has two parts: the monitor as the server stored it, and an owner_token. The owner token is shown only once. Anyone who holds it can publish to the monitor and change it, so keep it private.

Field What it means
owner Who the monitor belongs to, as an actor name such as human:you or agent:ci
horizon The time scale it works on: event, minute, hour, day, week or month. Every observation carries it.
capabilities What a subscriber may be granted: observe to read, react for mechanical reactions, act to take leases on work
visibility private needs a token to see, shared needs the subscribe token the server mints, public is open to read
types The kinds of observation it emits, named in reverse domain style

3. Record what it saw

A producer publishes observations with the owner token. Here, a CI probe reports three builds:

for build in 1:passed 2:failed 3:passed; do
  n=${build%%:*}; status=${build#*:}
  curl -s localhost:8130/mp/v0/monitors/ci/observations \
    -H "Authorization: Bearer $OWNER_TOKEN" \
    -d "{\"type\":\"com.example.ci.run\",\"subject\":\"build-$n\",\"actor\":\"probe:ci\",\"origin\":\"probe\",\"tier\":\"measured\",\"data\":{\"status\":\"$status\"}}" \
    | jq -c '{seq, subject: .observation.subject, tier: .observation.tier}'
done

Each reply carries the observation and its seq, its place in the log. Three fields say where the observation came from:

  • actor is who saw it, here probe:ci.
  • origin is what kind of source that is: human, agent, webhook, probe or system.
  • tier is how much the claim can carry. measured means an instrument measured it. The full ladder is in Agents acting for people.

4. Subscribe

A subscription is a reader with its own place in the stream.

REPLY=$(curl -s localhost:8130/mp/v0/subscriptions \
  -d '{"monitor":"ci","subscriber":"agent:reader","capabilities":["observe"]}')
echo "$REPLY" | jq .subscription
SUBSCRIPTION=$(echo "$REPLY" | jq -r .subscription.id)
READER_TOKEN=$(echo "$REPLY" | jq -r .token)

The subscription starts at cursor 0, the beginning of the log, so it can catch up on everything that happened before it arrived. To start with new observations only, send "from": "head".

5. Read what you have not seen

curl -s "localhost:8130/mp/v0/subscriptions/$SUBSCRIPTION/pull?limit=20" \
  -H "Authorization: Bearer $READER_TOKEN" \
  | jq '{cursor, next, lag, redelivered, seen: [.observations[] | {id, type, subject}]}'
Field What it means
observations Everything after your cursor, oldest first. It includes the monitor's own records, such as ai.mentu.monitor.configured, because configuration is kept as observations too.
cursor Your committed place in the log. A pull never moves it.
next The value to acknowledge once you have handled this batch
lag How many observations are waiting for you, counting only what your filter lets through
redelivered How many of these you were given before without acknowledging them

Add wait=25 to the query to hold the request open for up to 25 seconds until something new arrives.

6. Crash before acknowledging

Pretend your reader crashed while it was working, before it acknowledged anything. Run the same pull again:

curl -s "localhost:8130/mp/v0/subscriptions/$SUBSCRIPTION/pull?limit=20" \
  -H "Authorization: Bearer $READER_TOKEN" \
  | jq '{redelivered, seen: [.observations[] | {id, subject, redelivered}]}'

The same observations come back, and each one is marked "redelivered": true. The monitor did not forget them just because they were handed out once. That is at-least-once delivery: a reader may see something twice, but it never misses it.

7. Acknowledge

Once the work is done, acknowledge with the next value:

NEXT=$(curl -s "localhost:8130/mp/v0/subscriptions/$SUBSCRIPTION/pull?limit=20" \
  -H "Authorization: Bearer $READER_TOKEN" | jq .next)
curl -s localhost:8130/mp/v0/subscriptions/$SUBSCRIPTION/ack \
  -H "Authorization: Bearer $READER_TOKEN" -d "{\"cursor\": $NEXT}" | jq

The reply shows the new cursor and a lag of 0. Pull again and the batch is empty.

The cursor only moves forward. Acknowledging a lower value is refused with CURSOR_BACKWARDS, and sending the same acknowledgement twice is harmless: it returns the same result.

8. Read the state

curl -s localhost:8130/mp/v0/monitors/ci/state | jq '{live, counters, confidence}'

The state is the monitor's running summary. Look at confidence: its value is null, and inputs.missing lists what it would need to compute one. The state says what it does not know instead of guessing.

The gaps list names what is uncertain. You will see independence_unknown, because nothing has checked whether the sources are independent of each other, and unattested_origin.

ℹ️

unattested_origin means nobody independent vouched for the monitor's owner. This server took human:you at its word. Start the server with --registration-token <token> and monitor creation will require that token, and the monitors it creates are marked as attested.

9. Follow it live (optional)

Server-sent events deliver observations as they arrive:

curl -N "localhost:8130/mp/v0/subscriptions/$SUBSCRIPTION/stream" \
  -H "Authorization: Bearer $READER_TOKEN"

Publish another observation from a third terminal and it appears in the stream. A stream is a wake-up, not a commit: only an acknowledgement moves your cursor.

Next steps

© 2026 Mentu.