Skip to main content

agmon

Remote agent-run monitor. A single FastAPI process that:

  1. Ingests — a background thread scans $AGENT_RUNS_DIR every 2 seconds and folds the spool files written by agmon run (<run_id>.meta.json and <run_id>.jsonl) into a SQLite database.
  2. Serves — a read-only HTTP API for querying run state and streaming events, meant to be hit from a laptop on the same tailnet.

The ingester is the only writer; it owns one SQLite connection. HTTP handlers open short-lived read-only connections. Event ingestion is crash-safe: each .jsonl file's byte offset is persisted in the same transaction as the events it covers, so a crash never skips or duplicates events. Partial trailing lines (mid-write) are left for the next scan.

On top of the raw ingest, a small derivation layer (agmon/derive.py, pure functions) turns events into answers: a run's effective status, what it is doing right now, what went wrong, and what the fleet is costing.

Migrations are: drop and replay

The spool is the source of truth; the SQLite database is a disposable index. The schema carries a version (schema_meta.version). On startup, if the db's version is missing or does not match the code's SCHEMA_VERSION, agmon deletes the db file (and its -wal/-shm sidecars) and recreates it empty — which resets all ingest offsets, so the next scan re-ingests the entire spool from scratch. There is no in-place migration path; never hand-edit the db.

Requirements

  • Python 3.12+
  • uv

Configuration

All configuration is via environment variables, read once at startup:

Variable Default Meaning
AGENT_RUNS_DIR ~/agent-runs Directory of run spool files
AGMON_DB ~/.local/share/agmon/agmon.db SQLite database path (WAL mode)
AGMON_HOST 0.0.0.0 Bind host
AGMON_PORT 8400 Bind port
AGMON_STALL_SECONDS 300 Quiet time before a live running run is reported stalled

Running

agmon is one installable tool with two faces: a server (agmon serve) and a CLI client (everything else). Bare agmon / python -m agmon is now the CLI.

uv sync            # install deps
uv run agmon serve # start the collector + API (box-side)
uv run agmon ls    # query it from the CLI

⚠ Upgrading from an earlier version — systemd unit change required. The server used to start with python -m agmon; that command is now the CLI. Change your unit's ExecStart to agmon serve and reload:

systemctl --user daemon-reload && systemctl --user restart agmon

A ready-to-copy user unit lives at deploy/agmon.service.

Security

no auth because the tailnet is the auth, bind accordingly, never expose 8400 publicly, tailscale serve for HTTPS

CLI

The agmon client curates the API into default views with escape hatches. serve and run execute box-side (they touch the local spool/process); the read commands (ls/show/tail/events/costs) work from anywhere with $AGMON_URL set.

Global behaviour: the server is --url, else $AGMON_URL, else http://localhost:8400. Run-id arguments are optional (omitted = the most recent run) and match by unique substring (agmon show a3f9). Output is a rich table on a TTY, decoration-free TSV when piped (--plain forces TSV); --json dumps the underlying object(s); --fields a,b,c projects one-level dotted fields (bare --fields lists the available names). agmon --version prints the version.

agmon ls                        # fleet glance, newest first (-n N, --all)
agmon ls --status running       # filter by raw status (also --session <sid>)
agmon show a3f9                 # one run, digested (--full-prompt, --raw)
agmon tail                      # live-follow the latest run (--last N)
agmon events a3f9 --errors-only     # forensics table (--type, --after, -n)
agmon costs --days 7            # cost/turn rollup (--since/--until)
agmon serve --port 8400         # start the server (--host/--port override env)
agmon run @task.md --cwd ~/src/proj    # launch + spool a run, prints run_id

agmon tail is scriptable — it exits 0 on a finished run, 1 on error, and 3 if the run died — so agmon tail $id && next-thing works. Fields are discoverable from the tool itself:

agmon show --fields             # list the projectable field names, then exit
agmon show a3f9 --fields status.effective_status,metrics.total_cost_usd

Client-only install

The read commands need only the client, not the server box. Install the package anywhere on your tailnet and point it at the server:

uv tool install agmon           # or: pipx install agmon
                                # or: uv tool install git+https://github.com/phairoh/agmon
export AGMON_URL=http://server-box:8400
agmon ls

Tests

uv run pytest

Tests drive the scan step directly (app.state.ingester.scan()) rather than sleeping on the polling thread.

API

All responses are JSON. All routes are under /v1.

Effective status

Several endpoints report an effective_status derived from the run's meta status, process liveness, and event recency. The vocabulary:

value meaning
finished meta status finished (passed through)
error meta status error with a non-null result_subtype — the task itself failed
interrupted meta status error with a null result_subtype — the stream ended with no result event (overload, kill signal, crash); the retryable kind
died meta status running but the pid is gone — the wrapper stopped without finalizing (surfaced as-is, the row is not rewritten)
stalled meta status running, pid alive, but no new events for more than AGMON_STALL_SECONDS; stalled_seconds is set
running live and recent

GET /healthz

Unversioned on purpose: health is an operational endpoint with a different stability contract than the /v1 data API.

