agentleFS
ric03uecric03uec/clawrium/AGENTS.md

clawrium

AGENTS.md67 starsChanged 2 months agoApache-2.0View on GitHub

Clawrium is a CLI tool (clawctl) that manages AI agent fleets across your local network. Point it at any machine, and it handles deployment, configuration, and lifecycle management via SSH and Ansible. Install instructions live in docs/installation.md. This is the canonical source — when updating install steps: The website docs MUST follow docs/installation.md exactly. Do not edit website/docs/installation.md directly. Host preparation instructions live in docs/host-preparation.md. This is the canonical source — when updating host-setup steps: The website docs MUST follow…

  • Reads credentials

The file

Show its rules and text

We split this AGENTS.md into the rules it gives an agent

30 rules. About 0 are common, 16 are distinctive, and 14 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈16

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 14

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Homelabbers: Run multiple AI assistants on spare hardware

    line 24
  2. Very specific:

    current, unreleased version only: All new work is documented here as it lands, under the ## [Unreleased] heading.

    line 81
  3. Very specific:

    Update the root CHANGELOG.md (under ## [Unreleased]) as part of the same change that introduces the behavior — not as an afterthought:

    line 95
  4. Very specific:

    Every breaking change: MUST be documented under ### BREAKING.

    line 103
  5. Very specific:

    When a Hermes agent (e.g. Maurice) is asked to work on this repository, it MUST load skills from .hermes/skills/ and treat them as …

    line 115
  6. Very specific:

    All Hermes skills shipped from this repository are scoped to the clawrium namespace — the name: field in their SKILL.md frontmatter MUST be …

    line 117
  7. Very specific:

    MUST install at sync time via a dedicated single-purpose Ansible runbook invoked from core/lifecycle_canonical.py:sync_agent_canonical.

    line 308
  8. Very specific:

    NOT from the agent's configure.yaml.

    line 310
  9. Distinctive:

    One runbook per binary: Do not group multiple integration installs into one playbook.

    line 333
  10. Very specific:

    Always use make commands to run tests and validate changes:

    line 406
  11. Very specific:

    Use /clawctl to manage the Clawrium fleet from an AI assistant session.

    line 443
  12. Very specific:

    IMPORTANT: Always commit the .itx/ directory with your changes.

    line 501
  13. Very specific:

    When completing work on an issue, ensure .itx/<issue-number>/ is included in your commit.

    line 506
  14. Distinctive:

    Read implementation plan from issue

    line 513
  15. Distinctive:

    Create an implementation task for each phase or step using the harness's task tracker

    line 514
  16. Distinctive:

    Create verification tasks (tests, lint, review if MCP enabled)

    line 515
  17. Distinctive:

    If execution feels unclear or you lose orientation:

    line 536
  18. Very specific:

    Check which task is in_progress

    line 538
  19. Very specific:

    When mcp.review_enabled is true, all code changes MUST attempt automated review before merging.

    line 563
  20. Distinctive:

    Document each review iteration in commit message and PR body

    line 575
  21. Distinctive:

    Before creating a commit with code changes

    line 578
  22. Distinctive:

    After fixing issues from previous review

    line 579
  23. Distinctive:

    Before marking PR as ready for merge </atx-review-requirements>

    line 580
  24. Very specific:

    When mcp.review_enabled is false, not configured, or both ATX transports are exhausted, use manual review with self-attestation.

    line 679
  25. Distinctive:

    Run all tests and ensure they pass

    line 684
  26. Distinctive:

    Run linter and fix any issues

    line 685
  27. Distinctive:

    Document testing approach in PR </manual-review-requirements>

    line 687
  28. Distinctive:

    Tests must pass: All tests must pass before merge

    line 744
  29. Distinctive:

    Linter must pass: No lint errors allowed

    line 745
  30. Distinctive:

    One log per stage: if a stage runs multiple times, append additional H2 sections to the same file rather than creating new files.

    line 773

≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

Clawrium

How It Works

Clawrium is a CLI tool (clawctl) that manages AI agent fleets across your local network. Point it at any machine, and it handles deployment, configuration, and lifecycle management via SSH and Ansible.

Your Machine (clawctl CLI)
    │
    ├── Host A ──> zeroclaw instance
    ├── Host B ──> openclaw instance
    └── Host C ──> nemoclaw instance, zeroclaw instance

Why

  • Single pane of glass: Manage all agents from one CLI instead of SSH-ing to each host
  • Consistent lifecycle: Same commands for install, configure, start, stop, remove across all agent types
  • Secrets management: Secure API key storage with per-agent isolation
  • Fleet visibility: clawctl agent get shows status of all agents across all hosts

Who Is This For

  • Homelabbers: Run multiple AI assistants on spare hardware
  • Teams: Standardize agent deployment across developer machines
  • Experimenters: Try different models/agents without manual setup on each host

Quickstart

# Install
uv tool install clawrium

# Add a host (see docs/host-preparation.md for the xclm setup commands)
clawctl host create 192.168.1.100 --user xclm --alias mybox

# Install an agent
clawctl agent create <agent-name> --type openclaw --host mybox

# Configure and start
clawctl agent configure <agent-name>
clawctl agent start <agent-name>

# Run a command against the agent's native CLI on its host
clawctl agent exec <agent-name> -- --version

# Install a skill onto the agent (catalog: skills/)
clawctl skill registry get
clawctl skill registry describe clawrium/tdd       # Inspect a skill before installing
clawctl agent skill add <agent-name> --from-template clawrium/tdd
clawctl agent sync <agent-name>

# Check fleet status
clawctl agent get

Installation Source of Truth

Install instructions live in docs/installation.md. This is the canonical source — when updating install steps:

  1. Edit docs/installation.md first.
  2. Mirror the body verbatim to website/docs/installation.md. Keep the Docusaurus frontmatter and the mirror-warning HTML comment at the top of that file; everything below them must match docs/installation.md exactly.
  3. The README's install section is intentionally brief (just the two uv tool install / uvx commands). Only update it if those commands change.

The website docs MUST follow docs/installation.md exactly. Do not edit website/docs/installation.md directly.

Host Setup Source of Truth

Host preparation instructions live in docs/host-preparation.md. This is the canonical source — when updating host-setup steps:

  1. Edit docs/host-preparation.md first.
  2. Mirror the body verbatim to website/docs/guides/host-setup.md. Keep the Docusaurus frontmatter and the mirror-warning HTML comment at the top of that file; everything below them must match docs/host-preparation.md exactly (modulo Docusaurus :::note / :::warning admonitions where the engineering docs use > blockquotes).

The website docs MUST follow docs/host-preparation.md exactly. Do not edit website/docs/guides/host-setup.md directly.

Changelog & Release Notes

Clawrium tracks every change across every release. There are two layers:

  • Root CHANGELOG.md — the working log for the current, unreleased version only. All new work is documented here as it lands, under the ## [Unreleased] heading.
  • docs/releases/<version>/ — one folder per shipped release, each containing a frozen CHANGELOG.md. On every release cut the itx-release skill archives the root changelog into a new docs/releases/<version>/CHANGELOG.md and then resets the root file to an empty [Unreleased] template. The per-version folder may also hold detailed migration instructions for that release.

docs/releases/ is therefore the single place to read the full history of what changed in any release. Do not delete entries from it.

Update Rules

Update the root CHANGELOG.md (under ## [Unreleased]) as part of the same change that introduces the behavior — not as an afterthought:

  • Every new feature gets an entry under ### Added with a short, plain description of what it does (one or two lines, user-facing language).
  • Every behavior change goes under ### Changed; every notable bug fix goes under ### Fixed, referencing the issue/PR number where one exists (e.g. #555).
  • Every breaking change MUST be documented under ### BREAKING. State what breaks, why, and exactly what operators must do to recover (commands, config edits). If there is no automated migration, say so explicitly and give the manual steps. Breaking changes are non-negotiable to document — an undocumented breaking change is a release blocker.
  • Documentation-only changes go under ### Documentation.

The itx-release skill handles archiving and resetting the changelog at cut time; contributors only ever edit the root file's [Unreleased] section.

Hermes Skills

When a Hermes agent (e.g. Maurice) is asked to work on this repository, it MUST load skills from .hermes/skills/ and treat them as available alongside its built-in skills. Each skill follows the upstream Hermes skill format documented at https://hermes-agent.nousresearch.com/docs/developer-guide/creating-skills.

All Hermes skills shipped from this repository are scoped to the clawrium namespace — the name: field in their SKILL.md frontmatter MUST be prefixed with clawrium- (hermes' only documented separator is the hyphen).

Currently available:

Skill namePathPurpose
clawrium-release-announcements.hermes/skills/release-announcements/Daily release blog draft on Blog Pipeline kanban + Discord announcement

Key Concepts

  • Host: A machine in your network that runs one or more agents
  • Agent: An AI assistant instance (zeroclaw, nemoclaw, or openclaw)
  • Agent Type: The specific AI assistant implementation (e.g., zeroclaw, nemoclaw, openclaw)
  • Agent Name: The unique identifier for an installed agent instance
  • Agent Registry: Platform-defined agent types with versions, dependencies, and templates
  • Skill Registry: A namespace under bundled skills/ and the user overlay at ~/.config/clawrium/skills/ from which skill templates can be copied onto an agent (clawrium, openclaw, hermes, zeroclaw). Catalog templates are referenced as <skill-registry>/<name> (e.g. clawrium/tdd); once copied onto an agent, the local desired state stores the bare skill name (e.g. tdd). Distinct from the Agent Registry.

Resources

Gateway Token Lifecycle (zeroclaw)

The zeroclaw gateway authenticates clawctl agent chat sessions with a bearer token the daemon mints via a /pair/code → /pair loopback handshake. The daemon does not persist that bearer across systemd restarts, so the only way for clawctl to guarantee hosts.json.gateway.auth equals the bearer the daemon will enforce on the next request is to always re-pair on every lifecycle op that touches the daemon.

Rules (issue #437):

  • clawctl agent configure, clawctl agent sync, and clawctl agent restart all mint a fresh bearer and overwrite hosts.json.gateway.auth atomically.
  • There is no idempotent-skip path. Do not add a --no-rotate flag — branching here is the bug the original ATX Round 1 B3 code introduced.
  • Remote clawctl agent chat sessions (running on a different machine than the one that ran the lifecycle op) will get a clean 401 on their next request. They must reconnect — that's the documented trade-off.
  • Local clawctl agent chat reconnects transparently: on 401 it reloads hosts.json once, compares the bearer in memory vs disk, and rebuilds the backend with the fresh token if they differ.
  • A single gateway_token_rotated event is emitted from lifecycle.py whenever the bearer is overwritten. The CLI renders it as a yellow notice during configure/sync/restart.

Hermes Config Rendering (issue #622)

~/.hermes/config.yaml and ~/.hermes/.env are rendered exclusively by src/clawrium/core/render.py:render_hermes. Both clawctl agent configure (via lifecycle.configure_agent) and clawctl agent sync (via lifecycle_canonical.sync_agent_canonical) call the same renderer; the configure playbook copies the pre-rendered bytes via ansible.builtin.copy, it does not template them server-side.

To change the on-host shape, edit the canonical templates in src/clawrium/platform/registry/hermes/templates/ (hermes-config.canonical.yaml.j2 and hermes-env.canonical.j2) and the render_hermes plumbing in core/render.py. There is no second template path.

Multi-provider attachments (primary + N auxiliary slots) are a hermes-only feature. Zeroclaw, openclaw, and nemoclaw enforce single-provider invariants at the CLI and renderer layers; their templates do not iterate config.providers.

Native Dashboards (issues #478, #491)

Three agent types ship a native web UI today: hermes (issue #478), zeroclaw (issue #491), and openclaw. The manifest's features.web_ui block is the single gate — clawctl agent open <name> and the GUI's Open Agent UI button both consult the resolver in src/clawrium/core/web_ui.py, which returns None for any agent whose manifest does not declare features.web_ui.

Hermes (issue #478) runs the dashboard in a separate systemd unit on the agent host:

  • hermes-<agent_name>.service — the OpenAI-compatible API gateway (the existing unit).
  • hermes-dashboard-<agent_name>.service — the SPA dashboard, with PartOf=hermes-<agent_name>.service and Also=hermes-<agent_name>.service in [Install]. systemd propagates stop/restart of the gateway to the dashboard automatically; we explicitly enable the dashboard unit on first start.

The hermes dashboard binds 127.0.0.1:<port> only (features.web_ui.bind: loopback). install.py picks the per-instance port in 45000..46999 and persists it under hosts.json.agents.<name>.config.dashboard.port.

Zeroclaw (issue #491) does not run a separate dashboard unit — the gateway daemon itself serves the SPA on the same port as config.gateway.port (features.web_ui.bind: wildcard, port persisted under gateway.port). install.py picks the per-instance gateway port in 40000..41999.

Openclaw uses the same shape as zeroclaw: the SPA is served by openclaw gateway run on the same port as config.gateway.port (features.web_ui.bind: wildcard, port persisted under gateway.port). install.py picks the per-instance gateway port in 40000..41999 via the same allocator branch as zeroclaw. Unlike zeroclaw, openclaw is intentionally NOT in _PAIRING_AGENT_TYPES (see gui/routes/fleet.py) — its WebSocket auth uses the gateway bearer token already persisted under hosts.json.agents.<name>.config.gateway.auth (flat string; the nested .token form only appears inside ~/.openclaw/openclaw.json on the agent host). The GUI's Show Connection Token button surfaces it via POST /api/fleet/agents/<name>/connection-token; the canonical lookup is _resolve_openclaw_credentials in gui/routes/agents.py, which reads the secrets store first and falls back to the legacy hosts.json field. A dedicated pairing UX for openclaw is a follow-up.

Auth boundary (hermes / zeroclaw). There is no in-process auth on the dashboard for hermes or zeroclaw: the SSH key Ansible already uses for the host is the auth boundary. Anyone with shell access to the agent host could reach the dashboard directly, so layering a token wall on top would not raise the security floor. Zeroclaw's 0.0.0.0 bind is an upstream property; this trust model still holds because reachability is gated at the network/SSH layer. Openclaw additionally requires the gateway bearer token at the WebSocket handshake — see the openclaw section above.

Tunnel reuse. The tunnel manager at src/clawrium/core/web_ui_tunnel.py is idempotent — a second invocation for a live tunnel reuses the existing local port (state at ~/.config/clawrium/tunnels/<agent_key>.json, PID + cmdline-guarded). The GUI auto-reaps tunnels idle > 30 minutes; CLI tunnels close on Ctrl-C or process exit. The remote target is always loopback regardless of bind: both BIND_ADDRESS_MAP["loopback"] and BIND_ADDRESS_MAP["wildcard"] resolve to 127.0.0.1, because the SSH local-forward terminates on the remote loopback interface — reachable for both bind modes.

No default_port in bundled manifests. Hermes, zeroclaw, and openclaw manifests all omit features.web_ui.default_port. install.py always persists a per-instance port at port_field; a manifest-wide default would silently collide on hosts running multiple agents of the same type. The resolver surfaces a missing persisted port as "no UI available" rather than inventing one. Third-party manifests may still opt into default_port — the schema accepts it.

Workspace Overlay (issue #760)

Operator-supplied files dropped under ~/.config/clawrium/agents/<type>/<name>/workspace/ are mirrored onto the agent host at the manifest-declared destination root on every clawctl agent sync and clawctl agent configure. The shared push helper is src/clawrium/core/workspace_sync.py:push_workspace_phase; both lifecycle_canonical.sync_agent_canonical and lifecycle.configure_agent call it.

Manifest contract — per-agent manifest.yaml:

features:
  workspace_overlay:
    destination_root: "<absolute path under agent's home>"
    excludes:                  # optional, relative-path strings
      - <relpath>              # exact-file match
      - <relpath>/             # trailing slash = dir-prefix match

Per-type destinations:

Agentdestination_rootLinux pathmacOS pathNotes
openclaw~/.openclaw/workspace/home/<name>/.openclaw/workspace/Users/<name>/.openclaw/workspace (#770)No excludes — workspace zone is operator-owned. macOS GA via #770.
zeroclaw~/.zeroclaw/workspace/home/<name>/.zeroclaw/workspace/Users/<name>/.zeroclaw/workspace (#771)No excludes — workspace dir holds operator memory files; canonical render writes elsewhere. Every sync also rotates the gateway bearer (#437); macOS GA via #771.
hermes~/.hermes/home/<name>/.hermes/Users/<name>/.hermes (#772)Renderer-output paths excluded. Shares destination root with core/render.py output, skills_apply.yaml writes, and daemon state (SQLite + WAL companions). macOS GA via #772 — the per-file workspace_excluded Jinja filter applies identically on darwin and Linux.

Hermes excludes (manifest source of truth):

Excluded pathWhy
config.yaml, .envWritten by core/render.py:render_hermes. U5 enforces excludes ⊇ render output keys via AST scan.
auth.jsonOperator-paired upstream auth state.
state.db, state.db-journal, state.db-wal, state.db-shmSQLite DB + all three WAL companion files. Overlaying any while the daemon holds the WAL open corrupts the database silently — all three companions MUST stay listed (W13 iter-2).
sessions/ (dir)Per-conversation transcripts.
logs/ (dir)Daemon log output.
skills/clawrium/ (dir)Reconciled by hermes/playbooks/skills_apply.yaml. U33 enforces this dir is a strict superset of the playbook's write target.

Hermes is the only agent type whose playbook re-applies exclude semantics inside a per-file when: clause (via the workspace_excluded Jinja filter in hermes/playbooks/filter_plugins/). The filter mirrors core.workspace_sync._is_excluded exactly. Drift between the two would either let an excluded file through (Python applies the filter but the playbook does not) or silently drop a legitimate file (the playbook is stricter than Python) — any change to either side MUST land in the same commit. Openclaw and zeroclaw playbooks omit the filter because both ship empty exclude lists; their playbook ignores the workspace_excludes_* extravars.

CLI flags on clawctl agent sync:

  • --workspace-only — push the overlay alone, skip canonical render / restart / verify. Mutually exclusive with --diff. For zeroclaw the bearer rotation still runs (Phase 2, #768).
  • --no-restart — canonical + overlay, skip restart. Bearer rotation still runs for zeroclaw (Phase 2, #768).
  • --workspace — removed (BREAKING). Exits 2 with a hint to the two replacements above. No automated migration.

Host-write path is Ansible only — per-agent playbooks/workspace.yaml (Linux) and playbooks/workspace_macos.yaml (macOS; openclaw GA via #770, zeroclaw GA via #771, hermes GA via #772 — the workspace-overlay macOS matrix is complete). The Python side stages files into a managed tempfile.TemporaryDirectory under ${clawrium_config}/staging/workspace/<name>/ and passes the file list as the workspace_files extravar; ansible-runner reads the staged copies, never the operator's original path. Symlinks are rejected at enumeration (os.path.islink); follow: no on the ansible.builtin.copy task is belt-and-suspenders. The Linux playbook asserts the rendered destination starts with /home/{{ agent_name }}/; the macOS playbook asserts /Users/{{ agent_name }}/. The OS→home-root mapping is centralized in core.playbook_resolver.home_root_for and consumed by core.workspace_sync._expand_destination_root; no other module is allowed to branch on os_family. Neither variant references ansible_user_dir (B1 iter-3: become_user does not re-run setup, so the fact would resolve to the SSH user, not the agent user). This ansible_user_dir ban is a project-wide invariant — any new or refactored Ansible task that needs the agent user's home MUST use the literal /home/{{ agent_name }} (Linux) or /Users/{{ agent_name }} (macOS) pattern, never the fact. The macOS playbook also hardcodes group: staff (gid 20) instead of the Linux group: "{{ agent_name }}" convention, because macOS does not create a per-user primary group.

Secret-pattern files (*.key, *.pem, *.env, .env, *credentials*, *secret*, *token*, *password*) get mode: '0600' regardless of local perms. Operator-controlled strings in NDJSON event payloads (path, remote_path, reason) are passed through cli/output/_sanitize.py:sanitize_passthrough so bidi/zero-width codepoints cannot spoof terminal output.

Workspace-phase failure short-circuits before restart — the daemon is never restarted on a half-applied overlay. The failure surfaces as CanonicalSyncError → CLI emit_error → typer.Exit(code=1).

Integration Binary Install (architectural pattern)

When an integration attaches a subprocess binary to an agent (MCP servers, plugins, sidecars — anything the daemon execs), the binary MUST install at sync time via a dedicated single-purpose Ansible runbook invoked from core/lifecycle_canonical.py:sync_agent_canonical. NOT from the agent's configure.yaml. NOT from install.yaml.

This is the operator's mental model: clawctl agent integration attach <X> + clawctl agent sync materializes X on the host. The host install (install.yaml) is agent-lifecycle only — it does not know which integrations exist yet. The configure playbook (configure.yaml) is not invoked by the modern kubectl-style CLI at all — putting integration installs there produces a rendered config pointing at a binary the daemon never spawns. This regression class was closed for openclaw brave plugins in #755 and again for hermes Slack MCP in #834.

Canonical examples

IntegrationRunbookSync-time entry point
Openclaw brave (+ any future openclaw plugin)Direct SSH via openclaw plugins install --pin_openclaw_install_plugins (lifecycle_canonical.py:1307)
Hermes Slack MCP (slack-user, slack-cookie)hermes/playbooks/install_slack_mcp.yaml (+ _macos sibling)_hermes_install_slack_mcp (lifecycle_canonical.py, #834)
Openclaw Slack MCP (slack-user, slack-cookie)openclaw/playbooks/install_slack_mcp.yaml (+ _macos sibling)_openclaw_install_slack_mcp (lifecycle_canonical.py, #835)
Zeroclaw Slack MCP (slack-user, slack-cookie)zeroclaw/playbooks/install_slack_mcp.yaml (Linux-only; macOS sibling deferred — follow-up #836)_zeroclaw_install_slack_mcp (lifecycle_canonical.py, #851; darwin hosts refuse loudly with a clawctl agent integration detach hint)

Rules (non-negotiable)

  1. One runbook per binary. Do not group multiple integration installs into one playbook. A single-purpose runbook fails loudly when one integration is broken without dragging unrelated integrations into the failure. Naming: install_<integration>.yaml (Linux) + install_<integration>_macos.yaml (darwin, if the integration supports macOS).
  2. Runbook is a dumb consumer of extravars. Version pin, per-arch asset map, per-arch SHA256 map: all top-of-file vars:. No when: ansible_os_family == "Darwin" inside install tasks (dispatcher-only OS fork invariant — see the mac-test host available memory context). One narrow exception: a task-0 dispatcher-contract guard that fails-fast when the wrong-OS sibling was routed here by mistake (e.g., Refuse to run on non-Linux hosts) IS permitted — the check is against the opposite OS so a mis-routed run trips loudly. This guard MUST be task-0 (before the arch guard, before any install work), MUST only fail: (never conditionally install), and MUST have a matching mirror in the sibling runbook. If a runbook has more than one when: ansible_os_family clause, or any that gates an install task, it violates the invariant.
  3. Idempotency via checksum, not sentinel files. get_url with checksum: "sha256:{{ map[ansible_architecture] }}" short-circuits when the file exists and matches. Version bump changes URL + hash → auto-fires reinstall via mismatch. No creates: guard; no .integration-installed.<version> sentinel unless the install command is not itself checksum-verifying (e.g., npm install for openclaw plugins does use a sentinel — see #755).
  4. Gate at the sync-time entry point, not inside the runbook. The Python helper checks any(i.type in <supported_types> for i in inputs.integrations) before invoking ansible-runner. Runbook itself assumes the integration IS attached — if it runs, it does the install.
  5. Fail-fast on unsupported architecture. First task in the runbook: fail with a clear message that names the arch and links to the upstream release page. Do not silently skip. armv7l support is not universal — the plan must document which arches ship upstream at pin bump time.
  6. Failure short-circuits before restart. The helper raises CanonicalSyncError on any playbook failure. sync_agent_canonical propagates before _restart_unit runs. Mirrors the workspace- overlay failure contract (line 302 above).
  7. Runbook runs BEFORE the file-write loop. So the freshly- rendered config.yaml + env AND the freshly-installed binary land in a single daemon restart. Do not restart between install and config write.
  8. Version pin lockstep. The Python constant referenced by render.py (e.g., _HERMES_MCP_SLACK_VERSION) MUST equal the mcp_<integration>_version var at the top of EACH runbook (Linux + darwin sibling). A test in tests/platform/ MUST directly assert <render_constant> == vars['mcp_<integration>_version'] for every runbook file. Transitive equality via a Linux ↔ darwin pin-equality assertion is not sufficient — an intermediate test being renamed / skipped would silently break the darwin lockstep.

When NOT to use this pattern

  • Integrations that add ONLY environment variables + config lines, no subprocess binary. Those are pure render-layer concerns; no install runbook is needed. (Example: github — just wires GITHUB_TOKEN into .env and optionally runs gh auth login.)
  • Package-manager-installed system dependencies. Those belong in install.yaml (host-lifecycle), because they are host-scoped, not integration-scoped.

Tech Stack

  • CLI: Python + Typer
  • Execution: ansible-runner
  • Packaging: uv/uvx
  • User Data: ~/.config/clawrium/

Development

Always use make commands to run tests and validate changes:

make test       # Run tests (required before commits)
make lint       # Check code style
make format     # Format code
make test-cov   # Run tests with coverage

Development Workflow

GitHub Issues are the single source of truth. See CONTRIBUTING.md for full workflow documentation.

Worktree Convention

For parallel issue execution, use git worktrees with this naming:

<repo-parent>/<repo-name>-issue-<number>/

Example:

~/projects/clawrium/           # Main repo
~/projects/clawrium-issue-35/  # Worktree for issue 35

Trigger with: /itx-execute 35 in a subtree or /itx-execute 35 --worktree

Quick Reference

New Issue → /itx-triage → /itx-plan-create → /itx-plan-scaffold → /itx-execute → /itx-verify → /itx-review-pr → Merge

Fleet Management

Use /clawctl to manage the Clawrium fleet from an AI assistant session. The skill embeds the full clawctl CLI reference — hosts, agents, providers, channels, integrations, skills, secrets, and memory. Always use clawctl, never clm.

CommandPurpose
/clawctlManage fleet: hosts, agents, providers, channels, integrations, skills, secrets

Workflow Commands

CommandPurpose
/itx-bug-newCreate bug issue (asks for customer outcome)
/itx-bug-update <n> <text>Add context to an existing bug
/itx-issue-newCreate feature issue (asks for customer outcome)
/itx-issue-update <n> <text>Add context to an existing issue
/itx-note <text>Capture an idea in NOTES.md
/itx-triageReview unlabeled issues
/itx-plan-create <n>Create high-level implementation plan
/itx-plan-scaffold <n>Create phased execution with entry/exit criteria
/itx-execute <n>Execute issue (parent or subtask)
/itx-verifyRun tests and lint
/itx-review-pr [n]Review PR (MCP or manual)
/itx-pr-statusCheck open pull request status
/itx-release [version]Cut and publish a release

Cross-Harness Command Source

The thirteen .claude/skills/itx-*/SKILL.md files are the canonical ITX workflow definitions for Claude Code, OpenCode, and Pi.

  • OpenCode discovers the Claude skill directory directly. Do not add .opencode/commands/itx-*.md or .opencode/skills/itx-*/ copies; explicit OpenCode commands shadow skills and create drift.
  • Pi 0.84.2 or newer loads the canonical directory through .pi/settings.json when started from the repository root. Its .pi/extensions/itx-command-aliases.js adapter dynamically registers exact /itx-* aliases for Pi's native /skill:itx-* commands and queues delegated commands as follow-up messages when Pi is streaming.
  • .claude/itx-config.json remains the shared ITX configuration file for all harnesses.
  • itx-execute orchestrate mode and its tmux-backed standalone worktree path intentionally launch Claude Code child sessions through the claude CLI. They must verify that launcher before creating worktrees. Cross-harness child orchestration is a separate behavior change.

See docs/architecture/itx-cross-harness-commands.md for the implementation plan and maintenance contracts.

Planning Artifacts Directory (.itx/)

The .itx/ directory stores implementation plans and execution documentation for each issue. These are NOT ephemeral files - they document how an issue was planned and executed.

.itx/
└── <issue-number>/
    ├── 00_PLAN.md           # High-level implementation plan (from /itx-plan-create)
    └── 01_SCAFFOLD.md       # Phased execution plan (from /itx-plan-scaffold)

IMPORTANT: Always commit the .itx/ directory with your changes. These files serve as:

  • Historical record of implementation decisions
  • Context for future maintenance
  • Documentation of the execution approach

When completing work on an issue, ensure .itx/<issue-number>/ is included in your commit.

Task-Based Execution

The /itx-execute skill uses a structured task checklist approach to prevent getting lost during execution:

Planning Phase (Mandatory):

  1. Read implementation plan from issue
  2. Create an implementation task for each phase or step using the harness's task tracker
  3. Create verification tasks (tests, lint, review if MCP enabled)
  4. Set dependencies between tasks if needed
  5. Review task list to confirm structure

Execution Phase:

  1. Get the next unblocked pending task
  2. Mark the task in_progress
  3. Execute the task requirements
  4. Mark task completed
  5. Inspect the task list for remaining work
  6. Repeat until all tasks done

Example Task List:

[pending] Implement: Update CLI help text
  Requirements: Update all help text in src/clawrium/cli/agent.py
[pending] Run test suite
  Requirements: Execute make test and ensure all tests pass

Recovery Mechanism: If execution feels unclear or you lose orientation:

  • Inspect the task list to see current state
  • Check which task is in_progress
  • Review that task's description
  • Complete current task before starting next

Review

Review requirements depend on whether automated review is enabled in .claude/itx-config.json and an ATX transport is available.

Check Review Mode

ITX_CONFIG="$(git rev-parse --show-toplevel)/.claude/itx-config.json"
if [ -f "$ITX_CONFIG" ]; then
  REVIEW_ENABLED=$(jq -r '.mcp.review_enabled // false' "$ITX_CONFIG")
else
  REVIEW_ENABLED="false"
fi

If Automated Review Enabled (ATX)

<atx-review-requirements> When `mcp.review_enabled` is `true`, all code changes MUST attempt automated review before merging. Try an ATX MCP request-review tool available in the current harness first. If it is unavailable, fails, or times out, try the stateless `atx review request` CLI and require `atx server status` to report `Running: true`. If the CLI is unavailable, stopped, fails, or times out, follow the manual review requirements below and document each failed or unavailable transport.

Iteration Requirements

  1. Attempt review using the MCP → CLI → manual fallback sequence above
  2. Fix ALL blocking issues (B1, B2, etc.)
  3. Iterate until: Rating > 3/5 AND no blocking issues remain
  4. Document each review iteration in commit message and PR body

When to Request Review

  • Before creating a commit with code changes
  • After fixing issues from previous review
  • Before marking PR as ready for merge</atx-review-requirements>
<commit-format-atx> ### Commit Message Format (ATX)

Include ATX review summary after the commit body:

feat(component): short description

Detailed explanation of changes.

Closes #XX

ATX Review Summary
Review 1: Rating 2/5 | Cost: $0.12 | Time: 45s | Agents: leader, cli-ux
Blocking issues:
| # | Status | Issue |
|---|--------|-------|
| B1 | Fixed | Description of issue and fix |
| B2 | Out-of-scope | Pre-existing issue, tracked in #YY |

Warnings:
| # | Status | Warning |
|---|--------|---------|
| W1 | Fixed | Description |
| W2 | Acknowledged | Will address in follow-up |

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: @atx-ci <269048218+atx-ci@users.noreply.github.com>
</commit-format-atx><pr-format-atx> ### PR Body Format (ATX)

Include detailed ATX review after Summary and Testing sections:

## ATX Review Summary

**Final Review: Rating 4/5**
**Total Cost: $0.20 | Total Time: 1m 17s**

| Review | Rating | Blocking Issues | Status | Cost | Time | Agents |
|--------|--------|-----------------|--------|------|------|--------|
| 1 | 2/5 | B1, B2, B3 | All fixed | $0.12 | 45s | leader, cli-ux, test-coverage |
| 2 | 4/5 | None | Ready | $0.08 | 32s | leader, cli-ux |

> **Note**: ATX does not expose model information per agent.

<details>
<summary>Review 1 Details (Rating 2/5)</summary>

**Blocking Issues:**

| # | File | Issue | Resolution |
|---|------|-------|------------|
| B1 | `module.py:42` | SQL injection risk | Fixed - parameterized query |
| B2 | `test_module.py` | Missing edge case test | Fixed - added test |

**Warnings:**

| # | File | Warning | Action |
|---|------|---------|--------|
| W1 | `module.py:15` | Consider adding timeout | Added 30s timeout |
| W2 | `config.py` | Magic number | Deferred to #XX |

**Suggestions:**

| # | Suggestion | Action |
|---|------------|--------|
| S1 | Add docstring | Added |
| S2 | Consider caching | Deferred |

</details>

Co-Authored-By: @atx-ci <269048218+atx-ci@users.noreply.github.com>

See PRs #19, #21, and #205 for real examples of this format. </pr-format-atx>

<enforcement-atx> ### Enforcement Rules (ATX Result Available)
  1. No merge without review: PRs lacking ATX review section will be rejected
  2. No unresolved blockers: All B# issues must be Fixed or Out-of-scope with justification
  3. Rating threshold: Final review must be > 3/5
  4. Attribution required: Co-Authored-By: @atx-ci must appear in both commit and PR
  5. Iteration tracking: Each review round must be documented with its rating</enforcement-atx>

If Automated Review Disabled or Unavailable (Manual Review)

<manual-review-requirements> When `mcp.review_enabled` is `false`, not configured, or both ATX transports are exhausted, use manual review with self-attestation. If review was enabled, record each unavailable, stopped, failed, or timed-out transport in the PR.

Requirements

  1. Run all tests and ensure they pass
  2. Run linter and fix any issues
  3. Self-review changes for quality and security
  4. Document testing approach in PR</manual-review-requirements>
<commit-format-manual> ### Commit Message Format (Manual)
feat(component): short description

Detailed explanation of changes.

Closes #XX

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
</commit-format-manual><pr-format-manual> ### PR Body Format (Manual)
## Summary
<1-3 bullet points describing what this PR does>

## Testing

### Test Results
- [ ] All existing tests pass (`make test`)
- [ ] Linter passes (`make lint`)
- [ ] New tests added for new functionality

### Test Coverage
- Files changed: <list files>
- New tests: <list new test files or "N/A">
- Coverage impact: <increased/maintained/decreased>

### Manual Testing
<Describe any manual testing performed>

### Security Checklist
- [ ] No hardcoded secrets or credentials
- [ ] Input validation for user-provided data
- [ ] No SQL injection, XSS, or command injection risks
- [ ] Dependencies are from trusted sources

## Reviewer Notes
<Any additional context for reviewers>

---

🤖 Generated with [Claude Code](https://claude.com/claude-code)
</pr-format-manual><enforcement-manual> ### Enforcement Rules (Manual)
  1. Tests must pass: All tests must pass before merge
  2. Linter must pass: No lint errors allowed
  3. Testing documented: PR must include Testing section
  4. Security reviewed: Security checklist must be completed</enforcement-manual>

Prompt Logging Standard

Skills that produce artifacts as part of an issue (plans, scaffolds, demos, etc.) append a prompt log to .itx/<issue-number>/<NN>_<STAGE>.md. The log captures the prompt, model, and skill name so the path from human input → artifact is reproducible.

Format (markdown, append-only):

## <Stage Name>

**Stage**: <stage-slug>           # e.g. demo, plan, scaffold
**Skill**: /<skill-name>          # e.g. /create-vhs
**Timestamp**: <ISO-8601 UTC>     # e.g. 2026-05-19T15:30:00Z
**Model**: <model-id>             # e.g. claude-opus-4-7

```prompt
<verbatim user prompt that triggered the skill>

Output: <one-line description of what was produced>


**File numbering**: `00_PLAN.md`, `01_SCAFFOLD.md`, `02_EXECUTE.md`, `03_DEMO.md`, …  Match the order in which stages run for the issue.

**One log per stage**: if a stage runs multiple times, append additional H2 sections to the same file rather than creating new files.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.