Operating the service¶
Commands run from nimi-service/ as .venv/bin/python -m nimi_service <command>. The full list is in
the CLI reference.
The poll loop¶
run repeats every NIMI_POLL_SECONDS (default 60):
- Recover stuck runs: requests left
runninglonger thanNIMI_RCA_TIMEOUT_SECONDS+ 60s go back to the queue, or tofailedaftermax_attempts(2). - Ingest new messages from every channel (first poll of a channel backfills).
- Schedule:
- post replies that finished but were never posted (outbox sweep);
- if Claude is paused, stop here;
- skip requests older than
NIMI_STALE_HOURS; - triage waiting requests (up to 20 per cycle);
- start queued RCAs, up to
NIMI_MAX_PARALLELat once andNIMI_MAX_PER_DAYper UTC day.
- Reactions: sync 👍 / 🤔 / 👎 on replies posted in the last
NIMI_REACTION_DAYS. - Red flags: retry notifications that failed to send.
A failing cycle raises a red flag and the loop carries on.
Request lifecycle¶
stateDiagram-v2
[*] --> triage: new bot message in an RCA channel
[*] --> skipped: stale / sender not allowed / exclusion rule
triage --> held: critical secret in the message
held --> triage: release
triage --> skipped: exclusion rule added later / stale
triage --> not_applicable: OK, informational, test
triage --> reused: same incident as an earlier RCA
triage --> queued: needs a fresh RCA (fully or partly)
queued --> running
queued --> skipped: daily cap / stale
running --> done
running --> queued: interrupted (retry)
running --> failed: errors after max attempts
reused, not_applicable, done, skipped and failed are final. Every skip has a skip_reason.
Watching it¶
.venv/bin/python -m nimi_service status # channels, message counts, request states, active pause
.venv/bin/python -m nimi_service red-flags # things an admin should look at
.venv/bin/python -m nimi_service exclude # exclusion rules and their hit counts
Logs go to stderr (INFO level): each poll's counts, triage decisions, RCA runs with cost and duration.
Restarts and downtime¶
Progress lives in Postgres, so after a restart (even a long one) the service continues where it stopped:
- Messages: each channel's watermark makes the first poll fetch everything posted while it was down
(with
overlap_secondsof overlap so nothing is missed). - One age rule: alerts older than
NIMI_STALE_HOURS(default 6) when NIMI can process them get no RCA — whether they arrived during downtime, waited in the queue across a shutdown, or waited out a usage-limit pause (skip_reason: "stale: alert older than …"). - Interrupted runs go back to the queue (subject to the age rule).
- Missed replies: an RCA or reuse decision that finished but was never posted is posted by the outbox
sweep on the next poll, if the alert is still within
NIMI_STALE_HOURS.
Running it permanently¶
NIMI has no packaged service unit yet. A systemd user unit like this works:
# ~/.config/systemd/user/nimi.service
[Unit]
Description=NIMI RCA service
After=network-online.target
[Service]
WorkingDirectory=%h/nimi/nimi-service
ExecStart=%h/nimi/nimi-service/.venv/bin/python -m nimi_service run
Restart=on-failure
RestartSec=30
[Install]
WantedBy=default.target
systemctl --user daemon-reload && systemctl --user enable --now nimi
journalctl --user -u nimi -f
Maintenance tasks¶
| Task | Command |
|---|---|
| Pull older history for a channel | backfill <channel> --days N (stored as backfill: no RCAs; safe to re-run) |
Rebuild parses/signatures after changing parse.py or a platform profile |
Check with parse-test, then reparse (bump RULES_VERSION in parse.py for code changes; profile changes are versioned automatically). Also moves RCA requests to their message's new signature, so earlier RCAs stay reusable, and warns about exclusion rules whose signature no longer exists. |
| Clear a Claude pause now | resume |
| Send a held request to triage anyway | release <request_id> |
| Keep project copies current | "sync" in the registry, or a cron job (Adding a project) |
| Back up the database | pg_dump -h 127.0.0.1 -U nimi_service nimi > nimi-$(date +%F).sql |
| Apply new migrations after pulling | db upgrade (back up first) |