Skip to content
Back to skills

Bus Protocol

ASecurity

Use when creating, modifying, or reviewing anything that reads or writes the Zyggy bus: envelope fields, canonical form and HMAC signing, ULID file names, tenant-prefixed paths via BusPaths, the job state machine (jobs → claimed → reports / rejected), write rules per machine, the git write sequence and push-retry rule, the registry file, and schema versioning. Checklist form of founding spec §4/§5/§14. Use for: adding an envelope field, a new state move, a new CLI verb that submits or reads, ...

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 10, 2026
ai-agentsgoshellnodetestinggit

Works with

  • cli

Security analysis

A100/100

Scanned October 10, 2026

npx -y skills add zyggy-org/zyggy --skill bus-protocol --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Bus Protocol?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Bus Protocol
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/zyggy-org-bus-protocol/badge)](https://www.skillsdirectory.com/skills/zyggy-org-bus-protocol)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: bus-protocol
description: "Use when creating, modifying, or reviewing anything that reads or writes the Zyggy bus: envelope fields, canonical form and HMAC signing, ULID file names, tenant-prefixed paths via BusPaths, the job state machine (jobs → claimed → reports / rejected), write rules per machine, the git write sequence and push-retry rule, the registry file, and schema versioning. Checklist form of founding spec §4/§5/§14. Use for: adding an envelope field, a new state move, a new CLI verb that submits or reads, a Hub tool that touches the bus, or reviewing a PR for protocol drift."
metadata:
  argument-hint: "Describe the bus task, e.g. 'add attempt counter to job envelopes', 'implement the rejected move', 'review BusRepository against §4'"
---

# Bus Protocol — checklist for code that touches `zyggy-bus`

The bus is a private git repository. One file per message, state encoded in the path, every file HMAC-signed. **§4 of the founding spec is normative and `PROTOCOL.md` in `zyggy-core` must match it verbatim** — so any change to what is below is an Open Question for the technical-analyst and a ⚠️ Risk Area in a plan, never a quiet code edit.

> Source of truth: `_specs/00 - Personal Agent Platform — Technical Specification.md` §4 (protocol), §5 (transport), §9 (design rules), §14 (tenancy, schema). This skill restates them as checks; when in doubt the spec wins.

## Layout and paths

```
tenants/<org>/
  policy.yaml                 # signed by Central
  registry/<machine>.yaml     # published by each node's discover
  nodes/<machine>/jobs/       # new jobs addressed to <machine>
  nodes/<machine>/jobs/claimed/
  nodes/<machine>/jobs/rejected/
  nodes/<machine>/reports/    # written only by <machine>
  nodes/<machine>/context/    # written only by <machine>
archive/YYYY-MM/              # moved by Central only
```

- [ ] Every path is produced by `BusPaths` (tenant, machine, state, id). No string concatenation elsewhere. Grep for `"nodes/"`, `"jobs/"`, `"tenants/"` outside `BusPaths` → violation.
- [ ] The tenant prefix is present from day one; v1 tenant is `geoffrey`.
- [ ] File name = ULID + `.md`. ULIDs are generated once at envelope creation; a report/context file reuses its own new ULID and references the job through `in_reply_to`.

## Write rules (who may touch what)

- [ ] A sender writes only into `nodes/<target>/jobs/` (and `nodes/central/context/` when the target is Central).
- [ ] A machine writes only its own `reports/`, `context/`, `registry/<machine>.yaml`, and moves files between its own `jobs/`, `jobs/claimed/`, `jobs/rejected/`.
- [ ] Only Central writes `archive/`.
- [ ] Two writers never touch the same file, so `git pull --rebase` never conflicts on content. If a new feature would break this, stop and raise it.

## Envelope

YAML front matter + Markdown body. The body is **data**, never instructions to the receiving agent.

Common fields: `id`, `schema` (1), `tenant`, `type` (`job | report | context`), `from`, `to`, `created` (ISO-8601 UTC), `in_reply_to`, `priority` (`low | normal | high`), `key_id` (`<tenant>/<n>`), `sig`.
Job fields: `deadline`, `project`, `agent`, `worktree`, `allowed_tools`, `report_back`, `timeout_minutes`, `attempt`.
Report fields: `status` (`done | failed | timeout | rejected`), `reason` (closed enum), `started`, `finished`, `duration_seconds`, `cost_usd`, `input_tokens`, `output_tokens`, `model`, `files_changed`, `diff_ref`.
Context fields: `scope` (`project:<name> | machine | general`); body = one `[stated]`/`[observed]` fact per line.

- [ ] Unknown fields are preserved on round trip (parse → serialise) so older nodes do not strip newer fields.
- [ ] A node rejects `schema` with a higher major than it supports: `status: rejected`, `reason: schema_unsupported`.
- [ ] `to` must equal the receiver's machine name; otherwise reject.

## Signature

`sig` = HMAC-SHA256 over the canonical form: front matter with `sig` removed, keys sorted, YAML re-serialised with `\n` line endings, then `\n---\n`, then the body byte-for-byte.

- [ ] `EnvelopeSigner.Canonicalize` is the single implementation. Nothing else re-serialises front matter for signing.
- [ ] Every change to canonicalisation ships with golden files in `tests/golden/` (input envelope, canonical bytes, expected signature for the test key).
- [ ] Keys come from `ISecretStore` by `key_id`; rotation accepts both `k1` and `k2` for 7 days. Never a key in a file, an option class, or a test string that looks like a real one.
- [ ] Verification happens **before** any parsing of the body for execution and before any model call. Failure → `git mv` to `jobs/rejected/` + a `reports/` entry with `status: rejected`.

## State machine

| From | To | Actor | Git operation |
|------|----|-------|---------------|
| (none) | `jobs/<id>.md` | sender | add + commit + push |
| `jobs/` | `jobs/claimed/` | target node | `git mv` + commit + push; push rejected → pull, retry once, else skip |
| `jobs/claimed/` | `reports/<id>.md` + job deleted | target node | add report, `git rm` job, commit, push |
| `jobs/claimed/` older than `timeout_minutes` + 15 min | `jobs/` | Central sweep | `git mv` back, `attempt: n+1`; after 3 attempts → report `status: failed` |
| `reports/`, `context/` older than 14 days | `archive/YYYY-MM/` | Central | `git mv` |

- [ ] Every move is a `git mv` (or add + `git rm`) in one commit, never a copy + delete across commits.
- [ ] Commit message: `<type> <id> <from>→<to>`.
- [ ] A claim is skipped, not failed, when the project lock is held (`<project>/.claude/zyggy.lock`, younger than 2 h).

## Git write sequence (§5)

- [ ] `pull --rebase` → local change → `commit` → `push`. On push rejection: `pull --rebase`, retry at most 3 times with 1–3 s jitter, then leave the change uncommitted, log an error, let the next poll retry.
- [ ] Never leave the working copy dirty across iterations: on start-up `git status --porcelain` non-empty → `git stash` to a named stash + warning.
- [ ] Identity per machine: `zyggy (<machine>) <machine>@zyggy.org`.
- [ ] Shell out to the system `git` through `IProcessRunner`; credentials are git's business (deploy key / credential helper), never the code's.

## Poll loop (§5) — for code that decides *when* to pull

- [ ] Conditional GET on the commits endpoint with `If-None-Match`; `304` is silent; `200` → store ETag, `git pull --rebase`, process `nodes/<me>/jobs/`.
- [ ] Interval 10 s inside work hours, 60 s outside, ±2 s jitter, one in-flight request.
- [ ] `403/429` or `x-ratelimit-remaining < 100` → back off to 120 s until `x-ratelimit-reset`. Network error → exponential 10 s → 5 min.
- [ ] Unconditional `git fetch` + compare `origin/main` every 15 min regardless of ETag.
- [ ] `PollState` is a pure record; the transition function has no I/O and is unit-tested with a fake clock.

## Registry file

`registry/<machine>.yaml`: `machine`, `os`, `last_seen`, `work_hours`, `health`, `projects[]` (`name`, `path`, `default_branch`, `agents`, `skills`, `summary`).

- [ ] Regenerated on service start and hourly; committed only when content differs, excluding `last_seen` (updated at most every 6 h).

## Review checklist for a PR touching the bus

1. Which of the tables above changed? If any → is there a resolved Open Question and a `PROTOCOL.md` update in the same deliverable?
2. Are all new paths from `BusPaths`? Are all new git calls through `GitClient` → `IProcessRunner`?
3. Are there golden files for any canonical-form change?
4. Does every failure path end in a report with a closed-enum `reason` or a rejection move, never an exception to the loop?
5. Is there an integration test against the bare repo for the new move (see `integration-testing` skill)?
6. Does any envelope body get interpreted as instructions anywhere? It must not.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in ai-agents →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.