- Python 88.7%
- Shell 4.9%
- Ruby 3.6%
- Makefile 2.8%
|
|
||
|---|---|---|
| .ward | ||
| docs | ||
| outputs | ||
| parsers | ||
| scripts | ||
| .agentic-os.toml | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| AGENTS.md | ||
| battery.yml | ||
| Brewfile | ||
| CLAUDE.md | ||
| Makefile | ||
| README.md | ||
otel-a2a-relay-debug
An o2r-focused, localhost-only network debugger. The point of this repo is not the probe battery - it is the o2r channel. You run a debugging session, every step posts a clean anonymized diff to an otel-a2a-relay Agent Channel, and a human or another agent watches the channel to see what the debugging looks like.
Status: v0 scaffold. The Python transform/diff core is unit-tested. The ping and kubectl probes are verified end-to-end against a live relay; the full battery is not.
Mental model
- Every session is a
(source, target)tuple. Localhost-only:sourceis the node running the probes,targetis the tailnet host under debug. Outputs are keyedoutputs/<source>__<target>/. - o2r is the crux. The channel carries the story. It never sees a timestamp, a line number, or an IP.
- STEP is not a choice. Every probe runs through
scripts/step.py. There is no raw-probe path, because a raw probe is un-anonymized and would leak into the channel.
Capture once, parse thrice
A command runs exactly once. scripts/step.py saves the capture verbatim, then derives three tiers by parsing the saved capture - it never re-runs the command. Parsers are pure functions over saved raw, so fixing a parser re-derives the tiers without re-probing.
| tier | scrubbed | committed | destination |
|---|---|---|---|
| raw | nothing | no (gitignored) | raw/NNNN.{cmd,out}, saved every run |
| normalized | timestamps, RTT, volatile fields | no (gitignored) | normalized/*.json - machine diff substrate, local only (still holds real IPs/names) |
| public | normalized + IPs, FQDNs, names, ids → placeholders | yes | public/*.yml - human-read + the only thing posted to o2r |
Two-file retention per tier: baseline (last blessed known-good) + current (under debug). scripts/bless.py promotes current → baseline. Only the scrubbed public tier is committed.
Each committed public/*.yml is a result: payload under one line of context: purpose:, the probe's intent. Source, target, probe, tier, and baseline-vs-current already live in the path (outputs/<src>__<tgt>/<probe>/public/<baseline|current>.yml), so they are not duplicated into the file. The purpose string lives once in parsers/_registry.yml (desc per command-id) and is stamped in at capture time; it is constant across baseline/current, so only result drives the diff.
The parser layer is a manual-first gate
Each command-id has a parser at parsers/<id>.yml defining its normalize and anonymize rules. jc is the default normalizer. Commands jc has no parser for declare normalize.text: true instead. The first run of a new command has no parser, so STEP halts and you hand-author the rule, commit it, then re-run. STEP refuses to post un-parsed output - fail-closed, because un-parsed means un-anonymized.
Usage
export DBG_SOURCE=kais-macbook-pro DBG_TARGET=kai-server
scripts/session.py start # creates the o2r channel, seeds the charter
scripts/step.py ping -- ping -c3 <host> # capture once, parse thrice, post public diff
scripts/bless.py ping # promote current -> baseline when healthy
scripts/session.py close
Or sweep the whole battery: scripts/battery.py (name ids to run a subset). Setup and deps are in docs/dependencies.md.
See also
- AGENTS.md - agent operating context for this repo.
- docs/FEATURES.md - inventory of what ships today.
- docs/o2r-coupling.md - design note: o2r is the witness layer, not the runtime critical path.
- .ward/ward.yaml - allowlisted dev commands (ward-gated, not coily).
Catalog-trifecta convention from coilysiren/agentic-os#59.