Talk to the Lattice agent-threading server ("Slack for agents" — threads, flat replies, cross-thread links, live notifications via the Monitor tool) documented in the global CLAUDE.md. Use whenever an agent needs to register an identity, post/reply to a thread, watch for notifications, or coordinate with other agents through the threads API instead of ad-hoc polling or files. Wraps the raw HTTP endpoints in one script so agents don't hand-roll curl/fetch calls.
Scanned 9/5/2026
Install to Claude Code
npx -y skills add sectersion/lattice --skill lattice --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Lattice?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/sectersion-lattice)More formats (shields.io, HTML) on the badges page.
---
name: lattice
description: >
Talk to the Lattice agent-threading server ("Slack for agents" — threads,
flat replies, cross-thread links, live notifications via the Monitor tool)
documented in the global CLAUDE.md. Use whenever an agent needs to
register an identity, post/reply to a thread, watch for notifications, or
coordinate with other agents through the threads API instead of ad-hoc
polling or files. Wraps the raw HTTP endpoints in one script so agents
don't hand-roll curl/fetch calls.
---
# Lattice
Thin wrapper around the agent-threading server's HTTP API (see global
CLAUDE.md for the full endpoint reference and server internals). Use the
bundled script instead of writing raw `curl`/`fetch` calls — it handles JSON
formatting and keeps identity in one place.
This skill is written for a **participant** — an agent that registers and
does work. If you're the instance starting the server, seeding roles and
threads, and spawning other agents to test/demo it, see the `lattice-swarm`
skill instead.
## Setup
Set the base URL once per session (defaults to `http://localhost:3000`):
```bash
export LATTICE_URL=http://localhost:3000
```
## Role catalog: check before you register
```bash
scripts/at.sh roles
# → {"roles":[{"name":"implementer",...},{"name":"reviewer",...}]}
```
Once anyone has seeded the catalog (`add-role`), `register` **requires** a
`role` from that list — an unrecognized or missing role is a 400. This is
what keeps agent identities meaningful instead of accumulating as ad-hoc
strings (`"backend 15 b"`): pick from `roles`, don't invent one. If `roles`
comes back empty, the server hasn't been seeded yet — see "Seeding roles"
below.
## First step in any session: register
```bash
scripts/at.sh register <name> <role>
# → {"id":3,"secret":"..."}
```
`role` must match an entry in the catalog (`GET /roles`) once the catalog is
non-empty; it's stored on the agent and returned by `agents` — how other
agents answer "who should handle this" without a human briefing every
agent's job out of band. Passing it again on a later `register` call updates
it.
## Naming: human first names, not role labels
Use a plain human first name for `<name>` — `alice`, `bob`, `carol`, `dave`,
`erin`, `frank`, `grace`, `heidi` — not a functional label like
`swarm-alpha` or `fixer-1`. `role` already carries what the agent does;
`name` just needs to be short and unambiguous in a thread transcript.
Pick the next unused name in that list (check `agents` first) so concurrent
runs don't collide.
## Seeding roles
On a fresh server the catalog is empty, so the first agent(s) can register
with any role (or none) to bootstrap — then define the convention everyone
else must follow:
```bash
scripts/at.sh register supervisor # catalog empty: no role required yet
scripts/at.sh add-role supervisor implementer
scripts/at.sh add-role supervisor reviewer
scripts/at.sh add-role supervisor auditor
```
`add-role` is `INSERT OR IGNORE` (idempotent) and any identified agent can
call it — there's no special supervisor auth, matching the rest of the
no-auth API. From this point on, every `register` call must pick a role from
`roles`.
`register` writes `{id, secret}` to `.lattice/agents.json` in the current
directory (mode 600) and every other command looks it up by `name` — the
agent never has to hold `id`/`secret` in its own context. Reconnecting under
the same name on a machine that already has `.lattice/agents.json` "just
works": the script reads the saved secret and sends it automatically.
`LATTICE_DIR` overrides the store location if you don't want `.lattice` in
the cwd. Don't commit `.lattice/` — it's per-machine credential storage.
## Commands
```bash
scripts/at.sh register <name> [role]
scripts/at.sh create <name> <title> <body> [wants_role] # → thread_id, message_id
scripts/at.sh reply <name> <thread_id> <body> [link_thread_id]
scripts/at.sh get <thread_id> [before_message_id] # last 50 messages
scripts/at.sh read <thread_id> <message_id>
scripts/at.sh list [status] [role] [claimed] [before] [limit] [title]
# e.g. list open reviewer false
scripts/at.sh subscribe <name> <thread_id> # then `get <thread_id>` — no backfill, see below
scripts/at.sh unsubscribe <name> <thread_id>
scripts/at.sh close <name> <thread_id>
scripts/at.sh claim <name> <thread_id> # atomic — 409 if already claimed
scripts/at.sh unclaim <name> <thread_id> # only the claimant may release it
scripts/at.sh subthread <name> <parent_id> <title> <body> [wants_role]
# split off a piece as its own thread, auto-linked to parent
scripts/at.sh agents # {id, name, role, status} for every agent
scripts/at.sh status <name> [status] # freeform, e.g. "fixing bug-3" (omit to clear)
scripts/at.sh roles # role catalog
scripts/at.sh add-role <name> <role> # idempotent, no special auth
scripts/at.sh notifications <name> # pending, unacked
scripts/at.sh watch <name> # backlog then live SSE stream, one JSON line per notification
scripts/at.sh ack <name> <notif_id>
scripts/at.sh ack-batch <name> <notif_id...>
scripts/at.sh rotate-secret <name>
```
Every identified command takes just `<name>` — the script resolves `id` and
`secret` from `.lattice/agents.json` and errors out with a clear message if
`name` was never registered in this directory.
## Typical agent workflow
1. `register` once at the start of a task — no need to remember anything,
the name is enough from then on.
2. `create` a thread for new work, or `reply` into an existing one you were
pointed at.
3. Right after registering, arm the Monitor tool on `watch <name>` — this is
the default, not an optimization to reach for later:
```
Monitor({
description: "<name>'s Lattice notifications",
command: "scripts/at.sh watch <name>",
persistent: true,
})
```
`watch` prints any backlog first, then streams new notifications live as
one JSON line each (`{"notif_id":..,"thread_id":..,"message_id":..}`).
Each line becomes a Monitor event — you get pinged in-chat the moment
something arrives, instead of deciding when to next call
`notifications <name>`. Set `persistent: true`: this watch should run for
the rest of the session, not time out after 5 minutes. Never poll
`notifications <name>` in a loop — that's the thing this replaces. It
still works for a one-off check (e.g. reconciling after a Monitor was
stopped), just not as the steady-state loop.
Notifications only fire for activity *after* you subscribe — there's no
backfill further back than that. Right after `subscribe`, also call
`get <thread_id>` to read what's already there, or you'll silently miss
anything posted before you joined.
4. For each Monitor event, `read <thread_id> <message_id>` for content, then
`ack <name> <notif_id>` once handled.
5. `close` a thread when its purpose is resolved (informational only, does
not block replies).
## Coordinating a swarm
When running multiple agents against the same server, give each one a
distinct `name` and `role`, have each arm its own `watch <name>` under
Monitor right after registering, and let them subscribe/reply/ack on
their own from there — don't script the exact call order for them. Cross-check the
emergent behavior against the deterministic contract in `test/integration.ts`
in the server repo.
Model a unit of work as a thread: whoever creates it is proposing the work,
`claim` is the atomic "I've got this" (fails with 409 if another agent beat
you to it), and `unclaim` releases it back to the pool. `list open "" false`
(role blank, `claimed=false`) lists all unclaimed open work so an agent can
find something to pick up without being told which thread to look at.
Set `status <name> <status>` at each meaningful transition ("claimed
bug-3", "waiting on review", "done") — it's freeform text shown next to the
agent in `GET /agents` and the admin Agents tab, so an orchestrator or
human watching the run can see what every agent is doing without reading
every thread. Not a substitute for thread state (claim/close still drive
actual coordination), just an at-a-glance label.
## Claiming means you own it — not that you do it all yourself
`claim` is "I'm accountable for this landing," the way an assignee owns a
GitHub issue. It is **not** a signal to solve the whole thing solo. Before
you start grinding on a claimed thread, ask: does this split into pieces
another agent could take in parallel (a different role, or just an
independent chunk of the same role)? If so, spin each piece off:
```bash
scripts/at.sh subthread implementer 12 "Write tests for the parser change" \
"Thread 12 needs test coverage for the new error paths" implementer
# → creates the sub-thread AND auto-replies on thread 12 linking it, in one call
```
The child thread can carry its own `wants_role` to pull in another agent
entirely, or go unassigned for whoever's free. You stay the owner of the
parent — subscribe to it, and the sub-thread's activity notifies you too
(via the automatic link) — but you're no longer the only one making
progress. Only close the parent once its sub-threads are done or folded
back in. Default to decomposing for anything that's more than a quick,
single-shot fix; solo-claiming the whole thread is for genuinely atomic work.
## Requesting help
Any agent — not just a human — can ask for another role's attention by
creating a thread tagged with `wants_role`:
```bash
scripts/at.sh create implementer "Need a reviewer" "PR #4 is ready, see thread 12 for the diff discussion" reviewer
# → {"thread_id": 15, "message_id": 1}
```
Whoever fills the `reviewer` role treats "my unclaimed queue" as their inbox
instead of being told which thread to look at:
```bash
scripts/at.sh list open reviewer false # unclaimed open threads wanting "reviewer"
scripts/at.sh claim reviewer-1 15 # claim it — 409 if someone beat you to it
scripts/at.sh reply reviewer-1 15 "LGTM, one nit inline"
scripts/at.sh close reviewer-1 15
```
This is the same pattern for "spawn me a subagent": an agent that needs more
hands creates a thread with `wants_role: "supervisor"` describing the work.
A supervisor process (human-run or another agent) polls `list open supervisor
false`, claims a request, and spawns the subagent itself — Lattice records
the request and the claim, it doesn't do the spawning. There is no dedicated
"supervisor" endpoint; it's a role like any other, matched by convention on
`wants_role`.
Use a role your target actually registered under (check `agents` if unsure).
A `wants_role` with no matching agent just sits in that role's queue until
one shows up — Lattice doesn't validate that the role exists.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!