bundlewrap/docs/superpowers/specs/2026-05-10-agent-friendliness-design.md
CroneKorkN 8ec99db7d3
add agent-friendliness design spec
Brainstormed design for making this BundleWrap repo legible to agents:
root AGENTS.md + per-area docs + per-bundle template, with a focused
docs/agents/bundlewrap/ folder covering items.md and metadata.md as
the hard parts. Read-only bw command envelope and an after-change
runbook keyed by what was edited.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:04:36 +02:00

386 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Agent-friendly repo — design
Date: 2026-05-10
## 1. Goals & non-goals
**Goal.** Make this BundleWrap config repo legible to agents (and humans) so an
agent can land useful work — adding/modifying a bundle, configuring a node,
running read-only `bw` introspection, doing a cross-cutting refactor — without
spelunking and without unsafe side effects.
**In scope.**
- Root entry point: `AGENTS.md` (with `CLAUDE.md` as a symlink to it).
- Per-area `AGENTS.md` for `bundles/`, `nodes/`, `groups/`, `libs/`, `hooks/`,
`data/`, `items/`, `bin/`. Mechanism-focused; no enumeration of contents.
- Per-bundle `AGENTS.md`: one balanced doc per bundle, replacing existing
bundle `README.md` files. Template provided.
- `docs/agents/conventions.md`: repo-specific idioms (vault magic strings,
custom bundlewrap fork, files-not-to-touch).
- `docs/agents/commands.md`: read-only `bw` command allowlist and an
after-change runbook keyed by what was edited.
- `docs/agents/bundlewrap/`: a focused folder explaining bundlewrap-as-used-here.
Three files at first: `README.md`, `items.md`, `metadata.md`.
- A docstring/header pass on `libs/*.py`, `hooks/*.py`, `bin/*` so each
individual file self-describes.
- Phase 2 seed: per-bundle `AGENTS.md` for 10 bundles selected empirically.
**Out of scope (explicitly).**
- No tooling changes: no `bw` wrapper, no Makefile, no lint, no CI.
- No code refactoring, renaming, or splitting bundles.
- No mass-fill of all 103 bundles' `AGENTS.md` up front. Phase 3 is leave-as-you-go.
- The root `README.md` (personal TODO list) stays untouched.
- Contributing `AGENTS.md` upstream to bundlewrap is acknowledged but out of
scope for this work.
## 2. Information architecture
```
ckn-bw/
├── AGENTS.md # root entry point (agents + humans)
├── CLAUDE.md # → symlink to AGENTS.md
├── README.md # untouched (personal TODO)
├── docs/
│ └── agents/
│ ├── conventions.md
│ ├── commands.md
│ └── bundlewrap/
│ ├── README.md
│ ├── items.md
│ └── metadata.md
├── bundles/
│ ├── AGENTS.md # what bundles are, how they compose
│ ├── AGENTS.template.md # template for per-bundle docs
│ └── <bundle>/
│ └── AGENTS.md # per-bundle doc (replaces existing READMEs)
├── nodes/
│ └── AGENTS.md
├── groups/
│ └── AGENTS.md
├── libs/ AGENTS.md
├── hooks/ AGENTS.md
├── data/ AGENTS.md
├── items/ AGENTS.md
└── bin/ AGENTS.md
```
**Reading order an agent should follow.** Root `AGENTS.md` → relevant area
`AGENTS.md` → specific `bundles/<x>/AGENTS.md``docs/agents/conventions.md`
or `docs/agents/bundlewrap/<file>` only when something non-obvious comes up.
**Per-area files (not just root).** An agent editing `bundles/nextcloud/items.py`
already has `bundles/AGENTS.md` and `bundles/nextcloud/AGENTS.md` adjacent in
the file tree. Locality of reference matters more than centralization.
**Existing per-bundle `README.md` files (~10 of them).** Folded into the new
`AGENTS.md` and removed in the same change, so there is exactly one doc per
bundle.
## 3. Per-bundle `AGENTS.md` template
One balanced doc serving both audiences. Prose where prose helps, structure
where structure helps. Sections in order:
````markdown
# <bundle-name>
<13 sentences: what this bundle does and when you'd use it.>
## Usage
<How to apply: which group(s) typically include it, or how a node opts in.
Minimal example of node metadata if any keys are required.>
## Metadata
Keys read from `node.metadata`:
```python
{
'nextcloud': {
'domain': 'nc.example.com', # str, required — public hostname
'admin_user': 'admin', # str, default 'admin'
'apps': [], # list[str], default [] — apps to enable
'preview': {
'enabled': True, # bool, default True
},
},
}
```
## Produces
<Brief list of items created: files, services, packages, users, etc.
One line each. Skip if trivially obvious from items.py.>
## Depends on
<Other bundles required, or "none". Note ordering quirks if any.>
## Gotchas
<Non-obvious behavior, manual steps, known pitfalls. Omit the section if none.>
````
**Design choices.**
- Free-form prose at the top (purpose, usage) keeps it readable for humans skimming.
- Metadata is a Python dict literal — matches how `metadata.py` actually looks,
shows nested structure at a glance. Trailing comment per leaf carries type,
required/default, and a short note.
- `Produces` and `Gotchas` are optional — skip when there is nothing useful to say.
- No version/changelog/author fields — git already covers that.
## 4. Root `AGENTS.md` content
Target ~150 lines. Sections in order:
1. **What this repo is.** 23 sentences. BundleWrap config-management for
personal/family infra (~22 nodes), Python-defined nodes/groups/bundles,
applies to real machines.
2. **Quickstart for agents.** Five bullets, the operating envelope:
- Default to read-only `bw` commands; never `bw apply`/`bw run`/`bw lock`
without explicit user request. See `docs/agents/commands.md`.
- Never paste/echo decrypted secret values; respect the demagify magic-string
convention. See `docs/agents/conventions.md`.
- Do not modify `.secrets.cfg*`, `.venv`, `.cache`, `.bw_debug_history`,
`.envrc`. Everything else is editable, but treat `hooks/` and `items/`
(custom item types) with extra care — they affect bw's behavior or item
resolution across the whole repo.
- Uses a custom **bundlewrap fork**, not upstream — check
`docs/agents/conventions.md` before assuming upstream behavior.
- Prefer adding helpers to `libs/` over duplicating logic across bundles.
3. **Layout map.** Terse, link-rich. One line per top-level dir, each linking
to that area's `AGENTS.md`.
4. **How nodes/groups/bundles fit together.** 510 lines: nodes pick up bundles
via groups; metadata flows from groups → node → metadata processors;
`nodes.py` and `groups.py` (root) are the loaders that walk the dirs and
run `demagify`.
5. **Conventions you must know.** One-line summary + link for each:
- `docs/agents/bundlewrap/README.md` — read first if new to bundlewrap.
`items.md` and `metadata.md` are deep dives for the hard parts.
- `docs/agents/conventions.md#secrets` — secrets / demagify magic strings.
- `docs/agents/conventions.md#fork` — custom bundlewrap fork.
- `docs/agents/conventions.md#groups` — group inheritance order.
- `docs/agents/commands.md` — safe `bw` commands and after-change checks.
- Lib helpers — see top-of-file docstrings in `libs/*.py`.
6. **Where to look for examples.** Pointers to a small bundle, a complex
bundle, and a node file.
7. **Where this doc lives.** Note that `CLAUDE.md` is a symlink to this file.
## 5. Per-area `AGENTS.md` content
**Rule.** An area `AGENTS.md` describes how that area works — mechanisms,
conventions, how to add or modify. It does **not** enumerate contents.
Specifics live with the thing itself (docstrings, top-of-file comments) or
in a per-subdir `AGENTS.md` only when that subdir has its own non-obvious
conventions.
**Corollary.** Every `libs/*.py` and `hooks/*.py` starts with a one-line
module docstring. Every `bin/*` script starts with a `# purpose:` header
comment. Discovery is by `ls` + reading those headers, not by an index page.
Each area `AGENTS.md` has the same five-section shape (target 3080 lines):
1. What's in this directory (one paragraph).
2. Conventions (naming, file structure, what each kind of file does).
3. How to add / modify (concrete steps for the most common change).
4. Pitfalls (area-specific gotchas).
5. See also (links to relevant `docs/agents/*` and example files).
Per-area specifics:
- **`bundles/AGENTS.md`** — bundle anatomy (`items.py`, `metadata.py`, `files/`,
`templates/`), where helpers go, when to extract to `libs/`. Links out to
`docs/agents/bundlewrap/items.md` and `metadata.md` for language-level detail.
- **`nodes/AGENTS.md`** — `eval()` loading mechanism via `nodes.py`, demagify
magic-string syntax, naming convention pattern (`<location>.<role>.py`).
**Pitfall:** because node files are `eval()`'d, no top-level imports — only
expression-level constructs.
- **`groups/AGENTS.md`** — same `eval()` mechanism via `groups.py`, subdir
purpose convention (`applications/`, `locations/`, `machine/`, `os/`),
how `all.py` interacts, group-membership rules, inheritance order.
- **`libs/AGENTS.md`** — what libs are (importable from bundles via
`repo.libs.<x>`), conventions for adding a helper, contribution rule
(one-line module docstring required).
- **`hooks/AGENTS.md`** — bw hook lifecycle, when each event fires, how to
write a hook.
- **`data/AGENTS.md`** — what `data/` is for (data sources templated and
consumed by bundles), conventions for adding a new data source.
- **`items/AGENTS.md`** — what custom item types are, how to write one,
when to use a custom item type vs a `file` item.
- **`bin/AGENTS.md`** — what `bin/` is for (operator tooling, not invoked by
bundlewrap).
## 6. `docs/agents/` content
### `conventions.md` (~80120 lines)
- **Secrets / demagify.** `!password_for:`, `!decrypt:`, `!decrypt_file:`,
`!32_random_bytes_as_base64_for:`. What each does, where they're allowed
(node files, evaluated through `nodes.py`), why agents must never echo
decrypted values.
- **Custom bundlewrap fork.** How it's installed
(`pip install --editable git+file:///…/bundlewrap-fork@main`), implications
(don't assume upstream-only behavior), pointer to the fork source.
- **Group inheritance order** & how metadata merges
(`all.py` → location → os → machine → applications → node).
- **Naming conventions** for nodes (`<location>.<role>.py`) and groups
(subdir purpose).
- **Files agents must not modify.** `.secrets.cfg*`, `.venv`, `.cache`,
`.bw_debug_history`, `.envrc`.
### `commands.md` (~80120 lines)
**Side-effect model** (paragraph up front):
- `bundles/<x>/metadata.py` `defaults` and `@metadata_reactor` can write into
*any* namespace (e.g. nextcloud's metadata writes into `apt.packages` and
`archive.paths`). Changing it can ripple into other bundles' inputs.
- `libs/<x>.py` is imported by both `items.py` and `metadata.py` across many
bundles — biggest blast radius.
- `groups/*.py` changes membership (which bundles a node gets) and merged
metadata.
- `bw hash` is the primary integrated check because it captures bundle
membership + metadata + items + file content.
**Read-only command reference** (one line each):
`bw hash`, `bw metadata`, `bw items`, `bw items <node> <id> -p`, `bw nodes`,
`bw groups`, `bw verify`, `bw debug`, `bw test`, `bw plot`. Each tagged
read-only.
**After-change checks, keyed by what you changed:**
| You changed | First check | Drill-in |
|---|---|---|
| `bundles/<x>/items.py` | `bw hash <node>` for a node with bundle `<x>` | `bw items <node> <id> -p`; `bw verify <node>` |
| `bundles/<x>/metadata.py` | `bw hash` for *all nodes including bundle `<x>`* (reactors can ripple beyond `<x>`'s namespace) | `bw metadata <node>`; `bw metadata <node> -k <key>` |
| `bundles/<x>/files/*` (template) | `bw hash <node>` | `bw items <node> <path> -p` |
| `groups/*.py` | `bw hash` every node in/near the group | `bw groups -n <node>`; `bw metadata <node>` |
| `libs/<x>.py` | `bw hash` **all** nodes (cheap, no network) — biggest blast radius | `bw debug` to inspect helper outputs |
| `nodes/<x>.py` | `bw hash <node>` | `bw metadata <node>` |
| `hooks/*.py` | re-run the `bw` command whose lifecycle the hook hooks | — |
| Anything | `bw test` — cheapest repo-level sanity | — |
**Mutating commands (forbidden without explicit user request).** `bw apply`,
`bw run`, `bw lock` — what each does and why agents must not invoke them
autonomously.
**Hash diff workflow.** Capture `bw hash > before.txt`, make change,
`bw hash > after.txt`, diff. Canonical pre/post comparison.
**Targeting.** How to scope to one node / one group (`-t`, group selectors).
### `bundlewrap/README.md` (~80 lines)
Mental model paragraph: nodes ← groups → bundles → items; metadata flow
(groups → node → metadata processors → bundle items); hooks vs items vs libs.
Glossary, one paragraph each: node, group, bundle, item, items.py, metadata.py,
metadata reactor, hook, lib, `repo.libs`. Folder index. Fork callout
(explicit "we use a fork — here's what differs (or 'nothing day-to-day')";
link to fork source). Links out to <https://docs.bundlewrap.org> for depth.
### `bundlewrap/items.md` (~200300 lines)
The item types this repo actually uses — file, pkg_apt, svc_systemd, action,
symlink, group, user, … — with common attributes and examples drawn from
this repo. The dependency keyword glossary: `needs`, `needed_by`, `triggers`,
`triggered`, `triggered_skip_for`, `tags`, `cascade_skip`, `unless` — each
with one sentence and a real example. Custom item types from `items/`
(currently `download`). "When in doubt, see upstream items reference" with
a link.
### `bundlewrap/metadata.md` (~200300 lines)
`defaults` dict semantics (deep-merge into resolved metadata, can write into
any namespace). `@metadata_reactor.provides(...)` contract: signature, return
shape, fixpoint resolution, when to declare `provides` narrowly vs broadly.
Vault integration: `repo.vault.password_for`, `repo.vault.random_bytes_as_base64_for`,
`repo.vault.decrypt`, `repo.vault.decrypt_file` — and how they tie back to
demagify magic strings in node files. `repo.libs.hashable.hashable(...)` for
putting dicts in sets — a pattern used heavily in this repo, worth explicit
example. Common pitfalls: reactor declared narrower than it writes; reactor
that doesn't reach fixpoint; reactor that reads a key it didn't declare reading.
## 7. Seed work & rollout
### Phase 1 — scaffolding (one PR-sized chunk)
1. Root `AGENTS.md` (Section 4) + `CLAUDE.md` symlink → `AGENTS.md`.
2. `docs/agents/bundlewrap/README.md`, `items.md`, `metadata.md` (Section 6).
3. `docs/agents/conventions.md` and `commands.md` (Section 6).
4. Per-area `AGENTS.md` for `bundles/`, `nodes/`, `groups/`, `libs/`, `hooks/`,
`data/`, `items/`, `bin/` (Section 5).
5. `bundles/AGENTS.template.md` so future bundle docs have something to copy.
6. Docstring/header pass: add a one-line module docstring to any `libs/*.py`
and `hooks/*.py` lacking one; `# purpose:` header to any `bin/*` script
lacking one.
Order within Phase 1: root → bundlewrap folder → conventions → commands →
area docs → docstring pass → template. Each piece can be reviewed in
isolation; the work bisects cleanly.
Honest scope: the bundlewrap folder is ~600 lines of focused writing total
(80 + 250 + 250). The rest is shorter — area docs and conventions land in
30120 lines each.
### Phase 2 — seed bundles (10)
Bundles selected empirically (node+group references and recent commit
activity, validated 2026-05-10):
**Usage hubs (6):**
1. `monitored` (12 node refs) — meta-bundle, often misunderstood.
2. `postgresql` (9 refs, 3 cross-bundle).
3. `wireguard` (8 refs, has own lib + bin script).
4. `php` (8 refs).
5. `apt` (6 refs, has own lib).
6. `nginx` (4 refs, web foundational).
**Recently active or complex (4):**
7. `telegraf` (9 cross-bundle refs, 6 recent commits) — monitoring snippets
ripple across bundles.
8. `backup` (7 refs, cross-node coordination).
9. `letsencrypt` (6 refs, cross-cutting).
10. `nextcloud` (5 recent commits, complex, actively edited).
For each: write one `AGENTS.md` from the template — purpose, usage, metadata
dict, produces, depends-on, gotchas. Migrate any existing
`bundles/<x>/README.md` content into it and remove the old `README.md`.
### Phase 3 — leave-as-you-go
- Convention from this point: any time an agent (or you) materially edits a
bundle, top-up or create its `AGENTS.md`. Documented as a rule in
`bundles/AGENTS.md`.
- No mass-fill of the remaining ~95 bundles up front — most are simple enough
that `items.py`/`metadata.py` are self-explanatory.
## 8. Future work (not this spec)
- Contributing an `AGENTS.md` to bundlewrap upstream (or to your fork)
describing items/metadata semantics for agents — would shrink the
bundlewrap folder over time and shift authority back upstream.
- Tooling: a read-only `bw` wrapper or lint that nudges new bundles toward
having an `AGENTS.md`. Worth considering only after Phase 1+2 reveal which
conventions actually drift.
- More bundlewrap docs files (`groups-nodes.md`, `hooks.md`) if real gaps
surface during Phase 2 or Phase 3 work.
## 9. Open questions / risks
- **Risk: docs drift.** As the repo evolves, `AGENTS.md` files lag behind
code. Mitigations: per-bundle docs are short (low maintenance); Phase 3
rule attaches doc updates to material code edits; the area docs are
mechanism-focused, which changes less often than enumerations.
- **Risk: bundlewrap-folder duplicates upstream.** Acknowledged trade-off.
Mitigation: the folder is scoped to *as we use it here*, with explicit
upstream links; not trying to be a full bundlewrap manual.
- **Open: which seed bundles to swap.** Phase 2 list is empirically grounded
but not rigid — `zfs` (8 refs), `bind` (4 refs, own lib), and
`routeros-monitoring` (15 recent commits, specialized) are honourable
mentions if a swap is wanted later.