curl -s localhost:8400/healthz
# {"ok":true,"runs_dir":"/home/you/agent-runs","db":"...","last_scan_at":"2026-07-08T...","runs_tracked":3}

GET /v1/runs?status=<s>&limit=<n>

Newest first by started_at (default limit=50). Each item carries the run columns plus prompt_preview (first 120 chars), event_count, last_event_at, last_event_type, pid_alive, effective_status, stalled_seconds, and issue_count (a count of error-flagged events). status here filters on the raw meta status, not effective_status. pid_alive is computed at request time (os.kill(pid, 0)) only for running runs with a pid, else null.

curl -s "localhost:8400/v1/runs?status=running&limit=10"

GET /v1/runs/{run_id}

Full run row including prompt and the parsed meta_json (everything from the spool .meta.json, including fields not columnized), plus the same computed fields. 404 with {"error": "..."} for an unknown id.

curl -s localhost:8400/v1/runs/20260708T174951-67a5e8

GET /v1/runs/{run_id}/summary

The full picture for one run: {run, status, activity, issues, metrics, result_text}. status is the derived status block above; activity is {last_tool, last_text, progress}; issues is the most recent 50 error-flagged tool_results and non-success results ({seq, category, tool, snippet}, where category is permission / tool_error / run_error); metrics is {num_events, tool_counts, duration_seconds, num_turns, total_cost_usd, usage}; result_text is the full result string from the run's result event (null if absent). All events for the run are loaded per request (no caching).

curl -s localhost:8400/v1/runs/20260708T174951-67a5e8/summary

GET /v1/runs/{run_id}/events?after=<seq>&limit=<n>&errors_only=<bool>

Events with seq > after (default after=0, limit=200), ordered by seq. Each event is {seq, ingested_at, type, subtype, payload} with payload parsed back into JSON (or the raw string for lines that failed to parse, stored as type = "_unparseable"). With errors_only=true, only error-flagged events are returned. The response includes next_after — the max seq returned, or the request's after if the page is empty — so a client can poll in a loop.

curl -s "localhost:8400/v1/runs/20260708T174951-67a5e8/events?after=0&errors_only=true"

GET /v1/stats/costs?since=<iso>&until=<iso>&bucket=day

Cost/turn rollup over the runs table, bucketed by started_at date in UTC (default since = 30 days ago; until optional). Runs with a null cost still count toward runs but contribute 0 to cost.

curl -s "localhost:8400/v1/stats/costs?since=2026-06-01T00:00:00%2B00:00&bucket=day"
# {"buckets":[{"bucket":"2026-07-08","runs":3,"total_cost_usd":0.31,"total_turns":12}],"totals":{...}}

Self-reported progress

An agent can surface a one-line progress note by emitting an assistant text line matching ^PROGRESS: (.+)$. The most recent such line is returned as activity.progress in the run summary.

Layout

src/agmon/
  config.py    # env-var configuration
  db.py        # schema + connection helpers, drop-and-replay migration
  ingest.py    # background scanner (the only writer)
  derive.py    # pure derivation functions (status/activity/issues/metrics/result)
  api.py       # FastAPI app + endpoints
  client.py    # pure HTTP client + id resolution + lineage (no printing)
  render.py    # all CLI formatting (tables/TSV/event compaction; no I/O)
  cli.py       # argument parsing + wiring (agmon ls/show/tail/events/costs/serve/run)
  runner.py    # `agmon run` — the ported launch/spool wrapper
  __main__.py  # `python -m agmon` (the CLI)
tests/
  test_agmon.py        # ingester + core API
  test_adversarial.py  # byte-offset / crash-durability
  test_derive.py       # pure derivation functions
  test_stage2.py       # stage-2 endpoints + replay-as-migration
  test_stage3_server.py # result_text + healthz move
  test_client.py       # id resolution + lineage
  test_render.py       # event compaction + field flattening
  test_cli.py          # output layering + tail loop + run smoke

Metadata

Release files for agmon 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for agmon 0.3.0
File Size Uploaded Provenance
agmon-0.3.0.tar.gz 28.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for agmon 0.3.0
File Interpreter ABI Platform
agmon-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 61.3 kB

Release files / agmon-0.3.0.tar.gz

Download URL agmon-0.3.0.tar.gz
Size 28.3 kB
Tags Source
SHA-256 checksum
How to use checksums
54a26576488ee8fc3f6bb80d210e640cac52e0b5b593e6642e3183bafb92d7c8
BLAKE2b-256 checksum
How to use checksums
398d13d8c45d503ce53f321507cb0353a402fda6eda8d4f30149a014ddd4905b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / agmon-0.3.0-py3-none-any.whl

Download URL agmon-0.3.0-py3-none-any.whl
Size 33.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
92a19e65becd5985d471dac33c537e052d58c1d52d47a7870735cc751e6bd582
BLAKE2b-256 checksum
How to use checksums
d32b122907ee4228ff22f9f12ae70af7580f974f67c96b20efa63ae2682d012f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for agmon 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page