Integrate
Change events
Every general volume can keep an ordered feed of its changes. The mountos event command reads that feed, as a stream on stdout or as a
local REST API. Indexers, sync tools, and AI agents follow a volume without
walking the tree.
What the feed is
Four event types, create, delete, modify,
and rename, cover every change. Each event carries the full path
(renames carry the old path too), the inode, the file type, the size, and a
capture timestamp. Events are ordered by a per-fork monotonic sequence number,
and each fork has its own feed.
Events surface within a few seconds of the change. Saves that land in quick succession on the same file coalesce, so the feed reports that the file changed, not every write. The feed is near real time, not synchronous.
The feed exists for general volumes only. A feed request against an Iceberg volume is rejected explicitly rather than served an empty feed.
Turn it on
The feed is off by default. One volume field, eventLogRetentionPeriod, controls it. The value is in days, from 0
to 30. It gates capture and sets how long events stay readable. At 0 nothing is
captured and nothing accumulates. Events past the window are pruned. The field
is set at volume create or later with an edit. The trade-off is storage growth.
Longer windows keep more events, so size the window to what consumers actually
replay.
await client.volumes.edit(volumeId, {
eventLogRetentionPeriod: 7,
})period := int32(7)
_, err := client.Volumes.Edit(ctx, volumeID, &sdk.EditVolumeRequest{
EventLogRetentionPeriod: &period,
})use mountos_admin_sdk::EditVolumeRequest;
client.volumes.edit(volume_id, &EditVolumeRequest {
description: None,
retention_period: None,
grace_period: None,
fork_grace_period: None,
event_log_retention_period: Some(7),
}).await?;Stream to stdout
The command authenticates with the volume's access key, the same way a mount
does. The key pair can also come from MOUNTOS_ACCESS_KEY_ID and MOUNTOS_SECRET_ACCESS_KEY, and the HUB
domain from MOUNTOS_DISCOVERY_URL.
# everything retained, oldest first
mountos event -a AKIA... -s
# the last 7 days, then keep tailing
mountos event -a AKIA... -s --since 7d --follow
# one subtree, machine readable
mountos event -a AKIA... -s --path-prefix /docs --json SEQ FORK TIMESTAMP EVENT TYPE INODE SIZE PATH
101 0 2026-06-10 09:14:02 create file 524291 18432 /docs/spec.md
102 0 2026-06-10 09:14:09 modify file 524291 20480 /docs/spec.md
103 0 2026-06-10 09:15:11 rename file 524291 20480 /docs/final.md <- /docs/spec.md
104 0 2026-06-10 09:16:40 delete file 524291 - /docs/final.md--sincefloors on time. It takes relative windows (7d,2h) or timestamps ("2025-01-01 14:30").--start-seqfloors on the sequence number instead.--path-prefixfilters to a subtree on a path component boundary./docsmatches/docs/spec.mdbut not/docs2.--followtails the feed by polling. Without--sinceor--start-seqit starts at the live end instead of replaying history.--jsonemits one JSON object per event, the same shape the REST API returns.--fork-nameselects a fork's feed. Empty reads the main line.
Serve a local REST API
--http turns the command into a local HTTP server for tools that
poll an endpoint instead of owning a process pipe. The server daemonizes
(--foreground keeps it attached), binds 127.0.0.1, and
listens on the port set by --http-port. It serves the fork it
was started against, selected with --fork-name. Exposure beyond
loopback requires TLS. --http-no-loopback is rejected without --http-cert and --http-key.
mountos event -a AKIA... -s --http --http-port 9876
Callers never send the secret key. POST /v1/auth takes a signed
challenge. The body carries the access key id, a unix timestamp, a single-use
nonce, and a signature keyed with the secret. The response is a bearer token.
POST /v1/auth
{
"access_key_id": "<key>",
"timestamp": 1781083200,
"nonce": "<random hex, single use>",
"signature": "<hex of HMAC-SHA256(secret, key:timestamp:nonce)>"
}
{ "token": "<bearer>", "expires_in": 3600 }GET /v1/events serves the feed. The first request takes since, start_seq, path_prefix, and limit (1 to 1000). Every
response carries a continuation_token that encodes the cursor and
the filters, so the next request passes only the token. seek_to_end=true returns a token at the live end with no events, the
API form of tail-from-now.
curl -s "http://127.0.0.1:9876/v1/events?since=7d&path_prefix=/docs" \
-H "Authorization: Bearer $TOKEN"{
"events": [
{
"seq": 103,
"fork_id": 0,
"incarnation_id": 3,
"event_ts": 1781082911000000,
"born_at": 1781082842000000,
"inode": 524291,
"event_type": "rename",
"type": "file",
"size": 20480,
"path": "/docs/final.md",
"from_path": "/docs/spec.md"
}
],
"continuation_token": "eyJwIjoiL2RvY3MiLCJzIjoxMDMsInQiOjE3ODA0NzgxMTEwMDAwMDAsImYiOjB9",
"has_more": false
}Consumer semantics
- Ordering. Events arrive in per-fork sequence order.
seqis the resume cursor. A consumer stores the last sequence it processed, or the continuation token, and picks up where it left off. - Latency. Events surface within a few seconds and the CLI tail polls on an interval. Budget seconds, not milliseconds.
- Coalescing. A file saved many times in quick succession
surfaces fewer
modifyevents than writes. The feed reports that the file changed, not every write. - Gaps. Each event carries an
incarnation_id. A change between consecutive events marks a possible gap. A consumer that needs exactness treats it as a signal to reconcile the affected subtree. - Retention. Events older than
eventLogRetentionPerioddays are pruned. A consumer that reads at a cadence shorter than the window never loses its place. - Timestamps.
event_tsis the capture wall clock in microseconds and the canonical feed time.born_atcorrelates the event with the file's version history.