Sensors — the built-in set¶
Attend ships six built-in sensors. Three are modules of the attend crate and always compiled in: context, git and disclosure, under tools/attend/src/sensors/. Three are separate crates linked behind feature flags: sensor-peers, sensor-processes and sensor-keepwarm. Anything else is a sensor of your own, an external script or another crate (see authoring-sensors.md).
attend sensors lists what is running:
Sensor Kind State Interval Description Source
────────────────────────────────────────────────────────────────────────────────────────────────────────────
context builtin active 60s / 20s tracks context window usage and projects compaction attend@0.15.2
git builtin active 30s / 10s tracks dirty files, branch changes, and upstream divergence attend@0.15.2
peers builtin active 30s / 10s discovers Claude Code sessions and reads peer signals sensor-peers@0.6.0
processes builtin active 30s / 5s watches build/package tools and correlates exits with markers sensor-processes@0.6.0
disclosure builtin active 60s / 20s reheats affordance instructions on token-distance drift attend@0.15.2
keepwarm builtin active 60s / 60s keeps the prompt cache warm with one wake per idle stretch sensor-keepwarm@0.1.0
Summary¶
| Sensor | Observes | Interval / min | Threshold | Lane |
|---|---|---|---|---|
| context | the session's own token usage | 60s / 20s | 1.5 | event |
| git | the working tree and its upstream | 30s / 10s | 2.0 | event |
| peers | other Claude sessions and peer messages | 30s / 10s | 2.0 | message |
| processes | build and dev-tool processes | 30s / 5s | 2.0 | event |
| disclosure | tokens since attend last taught the messaging contract | 60s / 20s | 5.0 | event |
| keepwarm | idle time against the prompt-cache hour | 60s / 60s | 3.0 | message |
Every sensor polls faster (toward its minimum interval) while it sees change and slower (toward its base interval) when quiet. Event-lane sensors pass through the action-potential refractory and the strict governor (engagement.md). Message-lane sensors skip the refractory and use the message lane's permissive governor (delivery.md).
When a sensor discloses, its events print to stdout only if its accumulated magnitude has reached 3.0 (medium priority) or 5.0 (high). Below that they go to stderr, and if another sensor in the same batch did print, one [attend] also: N quiet event(s) from … line counts them.
context¶
The sensor that watches the session itself rather than the world (ADR-113). Each poll runs ways context --json and reads the current token usage.
Tier crossings. Crossing into a tier emits once per session:
| % used | Magnitude | Gist of the message |
|---|---|---|
| 50% | 1.5 | halfway; keep going if on track, scope the next stages if barely started |
| 65% | 3.0 | agent-ways will capture todos (75%) and memory (80%) shortly |
| 83% | 4.0 | the compaction checkpoint fires at 85%; finish the current task and sync |
| 90% | 5.0 | stop or compact now; quality degrades past here, and auto-compact is off |
The line reads context at N% — <message>. When the burn rate is known it adds (burning V%/min, and below 90% also , ~M min to critical when that estimate is under an hour. The 90% line also names the way to read: Use `ways show attend context-pressure --session $CLAUDE_SESSION_ID` for reflection guidance.
Velocity spikes. Burning more than 2% a minute with more than 5 points of change since the previous reading emits context velocity spike: X% in last N min (V%/min) at 2.0.
The sensor surfaces observations only. The ways layer acts at its own thresholds.
git¶
Watches git state in the working directory through git with GIT_OPTIONAL_LOCKS=0, so it never races a foreground commit. It reports deltas between polls:
| Event | Magnitude | Example |
|---|---|---|
| branch changed | 3.0 | branch changed: main → feat/new-thing |
| new commits on the current branch | 2.0 | new commits on feat/attend (HEAD abc123 → def456) |
| upstream moved ahead | 2.5 | 4 new commits on upstream (now 4 behind) |
| new dirty files | 1.5 | 3 new dirty files: src/main.rs, src/config.rs, Cargo.toml |
| working tree clean | 1.0 | working tree clean (changes committed or stashed) |
| ahead of upstream | 1.0 | 2 commits ahead of upstream (unpushed) |
It does not read commit messages, tags or hooks.
peers¶
Discovers other Claude Code sessions from ~/.claude/sessions/*.json and reads peer messages from the signals base. This is the sensor that makes multi-agent work possible: who else is working, where, and what they said.
Sessions.
| Event | Magnitude |
|---|---|
| peer session started, same project / other project | 3.0 / 1.0 |
| peer session exited, same project / other project | 2.0 / 0.5 |
| same-project peer changed status (working, waiting) | 1.5 |
| same-project peer crossed 80% context | 2.0 |
Messages. Each poll scans the project tray, _broadcast/ and every joined channel, and emits every unseen message. Base magnitudes are 7.0 for a message sent to the project, 5.0 for a channel and 4.0 for #open, raised by the per-peer boost (engagement.md). More than 8 messages in one poll become one count line. delivery.md covers the line format, the cold start and the Stop-hook drain.
The whole sensor rides the message lane, so session events skip the refractory too. That is a known limitation of ADR-136, which chose lanes per sensor rather than per observation.
The sensor re-reads _groups.yaml on every poll, so attend join and attend leave take effect on the next scan.
processes¶
Tracks dev-tool processes in ps output, not every process. A process produces start and exit events only if its name is on the sensor's tracked list (build tools, runtimes, editors, servers, ssh, git and similar, in tools/sensor-processes/src/lib.rs) or contains one of the session's focus keywords.
| Event | Magnitude | Example |
|---|---|---|
| process started | 2.0 | cargo started |
| process exited | 2.0 | nvim exited |
| watched build tool exited, no marker | 2.5 | cargo exited. Use `ways show attend build-complete --session $CLAUDE_SESSION_ID` for next steps |
| watched build tool exited, success | 2.5 | cargo exited (success). … |
| watched build tool exited, failure | 3.5 | cargo exited (failure, code 101). … |
The watch list¶
The watch list picks which exits get build enrichment: the affordance line and the success or failure magnitude. The default is:
cargo, rustc, make, cmake, ninja,
gcc, g++, cc, c++, clang, clang++,
go, npm, yarn, pnpm, tsc,
mvn, gradle, pip, pip3
sensors.processes.watch replaces the default; it is not merged, and an empty list turns enrichment off. It cannot add a tool to the tracked list: a watched tool that is not tracked (yarn, tsc, clang among the defaults) produces no events at all unless a focus keyword matches it. To enrich only Rust and C builds:
Exit codes through a marker file¶
ps cannot see an exit code after the process is gone. To get success or failure, wrap the build so it writes one line to $XDG_STATE_HOME/attend/last-build-status (~/.local/state/attend/last-build-status) as it finishes:
attend_build() {
"$@"
local code=$?
local dir="${XDG_STATE_HOME:-$HOME/.local/state}/attend"
mkdir -p "$dir"
printf '%s|%d|%d\n' "$1" "$code" "$(date +%s)" > "$dir/last-build-status"
return $code
}
# usage: attend_build cargo build
The format is cmd|exit_code|unix_ts. When a watched tool exits, the marker's cmd matches, and the marker is under 60 seconds old, the exit is enriched; otherwise it falls back to the plain line. The marker is one slot for the whole machine, so concurrent builds overwrite each other. On a network filesystem, write to a temporary file and mv it into place.
The sensor does not capture output, and it does not batch a build's child processes: cargo and each rustc it starts are separate events.
disclosure¶
Re-teaches the messaging contract when it has drifted out of the agent's recent context (ADR-122). Each poll reads the token count from ways context --json. The first poll of an attend run emits the contract (how to send, reply and scope messages, the drain, keepwarm, the CLI-is-the-contract rule), and it emits again each time the session has moved 25% of the context window since the last time. The magnitude is 5.0, so the line prints at high priority on a single poll.
The body is tools/attend/src/sensors/disclosures/messaging.md, compiled into the binary. It is kept in step with the attend skill and the attend way. The ledger is in memory only, so a restarted run teaches again on its first poll.
keepwarm¶
While armed, wakes an idle session once at 50 idle minutes so the prompt cache is read before its hour lapses (ADR-182). It emits at 3.0 on the message lane and needs a resolved session id; without one it does not start. Arming, the status card, the cost model and the agent's one-word contract are in keepwarm.md.
Choosing where a new observation goes¶
- Does a built-in cover it? The
gitsensor already watches git state. - Can a shell command observe it? Then write an external script sensor: no Rust and no rebuild. Almost everything belongs here, from GitHub Project boards to custom event logs. See
authoring-sensors.md. - Does it need native speed, shared state or complex logic? Then write a crate sensor: a new
sensor-*crate intools/that implementsSensor, added to attend'sCargo.tomlas an optional dependency with a feature, and registered intools/attend/src/sensors/mod.rs.
Related¶
authoring-sensors.md— writing new sensorsengagement.md— the event lane's refractorydelivery.md— the message lane and its two conduitskeepwarm.md— the keepwarm sensor in fullconfiguration.md— per-sensor keys and defaults- ADR-113 — attend and the first context sensor
- ADR-117 — sensor crate extraction and feature flags
- ADR-122 — the disclosure sensor
- ADR-136 — the event and message lanes
- ADR-182 — keepwarm