rmagent-template — the lake-less skill factory
This is not a witness. It is the constitution + scaffold every
rmagent-* skill is cloned from.
Use it when you are about to write a new skill that:
- watches something on boxes (or wires, or rings) you administer
- must not become a SIEM / PCAP lake / span warehouse
- must answer a named question on demand, capped, with holes
Do not use it as a runtime hunt. Load rmagent-so, rmagent-iso,
rmagent-at, … for that. This file is the pattern those skills already share.
Push tools answer the questions you knew to ask, after the log rotated.
An rmagent skill answers the question you just thought of — by knocking
on the device that still has the ring.
1. The one-sentence test
Before you name the skill, finish this sentence:
Follow one ________ across ________ without a ________ warehouse,
by asking ________ on devices we administer.
| Existing skill |
Follow one … |
across … |
without a … warehouse |
by asking … |
rmagent-so |
principal (Admin/SYSTEM) |
Windows estate |
Security.evtx |
attest/sketch/edges/… |
rmagent-linux |
principal (root) |
Linux/macOS hosts |
journal export |
attest/sketch/edges/… |
rmagent-fr |
work id / ticket |
hops of an investigation |
span lake |
case/trace/trajectory |
rmagent-at |
application request |
Windows ETW rings |
ETL dump |
apptrace/appslow/… |
rmagent-ao |
agent process |
disk artifacts + LLM APIs |
transcript dump |
agents/agenttrace/… |
rmagent-iso |
STAN+RRN |
ISO taps Postilion↔Finacle |
PCAP lake |
txntrace/txnhops/… |
rmagent-redteam |
drill artifact |
WS1/WS2 |
(stages then scores so) |
stage/score/clean |
If you cannot fill the blanks, you do not have an rmagent skill yet. You have
a dashboard idea.
2. Constitution (non-negotiable)
Copy this block into every new SKILL.md. Delete nothing. Annotate domain
exceptions below it, never by editing it away.
- Watch only (Phase 0). No
actuate in the question set. Isolate /
disable / restart / iptables / usermod live in rmagent-actuate (or
the platform’s MOP), dual-controlled, journaled, reversible.
- Allowlisted questions only.
ask() refuses unknown names and refuses
actuate. Never an arbitrary remote shell as the product.
- Capped answers (32 KB default). Oversized → hole, not a truncated
lie and not a lake. Prefer a signal-aware cap (keep critical rows,
shed low-signal) over a bigger cap.
- A hole is an answer. Silent box, blind witness, retention-boundary,
unsupported field, TLS you cannot terminate, box you do not administer —
same JSON shape as a hop:
{asked, empty, why}.
- Never trust “no findings” until the witness can see. Every plane needs
an
*attest with blind_check. Empty + blind = false negative, the worst
failure mode.
- On-device rings, not shipped lakes. Data stays where it was written
(ETW, journald, circular JSONL, Sysmon). The jump host stores cases
(megabytes: path, holes, trajectory), not event exports.
- Context propagates; data does not. STC / grain ids travel. Event bodies
do not. Depth ≤ 8, fan-out ≤ 3 — an unconstrained hunter is a worm.
- Credentials never in the inventory, skill, or case. Env /
~/.rmagent/creds.json (mode 600) / vault. Never print them.
- Your estate only. Partner, scheme, SaaS you do not tenant, phone, NIBSS
— hole, not a witness.
- No tight retry on a silent host. Cooldown. Two missed attests = Critical.
- Secrets, PAN, payloads, prompt text — never stored unless the grain
requires a masked form. Env names not values. PAN
first6******last4.
Transcripts capped excerpts. PIN blocks never.
- Reversible persistent change. If you create a ring (AutoLogger, SPAN
session, analytic log), it is MOP, dry-run default,
--apply required,
--teardown exists and is tested.
- Honest fidelity gaps, documented in the skill. Do not silently degrade
Sysmon → kernel burst and still call it Sysmon. Separate question or a hole.
- Keep the existing sensor. EDR, auditd, Postilion, Finacle, Ollama stay.
This is the knock, not a rip-and-replace.
The lake test: if a new operator would scp home a file that grows without
bound, you have failed. Circular overwrite, case prune, 32 KB pull.
3. Anatomy of a skill
Every rmagent skill is five nouns.
GRAIN → SENSOR → DOOR → QUESTIONS → CASE
| | | | |
STAN ring/ETW ssh/winrm ask() fr case
ticket journal local file attest trajectory
PID SPAN tshark-in hops holes.jsonl
principal
3.1 Grain
The unit you follow. One string you can put on a case and query back.
| Plane |
Grain |
Not a grain |
| SecOps |
(host, principal, logonid) |
source IP (NAT lies) |
| Reliability |
ticket / work id |
“the outage” |
| App / APM |
request / trace_id |
“the service” |
| Payments |
(STAN, RRN) [+ TID] |
PAN |
| AgentOps |
(host, agent, session) |
“AI” |
| NetOps |
(flow 5-tuple, window) or (circuit_id) |
“the WAN” |
| MLOps |
(job_id, model, run) |
“the GPU box” |
| DCOps |
(asset_tag, PDU/circuit) |
“the row” |
Rules:
- Grain fields reject
; and = if you encode STC-style (injection).
- Recyclable ids (STAN, PID, LogonId) always pair with a window or a
second key (RRN, start time, ProcessGuid).
- The grain is not the ticket. The ticket is an alias (
rmagent-fr).
3.2 Sensor
Where the bits already are. Prefer resident circular over “start capture.”
| Sensor class |
Examples |
Lake risk |
| OS diary |
Security log, journald, auditd |
export = lake |
| Kernel ring |
ETW AutoLogger, kprobes (not default) |
grow-forever file |
| App ring |
Sysmon, HTTP.sys, .NET EventSource |
ETL copy home |
| Wire ring |
SPAN → decoded JSONL (not pcap) |
pcap-on-disk |
| Disk artifacts |
agent transcripts, unit files |
full transcript |
| Local API |
Ollama /api/ps, kubectl get (capped) |
cluster dump |
| Counter |
flowstats, SNMP ifInOctets |
1-min poll lake |
Decoder lives at ingest, mask at the boundary. rmagent-iso unpacks ISO
in memory, writes PAN-masked JSONL. Never write the sensitive form.
3.3 Door
How the jump host knocks. One skill may have many doors; ask() is door-aware.
| Door |
Typical OS |
Notes |
winrm / psrp |
Windows |
PSRP if payload > ~8191 UTF-16LE chars |
ssh |
Linux/macOS/network-os |
key auth; passwordless sudo only for named reads |
ring |
local file |
questions never call tshark; ingest is a separate process |
http-local |
model servers, kube |
allowlisted paths only |
snmp |
DC / net |
GET/WALK allowlist, not full table dumps |
Doors you do not add as the product: unrestricted exec, RDP, serial as
god-shell, MITM of a TLS link you do not terminate as a broker you own.
3.4 Questions
A closed set. Name them after the English question, not the vendor API.
The canonical six (rename per domain, keep the jobs):
| Job |
Typical name |
Returns |
Must NOT |
| Alive + sighted? |
attest |
up, clock, blind_check, oldest event |
full log |
| Anything odd? |
sketch |
deltas in window (new X, failed Y) |
raw lists |
| Who/what did they touch? |
edges |
capped join-keyed hops |
whole table |
| What changed? |
explain |
identity/config/deploy in window |
whole journal |
| Follow one grain |
trace / *trace |
one reconstructed journey |
every event |
| Localize / slow |
hops / *slow |
per-hop ms vs baseline |
continuous stream |
| Failures |
*fail |
timeouts, mismatches, candidates |
dumps |
| Normal |
*baseline |
p50/p95/p99, n ≥ 30 or hole |
warehouse |
| Persistence / drift |
attackmap / *drift |
state vs last baseline |
filesystem walk |
| Deep (optional, time-boxed) |
*deep |
10s burst, nothing persists |
a new agent |
Add domain questions only when a job above cannot express them.
pslogs, txnfail, agentmodels earned their names. dump_everything did not.
3.5 Case
Reuse rmagent-fr when the human cares about one ticket:
case.py open --title "…" --ticket GRAIN-OR-TICKET --principal <grain-type>
hunt.py … --case-dir <that>
trace.py --ticket …
The ISO/app/agent skill answers. fr records the investigation.
Do not fork case/trajectory/STC unless you must (injection rules stay).
4. Repository layout (clone this)
rmagent-<domain>/
SKILL.md # constitution + this domain's grain/sensor/questions
SAFETY.md # estate, confirm flags, what we will not do
estate.example.yaml # no secrets
EXAMPLES.md # worked questions with expected JSON shape
scripts/
lib.py # ALLOWED, ask(), cap, holes, door, creds
hunt.py # CLI: question names only
case.py # optional; prefer rmagent-fr
stc.py # optional; copy from fr (injection-hardened)
test_<domain>.py # pure-logic; fixtures; no live estate required
test_budget.py # WinRM 8191 if you have .ps1
questions/
windows/*.ps1 # compact, $Track $SinceHours $Limit
linux/*.sh # one bash -c, one JSON
generate_fixture.py # demo rings/answers
ingest.py # door only (tshark/SPAN) — not a question
teardown.py # if you created a ring
examples/rings|answers/ # fixtures committed, PAN/secrets masked
scripts/new_skill.py in this template repo stamps that tree.
5. How to mint a skill (procedure)
python3 ~/.agents/skills/rmagent-template/scripts/new_skill.py \
--name rmagent-net \
--title "Circuit Flight Recorder" \
--grain "circuit_id + time window" \
--sensor "SNMP counters + syslog ring on the collector" \
--plane netops \
--out ~/.agents/skills/rmagent-net
Then, in order (do not skip):
- Fill the one-sentence test in the new
SKILL.md.
- Write the question table (job / returns / must-not) before any payload.
- Implement
ask() allowlist + cap + hole in lib.py.
- One attest with
blind_check — prove sightedness first.
- Fixtures +
test_*.py that never need the live estate.
check_constitution.py against the new skill (must exit 0).
- One worked example with a grain that localizes a hop.
- Document fidelity gaps (TLS, no terminal tap, n<30, ring overwrite).
- Only then: live door against boxes you administer.
Red team for a watch skill is optional (rmagent-redteam pattern): stage
benign prefixed artifacts, score detection, clean, --confirm.
6. Domain cookbooks
Each row is a starter grain/sensor/question set. Specialize; do not copy
all questions into one mega-skill. One grain per skill.
6.1 SecOps — identity-led (rmagent-so / windows / linux)
- Grain: principal + LogonId. Sensor: Security log / journald / Sysmon.
- Questions: attest, sketch, edges, explain, netedges, pslogs, attackmap,
canary, drift. Correlate across witnesses (lateral-hop, shared-logonid).
- Must not: evtx copy, YARA filesystem, memory scan, disable EDR.
- Blind: auditpol / auditd rules. WS2 Failure-only Logon = fake clean.
6.2 Reliability / incident — ticket-led (rmagent-fr)
- Grain: ticket. Sensor: none of its own — joins other skills’ answers.
- Scripts: STC, trajectory DAG, hop index, causal blast radius, OTel emit.
- Honest limit: business must stamp the ticket OR another skill (iso/at)
supplies the grain. fr does not invent payment events.
6.3 App / sysops tracing — Windows ETW (rmagent-at)
- Grain: request-ish (time + PID + URL if present). Sensor: AutoLogger rings.
- Setup is MOP. Questions: apptrace, appslow, apperrors, appnet, appproc.
- Honest: Kernel-Network may be empty on Server 2022; fallback is a named
hole or Sysmon EID3, not a fake quiet box.
6.4 AgentOps (rmagent-ao)
- Grain:
(host, agent, session). Sensor: process + transcripts + local APIs.
- Tiers 1–4; Tier 4 is a hole. Never read API key values.
6.5 Payments / channel (rmagent-iso)
- Grain: STAN+RRN. Sensor: SPAN → decoded circular JSONL.
- Questions: txnattest, txntrace, txnhops, txnslow, txnfail, txnbaseline.
- PAN masked. Segment A/D often holes. DWD is a candidate if EJ sighted.
6.6 NetOps
- Grain:
(src, dst, proto, sport, dport) in a window, or circuit_id /
BGP neighbor. Sensor: interface counters, syslog/BMP, ERSPAN decoded
to flow records (not pcap keep).
- Questions (starter):
netattest (ifUp, error_delta, ntp), netedges
(top talkers capped), netfail (flaps, CRC, BGP down), nethops
(traceroute-like from existing telemetry — do not blast 65k pings),
netbaseline (error/discards p95, n≥30).
- Must not: full
tcpdump -w, MAC table dump of the campus, attacking
ping -f. SPAN design may live in netops skill; rmagent consumes rings.
6.7 DevOps / platform
- Grain:
(deploy_id | git_sha | rollout). Sensor: kube events, systemd
units, CI logs on the runner you administer.
- Questions:
platattest (api-server reachable, node NotReady count),
platsketch (CrashLoop, image-pull-backoff, new ClusterRoleBinding),
plattrace (one ReplicaSet’s events), platfail (failed jobs).
- Must not:
kubectl logs --all-containers --all-namespaces home.
Cap pod names. Secrets in env = names only.
6.8 SysOps / datacenter
- Grain:
(hostname) or (asset_tag, sensor). Sensor: IPMI/Redfish
allowlisted GETs, SMART, PDU SNMP.
- Questions:
dcatest (power, inlet temp, PSU redundancy), dcfail
(predictive fail, UID, lost redundancy), dcbaseline (temp p95).
- Must not: firmware flash, chassis locate as a toy, full SEL export lake.
6.9 MLOps
- Grain:
(job_id, model_name, revision). Sensor: scheduler API, GPU
counters (nvidia-smi -q parsed, capped), artifact mtimes.
- Questions:
mlattest (GPU visible, driver), mlslow (step time vs
baseline), mlfail (OOM, NaN loss if already in the job log),
mldrift (new models on disk).
- Must not: weight files, dataset copies, wandb dumps.
6.10 AIOps (the reasoning layer — do not confuse with a lake)
AIOps in this ethos is thinker.py over census history, not a training
set of every txn.
- Inputs: attest time series, hop latency percentiles, blind_count, holes.
- Outputs: cliff, silence, acceleration, z-score with n ≥ 30 else hole.
- Must not: ship raw events to a model vendor. Must not page on 20 samples.
rmagent-fr thinker / rmagent-iso txnbaseline are the reference.
6.11 Purple team (rmagent-redteam)
- Not a watch plane. Stages prefixed, reversible,
--confirm artifacts,
scores the watch skill, cleans. Never against boxes you do not administer.
7. ask() contract
Every lib.py exposes:
ALLOWED = {"attest", "sketch", ...} # closed
MAX_PULL_BYTES = 32 * 1024
WALK_DEPTH = 8
WALK_FANOUT = 3
COOLDOWN_SEC = 300
def ask(row, question: str, **params) -> dict:
"""row = inventory witness. Returns JSON-ready dict. Never raises secrets."""
Return shapes:
{"ok": true, "witness": "ws1", "question": "attest", "blind_check": "ok", "...": "..."}
{"hole": true, "asked": "edges", "empty": true, "why": "witness-blind"}
{"hole": true, "reason": "not-allowlisted", "question": "actuate"}
{"hole": true, "reason": "capped", "bytes": 90000, "limit": 32768}
Unparseable payload → hole, not ok with a raw blob.
8. Inventory shape
witnesses:
- id: unique-short
name: human
plane: endpoint | network | payments | agent | dc | ml
os: windows | linux | darwin | iosxe | other
door: winrm | ssh | ring | http-local | snmp
address: 10.0.0.1 # never a secret
user: deploy # never a password
skills: [attest, sketch] # subset of ALLOWED
track: [root] # principals or grain types
optional: false
policy:
max_pull_bytes: 32768
baseline_min_n: 30
actuate: false
Credentials: RMAgent_<ID>_USER / _PASS or vault. Never this file.
9. Anti-patterns (reject at review)
| Smell |
Why it fails |
| “We’ll just keep 7 days of pcap” |
lake |
Question named run / exec / query |
god-shell |
| Silent fallback with worse fidelity |
liar |
PAN / key / prompt in case.json |
constitution 11 |
| Baseline on n=8 called “normal” |
noise as science |
| Tight retry on WinRM timeout |
lockout |
| Skill that is “all of IT ops” |
no grain |
| MITM production TLS “to see ISO” |
not your broker |
| Copy-paste so.ps1 into netops |
wrong grain |
| Dashboard of every txn in real time |
warehouse with extra steps |
| Install an agent “just this once” |
you are now the EDR |
10. Review checklist (check_constitution.py)
The linter looks for:
Exit 0 = shippable as a template clone. Live-validation is still on you.
11. Worked clone (30 minutes) — rmagent-net sketch
One-sentence: Follow one circuit_id across PE routers without a NetFlow
warehouse, by asking netattest/netfail/netbaseline on boxes we administer.
Questions:
| Q |
Returns |
Must not |
| netattest |
ifOperStatus, crc_delta, ntp, blind (SNMP timeout) |
full IF-MIB |
| netfail |
flaps in window, BGP neighbor down (from syslog ring) |
debug log |
| netbaseline |
p95 error-delta; n<30 hole |
95th of a lake |
Sensor: already-running syslog JSONL on the collector (circular 64 MiB) +
SNMP GET of 6 OIDs. No tcpdump.
That is a skill. “Also add DPI and a year of pcap” is not.
12. Relationship to the family
| Skill |
You clone from here when … |
| so / linux / windows |
identity plane |
| fr |
you need the case/ticket tape |
| at |
Windows app ETW |
| ao |
LLM agents on a box |
| iso |
ISO 8583 / FEP |
| redteam |
proving a watch skill |
| template (this) |
any new plane |
RTerm bundles this skill so an agent that is asked “write rmagent-foo”
loads this first, not a SIEM tutorial.
13. Scripts in this directory
| Script |
Job |
scripts/new_skill.py |
stamp a new skill tree from templates |
scripts/check_constitution.py |
lint a skill dir |
scripts/lib_skeleton.py |
copy-me ask() / cap / hole |
templates/SKILL.md.tmpl |
SKILL stub with constitution |
templates/estate.example.yaml |
inventory stub |
examples/question-table.md |
blank question table |
CONSTITUTION.md |
printable 14 rules |
COOKBOOK.md |
domain grain/sensor map (expanded) |
Supporting files
Skill directory: /Users/olu/.agents/skills/rmagent-template
1---2name: rmagent-template3description: Lake-less, on-device RMAgent skill constitution and factory. Use when creating a new rmagent-* skill for any IT-operations plane (secops, devops, netops, sysops, datacenterops, mlops, aiops, payments, agentops, app tracing) — or when auditing an existing one against the no-lake ethos. Clone this, pick a grain and a sensor, write allowlisted questions, keep holes honest. Bundled with RTerm as the reference for every future rmagent skill.4---56# rmagent-template — the lake-less skill factory78This is **not** a witness. It is the **constitution + scaffold** every9`rmagent-*` skill is cloned from.1011Use it when you are about to write a new skill that:1213- watches something on boxes (or wires, or rings) you administer14- must **not** become a SIEM / PCAP lake / span warehouse15- must answer a **named question** on demand, capped, with holes1617Do **not** use it as a runtime hunt. Load `rmagent-so`, `rmagent-iso`,18`rmagent-at`, … for that. This file is the pattern those skills already share.1920> Push tools answer the questions you knew to ask, after the log rotated.21> An rmagent skill answers the question you just thought of — by knocking22> on the device that still has the ring.2324---2526## 1. The one-sentence test2728Before you name the skill, finish this sentence:2930> Follow **one ________** across **________** without a ________ warehouse,31> by asking **________** on devices we administer.3233| Existing skill | Follow one … | across … | without a … warehouse | by asking … |34|---|---|---|---|---|35| `rmagent-so` | principal (Admin/SYSTEM) | Windows estate | Security.evtx | attest/sketch/edges/… |36| `rmagent-linux` | principal (root) | Linux/macOS hosts | journal export | attest/sketch/edges/… |37| `rmagent-fr` | work id / ticket | hops of an investigation | span lake | case/trace/trajectory |38| `rmagent-at` | application request | Windows ETW rings | ETL dump | apptrace/appslow/… |39| `rmagent-ao` | agent process | disk artifacts + LLM APIs | transcript dump | agents/agenttrace/… |40| `rmagent-iso` | STAN+RRN | ISO taps Postilion↔Finacle | PCAP lake | txntrace/txnhops/… |41| `rmagent-redteam` | drill artifact | WS1/WS2 | (stages then scores so) | stage/score/clean |4243If you cannot fill the blanks, you do not have an rmagent skill yet. You have44a dashboard idea.4546---4748## 2. Constitution (non-negotiable)4950Copy this block into every new `SKILL.md`. Delete nothing. Annotate domain51exceptions **below** it, never by editing it away.52531. **Watch only (Phase 0).** No `actuate` in the question set. Isolate /54 disable / restart / `iptables` / `usermod` live in `rmagent-actuate` (or55 the platform’s MOP), dual-controlled, journaled, reversible.562. **Allowlisted questions only.** `ask()` refuses unknown names and refuses57 `actuate`. Never an arbitrary remote shell as the product.583. **Capped answers (32 KB default).** Oversized → **hole**, not a truncated59 lie and not a lake. Prefer a **signal-aware cap** (keep critical rows,60 shed low-signal) over a bigger cap.614. **A hole is an answer.** Silent box, blind witness, retention-boundary,62 unsupported field, TLS you cannot terminate, box you do not administer —63 same JSON shape as a hop: `{asked, empty, why}`.645. **Never trust “no findings” until the witness can see.** Every plane needs65 an `*attest` with `blind_check`. Empty + blind = false negative, the worst66 failure mode.676. **On-device rings, not shipped lakes.** Data stays where it was written68 (ETW, journald, circular JSONL, Sysmon). The jump host stores **cases**69 (megabytes: path, holes, trajectory), not event exports.707. **Context propagates; data does not.** STC / grain ids travel. Event bodies71 do not. Depth ≤ 8, fan-out ≤ 3 — an unconstrained hunter is a worm.728. **Credentials never in the inventory, skill, or case.** Env /73 `~/.rmagent/creds.json` (mode 600) / vault. Never print them.749. **Your estate only.** Partner, scheme, SaaS you do not tenant, phone, NIBSS75 — hole, not a witness.7610. **No tight retry on a silent host.** Cooldown. Two missed attests = Critical.7711. **Secrets, PAN, payloads, prompt text — never stored unless the grain78 requires a *masked* form.** Env **names** not values. PAN `first6******last4`.79 Transcripts capped excerpts. PIN blocks never.8012. **Reversible persistent change.** If you create a ring (AutoLogger, SPAN81 session, analytic log), it is **MOP**, dry-run default, `--apply` required,82 `--teardown` exists and is tested.8313. **Honest fidelity gaps, documented in the skill.** Do not silently degrade84 Sysmon → kernel burst and still call it Sysmon. Separate question or a hole.8514. **Keep the existing sensor.** EDR, auditd, Postilion, Finacle, Ollama stay.86 This is the knock, not a rip-and-replace.8788**The lake test:** if a new operator would `scp` home a file that grows without89bound, you have failed. Circular overwrite, case prune, 32 KB pull.9091---9293## 3. Anatomy of a skill9495Every rmagent skill is five nouns.9697```98GRAIN → SENSOR → DOOR → QUESTIONS → CASE99 | | | | |100 STAN ring/ETW ssh/winrm ask() fr case101 ticket journal local file attest trajectory102 PID SPAN tshark-in hops holes.jsonl103 principal104```105106### 3.1 Grain107108The **unit you follow**. One string you can put on a case and query back.109110| Plane | Grain | Not a grain |111|---|---|---|112| SecOps | `(host, principal, logonid)` | source IP (NAT lies) |113| Reliability | ticket / work id | “the outage” |114| App / APM | request / trace_id | “the service” |115| Payments | `(STAN, RRN)` [+ TID] | PAN |116| AgentOps | `(host, agent, session)` | “AI” |117| NetOps | `(flow 5-tuple, window)` or `(circuit_id)` | “the WAN” |118| MLOps | `(job_id, model, run)` | “the GPU box” |119| DCOps | `(asset_tag, PDU/circuit)` | “the row” |120121Rules:122123- Grain fields **reject** `;` and `=` if you encode STC-style (injection).124- Recyclable ids (STAN, PID, LogonId) **always** pair with a window or a125 second key (RRN, start time, ProcessGuid).126- The grain is **not** the ticket. The ticket is an alias (`rmagent-fr`).127128### 3.2 Sensor129130Where the bits already are. Prefer **resident circular** over “start capture.”131132| Sensor class | Examples | Lake risk |133|---|---|---|134| OS diary | Security log, journald, auditd | export = lake |135| Kernel ring | ETW AutoLogger, kprobes (not default) | grow-forever file |136| App ring | Sysmon, HTTP.sys, .NET EventSource | ETL copy home |137| Wire ring | SPAN → decoded JSONL (not pcap) | pcap-on-disk |138| Disk artifacts | agent transcripts, unit files | full transcript |139| Local API | Ollama `/api/ps`, `kubectl get` (capped) | cluster dump |140| Counter | `flowstats`, SNMP ifInOctets | 1-min poll lake |141142**Decoder lives at ingest, mask at the boundary.** `rmagent-iso` unpacks ISO143in memory, writes PAN-masked JSONL. Never write the sensitive form.144145### 3.3 Door146147How the jump host knocks. One skill may have many doors; `ask()` is door-aware.148149| Door | Typical OS | Notes |150|---|---|---|151| `winrm` / `psrp` | Windows | PSRP if payload > ~8191 UTF-16LE chars |152| `ssh` | Linux/macOS/network-os | key auth; passwordless sudo **only** for named reads |153| `ring` | local file | questions never call `tshark`; ingest is a separate process |154| `http-local` | model servers, kube | allowlisted paths only |155| `snmp` | DC / net | GET/WALK allowlist, not full table dumps |156157Doors you **do not** add as the product: unrestricted `exec`, RDP, serial as158god-shell, MITM of a TLS link you do not terminate as a broker you own.159160### 3.4 Questions161162A **closed set**. Name them after the English question, not the vendor API.163164The canonical six (rename per domain, keep the *jobs*):165166| Job | Typical name | Returns | Must NOT |167|---|---|---|---|168| Alive + sighted? | `attest` | up, clock, blind_check, oldest event | full log |169| Anything odd? | `sketch` | deltas in window (new X, failed Y) | raw lists |170| Who/what did they touch? | `edges` | capped join-keyed hops | whole table |171| What changed? | `explain` | identity/config/deploy in window | whole journal |172| Follow one grain | `trace` / `*trace` | one reconstructed journey | every event |173| Localize / slow | `hops` / `*slow` | per-hop ms vs baseline | continuous stream |174| Failures | `*fail` | timeouts, mismatches, candidates | dumps |175| Normal | `*baseline` | p50/p95/p99, **n ≥ 30 or hole** | warehouse |176| Persistence / drift | `attackmap` / `*drift` | state vs last baseline | filesystem walk |177| Deep (optional, time-boxed) | `*deep` | 10s burst, nothing persists | a new agent |178179Add domain questions **only** when a job above cannot express them.180`pslogs`, `txnfail`, `agentmodels` earned their names. `dump_everything` did not.181182### 3.5 Case183184Reuse `rmagent-fr` when the human cares about **one ticket**:185186```187case.py open --title "…" --ticket GRAIN-OR-TICKET --principal <grain-type>188hunt.py … --case-dir <that>189trace.py --ticket …190```191192The ISO/app/agent skill **answers**. fr **records the investigation**.193Do not fork case/trajectory/STC unless you must (injection rules stay).194195---196197## 4. Repository layout (clone this)198199```200rmagent-<domain>/201 SKILL.md # constitution + this domain's grain/sensor/questions202 SAFETY.md # estate, confirm flags, what we will not do203 estate.example.yaml # no secrets204 EXAMPLES.md # worked questions with expected JSON shape205 scripts/206 lib.py # ALLOWED, ask(), cap, holes, door, creds207 hunt.py # CLI: question names only208 case.py # optional; prefer rmagent-fr209 stc.py # optional; copy from fr (injection-hardened)210 test_<domain>.py # pure-logic; fixtures; no live estate required211 test_budget.py # WinRM 8191 if you have .ps1212 questions/213 windows/*.ps1 # compact, $Track $SinceHours $Limit214 linux/*.sh # one bash -c, one JSON215 generate_fixture.py # demo rings/answers216 ingest.py # door only (tshark/SPAN) — not a question217 teardown.py # if you created a ring218 examples/rings|answers/ # fixtures committed, PAN/secrets masked219```220221`scripts/new_skill.py` in **this** template repo stamps that tree.222223---224225## 5. How to mint a skill (procedure)226227```bash228python3 ~/.agents/skills/rmagent-template/scripts/new_skill.py \229 --name rmagent-net \230 --title "Circuit Flight Recorder" \231 --grain "circuit_id + time window" \232 --sensor "SNMP counters + syslog ring on the collector" \233 --plane netops \234 --out ~/.agents/skills/rmagent-net235```236237Then, in order (do not skip):2382391. Fill the **one-sentence test** in the new `SKILL.md`.2402. Write the **question table** (job / returns / must-not) before any payload.2413. Implement `ask()` allowlist + cap + hole in `lib.py`.2424. One **attest** with `blind_check` — prove sightedness first.2435. Fixtures + `test_*.py` that never need the live estate.2446. `check_constitution.py` against the new skill (must exit 0).2457. One **worked example** with a grain that localizes a hop.2468. Document fidelity gaps (TLS, no terminal tap, n<30, ring overwrite).2479. Only then: live door against boxes you administer.248249**Red team for a watch skill** is optional (`rmagent-redteam` pattern): stage250benign prefixed artifacts, score detection, clean, `--confirm`.251252---253254## 6. Domain cookbooks255256Each row is a *starter* grain/sensor/question set. Specialize; do not copy257all questions into one mega-skill. **One grain per skill.**258259### 6.1 SecOps — identity-led (`rmagent-so` / `windows` / `linux`)260261- Grain: principal + LogonId. Sensor: Security log / journald / Sysmon.262- Questions: attest, sketch, edges, explain, netedges, pslogs, attackmap,263 canary, drift. Correlate **across** witnesses (lateral-hop, shared-logonid).264- Must not: evtx copy, YARA filesystem, memory scan, disable EDR.265- Blind: auditpol / auditd rules. WS2 Failure-only Logon = fake clean.266267### 6.2 Reliability / incident — ticket-led (`rmagent-fr`)268269- Grain: ticket. Sensor: none of its own — joins other skills’ answers.270- Scripts: STC, trajectory DAG, hop index, causal blast radius, OTel emit.271- Honest limit: **business must stamp the ticket** OR another skill (iso/at)272 supplies the grain. fr does not invent payment events.273274### 6.3 App / sysops tracing — Windows ETW (`rmagent-at`)275276- Grain: request-ish (time + PID + URL if present). Sensor: AutoLogger rings.277- Setup is **MOP**. Questions: apptrace, appslow, apperrors, appnet, appproc.278- Honest: Kernel-Network may be empty on Server 2022; fallback is a **named**279 hole or Sysmon EID3, not a fake quiet box.280281### 6.4 AgentOps (`rmagent-ao`)282283- Grain: `(host, agent, session)`. Sensor: process + transcripts + local APIs.284- Tiers 1–4; Tier 4 is a hole. Never read API key **values**.285286### 6.5 Payments / channel (`rmagent-iso`)287288- Grain: STAN+RRN. Sensor: SPAN → decoded circular JSONL.289- Questions: txnattest, txntrace, txnhops, txnslow, txnfail, txnbaseline.290- PAN masked. Segment A/D often holes. DWD is a **candidate** if EJ sighted.291292### 6.6 NetOps293294- Grain: `(src, dst, proto, sport, dport)` in a window, or `circuit_id` /295 BGP neighbor. Sensor: interface counters, syslog/BMP, ERSPAN **decoded**296 to flow records (not pcap keep).297- Questions (starter): `netattest` (ifUp, error_delta, ntp), `netedges`298 (top talkers capped), `netfail` (flaps, CRC, BGP down), `nethops`299 (traceroute-like from existing telemetry — do not blast 65k pings),300 `netbaseline` (error/discards p95, n≥30).301- Must not: full `tcpdump -w`, MAC table dump of the campus, attacking302 `ping -f`. SPAN design may live in `netops` skill; rmagent **consumes rings**.303304### 6.7 DevOps / platform305306- Grain: `(deploy_id | git_sha | rollout)`. Sensor: kube events, systemd307 units, CI logs **on the runner you administer**.308- Questions: `platattest` (api-server reachable, node NotReady count),309 `platsketch` (CrashLoop, image-pull-backoff, new ClusterRoleBinding),310 `plattrace` (one ReplicaSet’s events), `platfail` (failed jobs).311- Must not: `kubectl logs --all-containers --all-namespaces` home.312 Cap pod names. Secrets in env = names only.313314### 6.8 SysOps / datacenter315316- Grain: `(hostname)` or `(asset_tag, sensor)`. Sensor: IPMI/Redfish317 **allowlisted GETs**, SMART, PDU SNMP.318- Questions: `dcatest` (power, inlet temp, PSU redundancy), `dcfail`319 (predictive fail, UID, lost redundancy), `dcbaseline` (temp p95).320- Must not: firmware flash, chassis locate as a toy, full SEL export lake.321322### 6.9 MLOps323324- Grain: `(job_id, model_name, revision)`. Sensor: scheduler API, GPU325 counters (`nvidia-smi -q` parsed, capped), artifact mtimes.326- Questions: `mlattest` (GPU visible, driver), `mlslow` (step time vs327 baseline), `mlfail` (OOM, NaN loss **if already in the job log**),328 `mldrift` (new models on disk).329- Must not: weight files, dataset copies, wandb dumps.330331### 6.10 AIOps (the reasoning layer — do not confuse with a lake)332333AIOps in this ethos is **`thinker.py` over census history**, not a training334set of every txn.335336- Inputs: attest time series, hop latency percentiles, blind_count, holes.337- Outputs: cliff, silence, acceleration, z-score with **n ≥ 30** else hole.338- Must not: ship raw events to a model vendor. Must not page on 20 samples.339340`rmagent-fr` thinker / `rmagent-iso` txnbaseline are the reference.341342### 6.11 Purple team (`rmagent-redteam`)343344- Not a watch plane. Stages **prefixed, reversible, `--confirm`** artifacts,345 scores the watch skill, cleans. Never against boxes you do not administer.346347---348349## 7. `ask()` contract350351Every `lib.py` exposes:352353```python354ALLOWED = {"attest", "sketch", ...} # closed355MAX_PULL_BYTES = 32 * 1024356WALK_DEPTH = 8357WALK_FANOUT = 3358COOLDOWN_SEC = 300359360def ask(row, question: str, **params) -> dict:361 """row = inventory witness. Returns JSON-ready dict. Never raises secrets."""362```363364Return shapes:365366```json367{"ok": true, "witness": "ws1", "question": "attest", "blind_check": "ok", "...": "..."}368{"hole": true, "asked": "edges", "empty": true, "why": "witness-blind"}369{"hole": true, "reason": "not-allowlisted", "question": "actuate"}370{"hole": true, "reason": "capped", "bytes": 90000, "limit": 32768}371```372373Unparseable payload → hole, not `ok` with a raw blob.374375---376377## 8. Inventory shape378379```yaml380witnesses:381 - id: unique-short382 name: human383 plane: endpoint | network | payments | agent | dc | ml384 os: windows | linux | darwin | iosxe | other385 door: winrm | ssh | ring | http-local | snmp386 address: 10.0.0.1 # never a secret387 user: deploy # never a password388 skills: [attest, sketch] # subset of ALLOWED389 track: [root] # principals or grain types390 optional: false391policy:392 max_pull_bytes: 32768393 baseline_min_n: 30394 actuate: false395```396397Credentials: `RMAgent_<ID>_USER` / `_PASS` or vault. **Never** this file.398399---400401## 9. Anti-patterns (reject at review)402403| Smell | Why it fails |404|---|---|405| “We’ll just keep 7 days of pcap” | lake |406| Question named `run` / `exec` / `query` | god-shell |407| Silent fallback with worse fidelity | liar |408| PAN / key / prompt in `case.json` | constitution 11 |409| Baseline on n=8 called “normal” | noise as science |410| Tight retry on WinRM timeout | lockout |411| Skill that is “all of IT ops” | no grain |412| MITM production TLS “to see ISO” | not your broker |413| Copy-paste so.ps1 into netops | wrong grain |414| Dashboard of every txn in real time | warehouse with extra steps |415| Install an agent “just this once” | you are now the EDR |416417---418419## 10. Review checklist (`check_constitution.py`)420421The linter looks for:422423- [ ] `SKILL.md` has the constitution numbered list (or `# Constitution`)424- [ ] `ask(` / `ALLOWED` in `scripts/lib.py`425- [ ] `"actuate"` rejected (test or explicit)426- [ ] `MAX_PULL_BYTES` or `32 * 1024`427- [ ] `hole` string appears in lib or questions428- [ ] `blind` or `attest` question exists429- [ ] no `password:` in yaml examples430- [ ] `test_*.py` exists431- [ ] `EXAMPLES.md` or `examples/` exists432- [ ] if `.ps1`: budget test or a comment about 8191433- [ ] if ingest/SPAN/AutoLogger: `teardown` or MOP wording in SKILL.md434435Exit 0 = shippable as a *template clone*. Live-validation is still on you.436437---438439## 11. Worked clone (30 minutes) — `rmagent-net` sketch440441One-sentence: *Follow one circuit_id across PE routers without a NetFlow442warehouse, by asking netattest/netfail/netbaseline on boxes we administer.*443444Questions:445446| Q | Returns | Must not |447|---|---|---|448| netattest | ifOperStatus, crc_delta, ntp, blind (SNMP timeout) | full IF-MIB |449| netfail | flaps in window, BGP neighbor down (from syslog ring) | debug log |450| netbaseline | p95 error-delta; n<30 hole | 95th of a lake |451452Sensor: already-running syslog JSONL on the collector (circular 64 MiB) +453SNMP GET of 6 OIDs. No `tcpdump`.454455That is a skill. “Also add DPI and a year of pcap” is not.456457---458459## 12. Relationship to the family460461| Skill | You clone from here when … |462|---|---|463| so / linux / windows | identity plane |464| fr | you need the case/ticket tape |465| at | Windows app ETW |466| ao | LLM agents on a box |467| iso | ISO 8583 / FEP |468| redteam | proving a watch skill |469| **template (this)** | **any new plane** |470471RTerm bundles this skill so an agent that is asked “write rmagent-foo”472loads **this** first, not a SIEM tutorial.473474---475476## 13. Scripts in this directory477478| Script | Job |479|---|---|480| `scripts/new_skill.py` | stamp a new skill tree from templates |481| `scripts/check_constitution.py` | lint a skill dir |482| `scripts/lib_skeleton.py` | copy-me `ask()` / cap / hole |483| `templates/SKILL.md.tmpl` | SKILL stub with constitution |484| `templates/estate.example.yaml` | inventory stub |485| `examples/question-table.md` | blank question table |486| `CONSTITUTION.md` | printable 14 rules |487| `COOKBOOK.md` | domain grain/sensor map (expanded) |488489## Supporting files490491Skill directory: `/Users/olu/.agents/skills/rmagent-template`