bundlewrap/libs/AGENTS.md
CroneKorkN 04558a9189
docs: scaffold agent-friendly entry points (Phase 1)
introduces a balanced set of agent + human docs:

- root AGENTS.md (with CLAUDE.md symlink) — 5-rule quickstart,
  layout map, mental model, use-case keyed example pointers.
- docs/agents/conventions.md — vault/demagify, eval-loader
  constraints, group inheritance, naming, do-not-touch list,
  suspension idioms, working-style notes.
- docs/agents/commands.md — repo-specific deltas to the fork's
  bw runbook (apt-key offline-verify, *.py_ suspended-node
  visibility, vault-echo rule).
- per-area AGENTS.md for bundles/, nodes/, groups/, libs/,
  hooks/, data/, items/, bin/ — mechanism-focused, no enumeration.
- bundles/AGENTS.template.md — per-bundle doc template with
  optional `## Writes into` section for cross-namespace reactors.

bundlewrap-language reference (item types, dep keywords, reactors,
runbook, three-tier safety envelope) is not duplicated here; we
link out to the fork's AGENTS.md instead.

bw test still green. all internal links resolve. Phase 0 invariants
preserved (libs/hooks docstrings, bin/* # purpose: headers).

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

2.5 KiB

libs/

What's here

Shared Python helpers reachable from any bundle as repo.libs.<modulename>.<symbol>. One module per file (no packages). Discovery is by ls libs/ plus the one-line module docstring at the top of each file.

head -1 libs/*.py

Conventions

  • One file per module. Filename (without .py) is the importable name. libs/wireguard.py exposes repo.libs.wireguard.
  • One-line module docstring. Every libs/*.py starts with """<name>: <one-line purpose>.""" so ls + head is a working index. Add one when introducing a new lib; the rule is enforced by the Phase-0 baseline (grep -L '"""' libs/*.py should report zero files).
  • Pure helpers, no I/O at import. Libs are imported on every bw invocation. Heavy work or filesystem reads at module scope slow the whole repo. Use functions, not module-level side effects.
  • No magic-string demagification here. Libs receive already- resolved values from bundles or nodes. Don't pass !password_for: strings into libs — they won't be resolved.
  • repo and vault are globals available at runtime. A few libs (wireguard.py, etc.) reach repo.vault.<verb>(...) directly. That's intentional inside libs, since libs run in the same loader scope as bundles.

How to add a helper

  1. Either extract from a bundle that's growing complex, or add a new libs/<name>.py file.
  2. Top line: """<name>: <one-line purpose>.""".
  3. Pure functions only; document side effects in the docstring if any slip through.
  4. Use it from bundles/<x>/items.py or metadata.py as repo.libs.<name>.<symbol>(...).

Pitfalls

  • Lib changes have repo-wide blast radius. Every bundle that imports the lib re-evaluates on the next bw test / bw apply. Before changing a lib's API, find consumers:

    git grep -l 'repo.libs.<x>'
    
  • @cache is your friend for deterministic key derivations (wireguard, rsa, ssh); without it, bw recomputes per call, which is slow.

  • Hashable wrappers. libs/hashable.py exists because raw dicts and sets aren't hashable, so you can't put them inside metadata sets. Wrap with repo.libs.hashable.hashable(...) before nesting.

See also