{"id":46729963,"url":"https://github.com/likesjx/openclaw-plugin-ansible","last_synced_at":"2026-03-23T16:01:08.252Z","repository":{"id":336339290,"uuid":"1149041351","full_name":"likesjx/openclaw-plugin-ansible","owner":"likesjx","description":"Distributed coordination layer for OpenClaw - one agent, multiple bodies","archived":false,"fork":false,"pushed_at":"2026-02-28T18:15:13.000Z","size":1424,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-28T21:21:40.002Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/likesjx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-03T16:52:11.000Z","updated_at":"2026-02-28T18:15:16.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/likesjx/openclaw-plugin-ansible","commit_stats":null,"previous_names":["likesjx/openclaw-plugin-ansible"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/likesjx/openclaw-plugin-ansible","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/likesjx%2Fopenclaw-plugin-ansible","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/likesjx%2Fopenclaw-plugin-ansible/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/likesjx%2Fopenclaw-plugin-ansible/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/likesjx%2Fopenclaw-plugin-ansible/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/likesjx","download_url":"https://codeload.github.com/likesjx/openclaw-plugin-ansible/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/likesjx%2Fopenclaw-plugin-ansible/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30863009,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-23T14:38:03.667Z","status":"ssl_error","status_checked_at":"2026-03-23T14:38:01.683Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-03-09T15:00:26.394Z","updated_at":"2026-03-23T16:01:08.245Z","avatar_url":"https://github.com/likesjx.png","language":"TypeScript","funding_links":[],"categories":["Skills vs Plugins: What's the Difference?"],"sub_categories":["Bundled Plugins"],"readme":"# OpenClaw Plugin: Ansible\n\n**Secure mesh control plane for OpenClaw: invite/join handshake, durable routing, and auditable delegation contracts.**\n\nAnsible enables a single agent identity (e.g., \"Jane\") to operate seamlessly across multiple devices. It synchronizes tasks, messages, and shared context in real-time using CRDTs (Yjs) over a secure mesh network (Tailscale), with explicit safety and governance gates for high-risk operations.\n\nThis repo also documents a pragmatic way to use Ansible as a reliable inter-agent communication substrate today: treat the shared Yjs document as the durable inbox, and treat auto-dispatch as an optimization (not the only delivery mechanism).\n\n## Four Pillars\n\n| Pillar | What It Does |\n|---|---|\n| Ring of Trust | Invite/join admission, auth-gate tickets, token lifecycle, and optional signed capability manifest verification |\n| Mesh Sync | Yjs CRDT replicated state over Tailscale for durable messages, tasks, and context |\n| Capability Routing | Capability contracts with delegation+execution skill pair semantics and governance gates |\n| Lifecycle Ops | Lock sweep, retention, coordinator sweeps, receipts, backpressure, and escalation controls |\n\n## Full Functional Surface\n\n1. Invite/join onboarding with token exchange and optional auth-gate websocket ticket handshake before Yjs attach.\n2. Durable shared-state transport for messages and tasks with per-agent delivery tracking.\n3. Reconcile heartbeat + retry logic so missed task/message injection is recovered automatically.\n4. Send receipts so operators can see when agents place work onto the mesh.\n5. Capability lifecycle (publish/unpublish/list/health/evidence) with provenance trust controls.\n6. Delegation policy distribution + per-node ACK tracking.\n7. Task governance (claim/accept/close, SLA sweep, backpressure policy, escalation controls).\n8. Admin controls (gateway admin nomination/distribution plus scoped token patterns).\n\n## Delegation + Execution Skill Pair Model\n\n1. A capability publish registers a contract, not just a label.\n2. Each capability contract references a **delegation skill** (requester side) and an **execution skill** (executor side).\n3. The delegation skill defines when/how to create tasks/messages and expected ACK/completion semantics.\n4. The execution skill defines accept-to-close lifecycle behavior in one run, including replies and error paths.\n5. Publishing/unpublishing updates routing eligibility, and lifecycle evidence records install/wire outcomes per target.\n\n## Key Concepts\n\n### Hemispheres vs. Friends (Default: Friends)\n\nAnsible coordinates **hemispheres** — mirrored instances of the *same* agent identity that share memory, context, and purpose. Think of it like one brain controlling multiple bodies:\n\n| | Hemispheres (Ansible) | Friends / Employees |\n|---|---|---|\n| **Identity** | Same agent (e.g., \"Jane\" on VPS + \"Jane\" on Mac) | Different agents (e.g., \"Jane\" and \"Alex\") |\n| **Memory** | Shared via CRDT sync | Separate memory stores |\n| **Purpose** | Same goals, different capabilities | Different roles and responsibilities |\n| **Communication** | Self-to-self (direct, efficient) | Inter-agent (polite, contextual) |\n| **Session** | Shared session state | Independent sessions |\n\nA hemisphere is your agent's presence on another machine.\n\nIn many setups, you do *not* want every agent to see cross-node context or have inbound ansible messages routed into the default agent. In those setups, treat nodes as **friends/employees** and centralize ops in a single operator agent (for example, an \"Architect\").\n\n### Node Topology\n\n- **Backbone nodes** (always-on) — Servers, VPS instances. Handle long-running tasks, scheduled work, background coordination. Host the Yjs WebSocket server.\n- **Edge nodes** (intermittent) — Laptops, desktops. Have local filesystem access, run interactively with the user. Connect to backbone on startup.\n\n## Prerequisites\n\n### 1. OpenClaw\n\nInstall OpenClaw on all nodes:\n\n```bash\nnpm install -g openclaw\n```\n\nEach node needs a working OpenClaw gateway (`openclaw gateway` or managed via launchd/systemd).\n\n### 2. Tailscale\n\nAnsible uses Tailscale for secure, zero-config networking between nodes.\n\n1. **Install Tailscale** on all nodes: [tailscale.com/download](https://tailscale.com/download)\n2. **Sign in** to the same Tailscale network (tailnet) on every node\n3. **Enable MagicDNS** in your Tailscale admin console — this lets you use hostnames like `jane-vps` instead of IPs\n4. **Verify connectivity** between nodes:\n   ```bash\n   tailscale ping \u003cother-node-hostname\u003e\n   ```\n\n**Important Tailscale details:**\n- Backbone peers in the ansible config MUST use Tailscale MagicDNS hostnames or Tailscale IPs (100.x.y.z), NOT SSH aliases or public IPs\n- If running inside Docker, Tailscale runs on the *host*, not inside the container. The container reaches Tailscale peers via the host's network\n- The ansible WebSocket port (default 1235) is separate from the OpenClaw gateway port (default 18789) — never mix them\n\n### 3. Ansible Skill (Recommended)\n\nInstall the companion skill so your agent knows how to use ansible:\n\n```bash\ncd ~/.openclaw/workspace/skills\ngit clone https://github.com/likesjx/openclaw-skill-ansible.git ansible\n```\n\nRestart your OpenClaw gateway to pick up the skill.\n\nTo enforce base ansible skills across all configured agent workspaces on a gateway:\n\n```bash\nopenclaw ansible skills sync --skill ansible\nopenclaw ansible skills verify --skill ansible\n```\n\n`sync` links the skill into each agent workspace and is safe by default (it will not replace existing mismatched paths unless you pass `--force-replace`).\n\n### Sync Skill Registry Entries Across Workspaces\n\nIf you want custom skills (for example `ansible-codex-comm`) to be slash-addressable in multiple workspace contexts, sync the `### Available skills` block in `AGENTS.md`:\n\n```bash\njust sync-agents-skills-dry-run\njust sync-agents-skills\n```\n\nThis uses `scripts/sync-agents-skills.sh` and reads target workspaces from `~/.openclaw/openclaw.json`.\n\n## Installation\n\n### 1. Install the Plugin\n\nOn every node:\n\n```bash\nopenclaw plugins install likesjx/openclaw-plugin-ansible\n```\n\nFor local development:\n```bash\nopenclaw plugins install /path/to/repo --link\n```\n\n### 2. Configure\n\nAdd the `ansible` plugin to `~/.openclaw/openclaw.json` on each node.\n\n#### Backbone Node (VPS / Docker)\n\n```jsonc\n// ~/.openclaw/openclaw.json\n{\n  \"plugins\": {\n    \"entries\": {\n      \"ansible\": {\n        \"enabled\": true,\n        \"config\": {\n          \"tier\": \"backbone\",\n          \"listenPort\": 1235,\n          \"listenHost\": \"0.0.0.0\",\n          \"authGate\": {\n            \"enabled\": true,\n            \"nodeIdParam\": \"nodeId\",\n            \"inviteParam\": \"invite\",\n            \"ticketParam\": \"ticket\",\n            \"requireTicketForUnknown\": true,\n            \"authPort\": 1236,\n            \"exchangePath\": \"/ansible/auth/exchange\",\n            \"ticketTtlSeconds\": 60,\n            \"requireNodeProof\": true,\n            \"rateLimitMax\": 30,\n            \"rateLimitWindowSeconds\": 60\n          },\n          \"manifestTrust\": {\n            \"allowUnsignedLegacy\": true,\n            \"trustedPublisherKeys\": {\n              \"vps-jane\": \"-----BEGIN PUBLIC KEY-----\\n\u003ced25519-pubkey\u003e\\n-----END PUBLIC KEY-----\"\n            }\n          },\n          \"capabilities\": [\"always-on\"]\n        }\n      }\n    }\n  }\n}\n```\n\nWhen running in Docker, expose the port bound to your Tailscale IP:\n\n```yaml\n# docker-compose.yml\nservices:\n  jane:\n    ports:\n      # Bind to Tailscale IP only (NOT 0.0.0.0) for security\n      - \"100.x.y.z:1235:1235\"\n```\n\n#### Edge Node (Mac / Laptop)\n\n```jsonc\n// ~/.openclaw/openclaw.json\n{\n  \"plugins\": {\n    \"entries\": {\n      \"ansible\": {\n        \"enabled\": true,\n        \"config\": {\n          \"tier\": \"edge\",\n          \"backbonePeers\": [\n            \"ws://jane-vps:1235\"\n          ],\n          \"dispatchHeartbeatSeconds\": 20,\n          \"sendReceiptAgents\": [\"architect\"],\n          \"capabilities\": [\"local-files\", \"voice\"]\n        }\n      }\n    }\n  }\n}\n```\n\n`backbonePeers` must use Tailscale MagicDNS hostnames or Tailscale IPs. SSH config aliases do NOT work here.\n\nWhen `authGate.enabled=true`, unknown nodes can be admitted in two ways:\n\n`ws://jane-vps:1235/?nodeId=\u003cnew-node-id\u003e\u0026invite=\u003cinvite-token\u003e`\n\nKnown authorized nodes reconnect with `nodeId` only.\n\nFor stricter admission, set `requireTicketForUnknown=true` and use one-time short-lived tickets:\n\n```bash\n# on inviter/backbone\nopenclaw ansible ws-ticket --token \u003cinvite-token\u003e --node \u003cnew-node-id\u003e --ttl-seconds 60\n```\n\nThen connect with:\n\n`ws://jane-vps:1235/?nodeId=\u003cnew-node-id\u003e\u0026ticket=\u003cws-ticket\u003e`\n\nYou can also mint tickets via HTTP exchange (no gateway admin token required):\n\n```bash\ncurl -sS -X POST http://jane-vps:1236/ansible/auth/exchange \\\n  -H 'content-type: application/json' \\\n  -d '{\n    \"inviteToken\": \"\u003cinvite-token\u003e\",\n    \"nodeId\": \"\u003cnew-node-id\u003e\",\n    \"nonce\": \"n-123456\",\n    \"clientPubKey\": \"\u003cPEM-public-key\u003e\",\n    \"clientProof\": \"\u003cbase64-signature\u003e\"\n  }'\n```\n\nThen connect with returned `ticket`:\n\n`ws://jane-vps:1235/?nodeId=\u003cnew-node-id\u003e\u0026ticket=\u003cticket\u003e`\n\n### Architect-Managed (Recommended for Multi-Agent Ops)\n\nIf you want ansible to be operated only by a dedicated agent (e.g., Architect), disable:\n- prompt context injection\n- auto-dispatch of inbound ansible messages into the default agent\n\n```jsonc\n{\n  \"plugins\": {\n    \"entries\": {\n      \"ansible\": {\n        \"enabled\": true,\n        \"config\": {\n          \"tier\": \"edge\",\n          \"backbonePeers\": [\"ws://jane-vps:1235\"],\n          \"injectContext\": false,\n          \"dispatchIncoming\": false\n        }\n      }\n    }\n  }\n}\n```\n\nIn this mode, the operator agent should poll and respond using tools like:\n- `ansible_read_messages`\n- `ansible_send_message`\n\n## Reliability \u0026 Delivery Semantics (If You Want To Rely On It)\n\nAnsible has two distinct mechanisms:\n\n1. **Durable state replication**: messages/tasks/context are written into the shared Yjs document and replicated across nodes.\n2. **Auto-dispatch (optional)**: when a node observes inbound work (messages, and explicitly-assigned tasks) in the shared Yjs doc, it can inject that work into the agent loop as a normal inbound turn.\n\nWhat this means today:\n\n- **Messages are durable** (persist in the Yjs doc; readable via `ansible_read_messages`; visible in context injection if enabled).\n- **Auto-dispatch is best-effort realtime + reconnect-safe**:\n  - New messages dispatch immediately while connected.\n  - On reconnect (provider `sync=true`), the dispatcher reconciles backlog deterministically (timestamp order) and injects any undelivered items.\n  - Heartbeat reconciliation (default every 20s) re-scans pending deliveries so missed observe events do not strand messages/tasks.\n  - Dispatch failures are retried with exponential backoff (with jitter) instead of being \"seen forever\".\n\nSend/delegate visibility:\n\n- `ansible_send_message` and `ansible_delegate_task` emit a compact send receipt message.\n- Default receipt recipients are:\n  - gateway admin agent\n  - plus any configured `sendReceiptAgents`\n\nIf you want to \"completely rely\" on Ansible for inter-agent communication, treat the shared Yjs doc as the source of truth and the dispatcher as the delivery worker. You can still keep manual tools (`ansible_read_messages`, `ansible_find_task`) as an operator backstop.\n\nFor a concrete protocol and improvement plan, see `docs/protocol.md`.\nFor the practical \"how do I add a new agent/gateway\" guide, see `docs/setup.md`.\n\n### 3. Bootstrap the Network\n\n1. **Start the backbone**: Restart OpenClaw on the VPS\n2. **Bootstrap** (run on the backbone node):\n   ```bash\n   openclaw ansible bootstrap\n   ```\n3. **Invite edge nodes** (run on backbone):\n   ```bash\n   openclaw ansible invite --tier edge --node \u003cexpected-node-id\u003e\n   ```\n4. **Join** (run on each edge node):\n   ```bash\n   openclaw ansible join --token \u003ctoken-from-invite\u003e\n   ```\n\n## How It Works\n\n### Message Dispatch\n\nWhen one hemisphere sends a message, the ansible dispatcher automatically injects it into the receiving hemisphere's agent loop — just like a Telegram or Twitch message would. The agent processes it as a full turn and can reply, call tools, or delegate tasks.\n\nReplies are delivered back through the Yjs document automatically.\n\nImportant: backlog is durable and will also be delivered on reconnect via reconciliation; this is what makes restarts/offline edges reliable.\n\n### Session Isolation\n\nEach sender gets a separate ansible session (`ansible:{nodeId}`). Conversation history is preserved per-hemisphere, so ongoing coordination has continuity. This mirrors how Telegram creates per-chat sessions.\n\n### State Sync\n\nAll state is synchronized via Yjs CRDTs:\n- **Messages**: Inter-hemisphere communication\n- **Tasks**: Delegated work items with claim/complete lifecycle\n- **Context**: Current focus, active threads, recent decisions\n- **Pulse**: Online status and heartbeat data\n\n## Agent Tools\n\n| Tool | Description |\n|---|---|\n| `ansible_status` | Check who's online, what they're working on, pending tasks |\n| `ansible_delegate_task` | Create a task for another hemisphere |\n| `ansible_claim_task` | Pick up a pending task |\n| `ansible_complete_task` | Mark a claimed task as done |\n| `ansible_send_message` | Send a message (targeted or broadcast) |\n| `ansible_update_context` | Update your current focus, threads, or decisions |\n| `ansible_read_messages` | Read messages (unread by default) |\n| `ansible_mark_read` | Mark messages as read |\n| `ansible_delete_messages` | **Operator-only emergency purge** (destructive; strongly discouraged for agent workflows) |\n| `ansible_get_coordination` | Read coordinator configuration (who coordinates, sweep cadence) |\n| `ansible_set_coordination_preference` | Record your preferred coordinator/cadence (per-node preference) |\n| `ansible_set_coordination` | Set coordinator configuration (initial setup or last-resort failover) |\n| `ansible_set_retention` | Configure coordinator roll-off (daily prune of closed tasks by TTL) |\n| `ansible_get_delegation_policy` | Read shared delegation policy + per-agent ACK records |\n| `ansible_set_delegation_policy` | Coordinator-only publish/update delegation policy (+ optional notify) |\n| `ansible_ack_delegation_policy` | Record this agent's ACK for the current policy version/checksum |\n\n`ansible_delete_messages` is intentionally high-friction (`confirm` token + required justification + explicit filters) and should only be used by human operators for emergency cleanup. It is hard-gated to nodes that advertise capability `admin`.\n\n## CLI Commands\n\n```bash\nopenclaw ansible status              # Show network health and nodes\nopenclaw ansible nodes               # List authorized nodes\nopenclaw ansible tasks               # View shared task list\nopenclaw ansible send --message \"hi\" # Send a manual message\nopenclaw ansible retention set       # Configure closed-task roll-off (coordinator-only service)\nopenclaw ansible messages-delete --dry-run --from architect --reason \"Emergency cleanup of stale chatter\"\nopenclaw ansible delegation show     # Show policy + ACK status\nopenclaw ansible delegation set      # Publish policy from markdown file (coordinator-only)\nopenclaw ansible delegation ack      # ACK current policy\nopenclaw ansible capability list     # List published capability contracts + eligibility\nopenclaw ansible capability publish --id cap.example --name \"Example\" --version 1.0.0 --owner executor --delegation-skill-name ansible-delegate-example --delegation-skill-version 1.0.0 --executor-skill-name ansible-executor-example --executor-skill-version 1.0.0 --contract schema://ansible/cap.example/1.0.0\nopenclaw ansible capability publish --id cap.highrisk --name \"High Risk\" --version 1.0.0 --owner executor --delegation-skill-name ansible-delegate-highrisk --delegation-skill-version 1.0.0 --executor-skill-name ansible-executor-highrisk --executor-skill-version 1.0.0 --contract schema://ansible/cap.highrisk/1.0.0 --approval-artifact CAB-1234 --approval-note \"Approved in CAB on 2026-03-03\"\nopenclaw ansible capability unpublish --id cap.example\nopenclaw ansible tasks claim \u003ctaskId\u003e --eta-seconds 900 --plan \"scan, patch, validate\"   # emits accepted ACK contract\nopenclaw ansible bootstrap           # Initialize as first node\nopenclaw ansible invite --tier edge --node \u003cexpected-node-id\u003e  # Generate node-bound invite token\nopenclaw ansible join --token \u003ctok\u003e  # Join with invite token\n```\n\nCapability publish provenance (`G2_PROVENANCE`) verifies signed manifests with configured trusted keys:\n\n- signature formats: `ed25519:\u003cbase64\u003e` or `ed25519:\u003ckeyId\u003e:\u003cbase64\u003e`\n- trust source: `manifestTrust.trustedPublisherKeys`\n- legacy fallback: `manifestTrust.allowUnsignedLegacy` (only when `signedManifestRequired=false`)\n- high-risk governance: when `riskClass=high` and `requiresHumanApprovalForHighRisk=true`, publish requires `approval_artifact_id` (CLI: `--approval-artifact`)\n- publish-path safety: manifest secret-like literals are blocked before publish; lifecycle metadata is redacted for sensitive keys/patterns\n\n### Gateway Transport Security (CLI -\u003e Gateway)\n\nBy default, the CLI targets local loopback (`http://127.0.0.1:\u003cport\u003e`), which is acceptable for local-only traffic.\n\nFor remote gateway calls, use HTTPS:\n\n```bash\nexport OPENCLAW_GATEWAY_URL=\"https://gateway.example.com\"\n```\n\nSecurity guardrail:\n- The CLI refuses non-loopback `http://` endpoints by default.\n- To override intentionally (not recommended), set:\n  `OPENCLAW_ALLOW_INSECURE_REMOTE_HTTP=1`\n\n### External Coding Agent Token Lifecycle (Recommended)\n\nUse a two-step flow so admins never hand out long-lived tokens directly:\n\n```bash\n# 1) Admin issues temporary invite (single-use, short TTL)\nopenclaw ansible agent invite --id codex --ttl-minutes 15 --as admin --token \"$OPENCLAW_ANSIBLE_TOKEN\"\n\n# 2) Agent accepts invite and receives permanent token (rotated on accept)\nopenclaw ansible agent accept --invite-token \u003ctemp_invite_token\u003e \\\n  --write-token-file ~/.openclaw/runtime/ansible/codex.token\n```\n\nNotes:\n- Invite tokens are one-time and expire automatically.\n- Accepting an invite mints a permanent `agent_token` and invalidates the invite.\n- Any other outstanding invites for the same agent are revoked after successful accept.\n- Admin can inspect invite state with:\n  `openclaw ansible agent invites` (or `openclaw ansible agent invites --all`).\n- Admin can inspect non-secret auth lifecycle metadata with:\n  `openclaw ansible agent list` (token hint + issued/rotated/accepted timestamps).\n- Admin-sensitive operations require a valid admin `agent_token` (invite, token issue, destructive message delete).\n\n### External Agent Rotation Runbook\n\nTwo supported rotation paths:\n\n1. Immediate rotate (admin-driven):\n```bash\nopenclaw ansible agent token-issue --id codex\n```\n\n2. Re-invite rotate (recommended for unattended coding agents):\n```bash\nopenclaw ansible agent invite --id codex --ttl-minutes 15 --as admin --token \"$OPENCLAW_ANSIBLE_TOKEN\"\nopenclaw ansible agent accept --invite-token \u003ctemp\u003e --write-token-file ~/.openclaw/runtime/ansible/codex.token\n```\n\nRecommended policy:\n- Rotate every 30 days (or immediately after suspected exposure).\n- Prefer re-invite flow when you need explicit handoff/acceptance proof.\n\n### Automatic Token Storage Options\n\nChoose one primary storage path per coding agent:\n\n1. Environment variable (simple)\n```bash\nexport OPENCLAW_ANSIBLE_TOKEN=\"\u003cagent_token\u003e\"\n```\n\n2. Restricted runtime file (recommended baseline)\n```bash\nopenclaw ansible agent accept --invite-token \u003ctemp\u003e \\\n  --write-token-file ~/.openclaw/runtime/ansible/codex.token\nchmod 600 ~/.openclaw/runtime/ansible/codex.token\n```\n\n3. OS key vault / secret manager (best for production)\n- macOS: Keychain\n- Linux: `pass`, Secret Service, or cloud secret manager\n- Windows: Credential Manager / DPAPI-backed store\n\nFor automation, retrieve from vault at process start and export into `OPENCLAW_ANSIBLE_TOKEN` in-memory only.\n\n## Updating (Maintainers + Users)\n\n### Maintainers (this repo)\n\nThis plugin is typically installed from GitHub, so **`dist/` must be committed**.\n\n1. Make changes in `src/` and/or docs.\n2. Build: `npm ci \u0026\u0026 npm run build`\n3. Verify `dist/` changed as expected.\n4. Commit both `src/` and `dist/` (plus docs), then push.\n\n### Release Preflight (Plugin + Skill)\n\nFor npm plugin release readiness in this repo:\n\n```bash\nnpm run release:preflight\n```\n\nFor ClawHub skill readiness against the skill repo:\n\n```bash\nnpm run test:skill:preflight -- --skill-dir=/Users/jaredlikes/code/openclaw-skill-ansible\n```\n\nFor full dual-track release steps, see:\n- `docs/release-dual-track-v1.md`\n\n### Users (machines running OpenClaw)\n\nAfter updating the plugin:\n\n1. Update the plugin checkout (either via `openclaw plugins update ansible` if you have an install record, or by re-running `openclaw plugins install likesjx/openclaw-plugin-ansible`).\n2. Run `openclaw ansible setup` to align skill + config (use `--dry-run` first if desired).\n3. Restart the gateway (`openclaw gateway restart`, or your supervisor).\n\n`openclaw ansible setup` intentionally updates **skill + config only**. Plugin code update remains a separate explicit step.\n\n### Gateway Deploy Hygiene (Recommended)\n\nTo avoid recurring `dist/*` merge conflicts on gateways:\n\n1. Use the repo Node version from `.nvmrc` (`22.22.0`).\n2. Deploy from a clean checkout only.\n3. Use:\n\n```bash\n./scripts/safe-deploy-pull.sh\n```\n\nThis script:\n\n1. fails fast if tracked files are dirty,\n2. runs `git pull --ff-only`, and\n3. runs `npm run build`.\n\nIf it fails with a dirty tree, resolve/stash local changes first (do not force pull).\n\n## Troubleshooting\n\n### Connection Refused\n- Check backbone is running: `openclaw ansible status`\n- Check firewall: `sudo ufw allow 1235/tcp` (or allow on Tailscale interface only)\n- Docker: Ensure `listenHost: \"0.0.0.0\"` is set in the backbone config\n- Try using the Tailscale IP directly: `ws://100.x.y.z:1235`\n\n### Tailscale Issues\n- Run `tailscale ping \u003chostname\u003e` to verify the tunnel\n- Ensure MagicDNS is enabled in your Tailscale admin console\n- Inside Docker containers, Tailscale hostname resolution depends on host DNS — if DNS is broken on the host, containers will fail too\n\n### \"Ansible not initialized\"\n- The gateway must be running and the Yjs document must be synced\n- For edge nodes, wait for the first successful sync with the backbone\n\n### Node ID Shows Container Hash Instead of Hostname\n- When running inside Docker, the hostname is the container ID (e.g., `2ad9255a2f3e`), not the Tailscale hostname\n- This is cosmetic — messages still route correctly because the dispatcher processes all new messages regardless of `to` field\n\n## Known Issues\n\n- **Gemini provider `.filter()` crash**: If using `google-gemini-cli` provider and the session transcript contains a corrupted `toolResult` message, the pi-ai library crashes with \"Cannot read properties of undefined (reading 'filter')\". Workaround: reset the session with `/new` or switch to a different provider. This is an upstream bug in `@mariozechner/pi-ai`.\n\n## Architecture\n\nSee [docs/architecture.md](docs/architecture.md) for detailed technical architecture.\n\n## Naming and Trademark Notice\n\nThis project's \"Ansible\" name references the fictional ansible communication concept from science fiction and is unrelated to Red Hat Ansible or Ansible Automation Platform.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flikesjx%2Fopenclaw-plugin-ansible","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flikesjx%2Fopenclaw-plugin-ansible","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flikesjx%2Fopenclaw-plugin-ansible/lists"}