Agent Performance Measurement Hooks
Overview
The perf-hooks skill gives every agent lightweight instrumentation for three key metrics:
| Metric | Unit | What it measures |
|---|---|---|
response_time |
ms | Wall-clock elapsed time for a labelled operation |
memory_rss |
MB | Resident-set-size of the measuring process at a point |
startup |
ms | Agent initialisation / cold-start duration |
All measurements are appended to docs/exec-plans/perf.md — an
append-only Markdown table shared across every agent in a plan. Active timer
state (start timestamps) lives in docs/exec-plans/perf-timers.json so a
start call in one shell and a matching stop in a later shell both resolve
correctly.
Workflow
Do you want to time a specific operation? → Response time hooks
Do you want to capture current process memory? → Memory sampling
Do you want to record how long agent startup took? → Startup recording
Do you want to read measurements back or print stats? → Querying measurements
Response Time
CLI
# 1 — start the timer (records epoch to perf-timers.json)
python skills/perf_hooks.py start \
--label "call_llm" \
--agent "agent/coder-v1"
# 2 — stop the timer; elapsed ms is appended to perf.md
python skills/perf_hooks.py stop \
--label "call_llm" \
--agent "agent/coder-v1" \
--notes "gpt-4o cache miss"
Programmatic — context manager (recommended)
from skills.perf_hooks import PerfHooks
hooks = PerfHooks()
with hooks.timer("call_llm", agent="agent/coder-v1") as t:
response = my_llm_call()
print(f"Elapsed: {t.elapsed_ms:.1f} ms")
The context manager records the entry in perf.md on exit, whether the block
succeeded or raised an exception.
Programmatic — manual start / stop
hooks.start_timer("build_index", agent="agent/coder-v1")
build_the_index()
elapsed_ms = hooks.stop_timer("build_index", agent="agent/coder-v1")
stop_timer raises KeyError if no matching start_timer entry exists.
Memory RSS
CLI
python skills/perf_hooks.py sample-memory \
--label "after_load_index" \
--agent "agent/coder-v1" \
--notes "warm cache"
Programmatic
mb = hooks.sample_memory("after_embed", agent="agent/coder-v1")
print(f"RSS: {mb:.1f} MB")
The skill reads /proc/self/status on Linux and resource.getrusage on macOS.
Returns 0.0 on platforms where neither is available.
Startup
CLI
# Measure externally then record:
python skills/perf_hooks.py record-startup \
--agent "agent/coder-v1" \
--duration-ms 1240.5 \
--notes "first run, cold disk cache"
Programmatic
import time
_t0 = time.perf_counter()
# … your initialisation code …
duration_ms = (time.perf_counter() - _t0) * 1000.0
hooks.record_startup(agent="agent/coder-v1", duration_ms=duration_ms)
record_startup always uses the label "startup" so all startup entries group
together in stats output.
Querying Measurements
List — print raw rows
# All measurements
python skills/perf_hooks.py list
# Filter by agent
python skills/perf_hooks.py list --agent "agent/coder-v1"
# Filter by metric kind
python skills/perf_hooks.py list --metric response_time
# Filter by operation label
python skills/perf_hooks.py list --label "call_llm"
# Programmatic
entries = hooks.list(agent="agent/coder-v1", metric="response_time")
for e in entries:
print(e.timestamp, e.label, e.value, e.unit)
Stats — aggregate min / max / mean / p95 / count
# All metrics, all agents
python skills/perf_hooks.py stats
# One agent only
python skills/perf_hooks.py stats --agent "agent/coder-v1"
# One metric kind only
python skills/perf_hooks.py stats --metric startup
hooks.stats(agent="agent/coder-v1", metric="response_time")
Example output:
Metric Label N Min Max Mean p95 Unit
--------------------------------------------------------------------------------
response_time call_llm 42 312.441 4821.003 893.124 2107.550 ms
response_time embed_docs 8 88.002 341.115 201.004 310.000 ms
memory_rss after_load_index 5 84.320 102.144 94.611 101.200 MB
startup startup 3 680.100 1240.500 960.300 1200.000 ms
Standalone helper script
The companion script scripts/query_perf.py wraps the
CLI for quick interactive use without any package installation:
# Quick stats dump
python skills/perf-hooks/scripts/query_perf.py
# Filter by agent and metric
python skills/perf-hooks/scripts/query_perf.py \
--agent "agent/coder-v1" \
--metric response_time
Log File Format
Entries are written to docs/exec-plans/perf.md as Markdown table rows:
# Agent Performance Measurements
> Auto-generated by `skills/perf_hooks.py` — do not edit manually.
| Timestamp (UTC) | Agent | Metric | Label | Value | Unit | Notes |
|-----------------|-------|--------|-------|-------|------|-------|
| 2026-03-20T09:00:00Z | agent/coder-v1 | ⏱ response_time | call_llm | 893.124 | ms | cache miss |
| 2026-03-20T09:01:10Z | agent/coder-v1 | 🧠 memory_rss | after_load_index | 94.611 | MB | — |
| 2026-03-20T09:01:11Z | agent/coder-v1 | 🚀 startup | startup | 1240.500 | ms | cold disk |
Pipe characters inside field values are escaped as \|.
Data Structures
Measurement
| Field | Type | Description |
|---|---|---|
timestamp |
str |
ISO-8601 UTC string (e.g. 2026-03-20T09:00:00Z). |
agent |
str |
Agent identifier (e.g. agent/coder-v1). |
metric |
str |
Canonical metric key: response_time, memory_rss, or startup. |
label |
str |
Operation label (e.g. call_llm). |
value |
float |
Measured value in the metric's unit. |
unit |
str |
ms or MB. |
notes |
str |
Optional free-text annotation; empty string when not supplied. |
Measurement.as_dict() returns all fields as a plain dict.
PerfHooks
| Method | Signature | Returns | Description |
|---|---|---|---|
start_timer |
(label, agent) → float |
Start epoch | Persist timer start to JSON; survives process boundaries. |
stop_timer |
(label, agent, notes="", timestamp=None) → float |
Elapsed ms | Stop timer, record elapsed, remove from JSON. Raises KeyError if not started. |
timer |
(label, agent, notes="") |
_TimerContext |
Context manager; records on exit; exposes elapsed_ms. |
sample_memory |
(label, agent, notes="", timestamp=None) → float |
RSS in MB | Sample current process RSS and record. |
record_startup |
(agent, duration_ms, notes="", timestamp=None) → Measurement |
Measurement |
Record a known startup duration. |
list |
(agent=None, metric=None, label=None) → list[Measurement] |
Row list | Return measurements from file, optionally filtered. |
stats |
(agent=None, metric=None) → None |
— | Print min/max/mean/p95/count table to stdout. |
Constructor accepts optional perf_file: Path and timer_state: Path to
override the default storage locations (useful in tests).
Metric Reference
| Metric key | Display | Unit | Typical use |
|---|---|---|---|
response_time |
⏱ response_time | ms | LLM calls, HTTP requests, index queries |
memory_rss |
🧠 memory_rss | MB | After loading large data, embedding batches |
startup |
🚀 startup | ms | Agent or service cold-start duration |
Key Files
| Path | Purpose |
|---|---|
skills/perf_hooks.py |
Full implementation — Measurement, PerfHooks, timer state helpers, CLI entry-point. |
skills/perf-hooks/SKILL.md |
This document — agent routing metadata and usage guide. |
skills/perf-hooks/scripts/query_perf.py |
Standalone CLI helper for quick interactive queries. |
docs/exec-plans/perf.md |
Generated append-only measurement log; auto-created on first write. |
docs/exec-plans/perf-timers.json |
In-flight timer state; entries are removed when stop_timer is called. |