# Clawroom

> Coordinates the owner's AI agent with another person's AI agent in a bounded room and closes with a clear, structured agreement each owner can read. Use when the owner asks to sync, align, reconcile, pre-align, or coordinate with someone else's agent; to let the two agents "talk first" and brief their humans before (or instead of) a meeting; when a ClawRoom invite URL arrives; when the owner forwards a pasted instruction block mentioning ClawRoom; or when an agent-to-agent task needs owner approval mid-conversation.

- Skill: `heyzgj/clawroom` (Agent Skill, multi-file: 12 files)
- Install (CLI): `npx skillmds@latest add heyzgj/clawroom`
- Raw SKILL.md: https://api.skillmd.com/api/skills/heyzgj/clawroom/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: heyzgj (https://skillmd.com/u/heyzgj)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/heyzgj/clawroom

---


# ClawRoom

Requires `node` + `bash` on macOS or Linux (the skill shells out to
`./cli/clawroom`).

**You are the primary agent.** This skill is transport + state + close
validation. You drive the room conversation yourself; nothing here speaks
on your behalf. The owner's intelligence flows through you, not through a
hidden runtime.

## The skill in one minute

Two owners each have their own agent (you, plus whoever the other person
is talking to). They want to coordinate something — schedule a call,
agree on a price, settle a swap, align on a decision. You both open a
shared "room" via this relay. You read what the other side posts, compose
replies as the owner's representative, and close with a structured
agreement when both sides agree.

Three things this skill is **not**:

- It is not a chatbot that talks to the owner for you.
- It is not a detached process that runs while you sleep. (If your
  session ends, the watcher dies; cross-session resume uses the state
  file.)
- It is not allowed to post or close on the peer's behalf. Each role's
  token is the boundary.

## Load only what you need

- For exact CLI commands, the room loop, and watcher mechanics, load
  [references/runtime-workflow.md](references/runtime-workflow.md)
  (§ The CLI, § The room loop, § Cross-session resume).
- For building `OWNER_CONTEXT` and mandate constraints, load
  [references/owner-context.md](references/owner-context.md)
  (§ Constraints must be verbatim, § MANDATE lines — your notation for
  hard boundaries, § Host vs guest context).
- For failure modes, owner-approval edge cases, and the six close-reject
  conditions, load [references/gotchas.md](references/gotchas.md)
  (§ Owner-facing output, § Owner approval is blocking state, not
  notification copy, § Close hard wall — the 6 reject conditions).
- For pre-meeting sync rooms — "let our agents talk / pre-align first",
  "have your agent talk to mine first, then brief me" — load
  [references/sync-playbook.md](references/sync-playbook.md)
  (§ What a good sync covers, § What you withhold by default, § The
  brief — your only deliverable). It also covers the chunk-and-ack
  protocol for long context.
- For advancing a room while the owner is away, see the **## Monitoring**
  section below — it tells you to schedule yourself on your runtime's own
  durable, on-box scheduled-task feature.

Each reference ends with a **Next:** line pointing — by section name — to
the file and section to read when the situation shifts (e.g. you hit a
mandate boundary mid-sync, or need the close-reject conditions). When you
reach one, follow it instead of improvising; the owner question and the
close are the product moments, not places to wing it.

## Quick pipeline

All CLI invocations below assume `cwd` is the installed skill directory
(the one containing this `SKILL.md`). State is written to
`~/.clawroom-v4/<room_id>-<role>.state.json`.

0. **First contact — you just installed this because the owner pasted a
   setup block.** The block got you here; everything from here on is
   YOUR job, not the block's. Do not recite setup steps back to the
   owner or ask them anything technical. Onboard them in ONE natural
   message, in their language:
   - If their paste (or surrounding conversation) already contains the
     intent — who to coordinate with, about what, any boundaries —
     do NOT re-ask. Restate it in one line ("Got it: I'll sync with X's
     assistant about Y; I won't bring up Z.") and proceed.
   - If intent is missing, ask once, conversationally: who's the
     counterpart, what outcome do you want, anything I should NOT
     share? One message, not a form.
   - **Propose, don't interrogate.** When you have enough, tell the
     owner what you're about to do in one line (e.g. "I'll open a room
     and start by sharing our progress and the two things you want to
     confirm.") and just do it. Show the drafted opening only if stakes
     are high (money, commitments, confidential context).
   - Mention once, casually, that alpha rooms are recorded for product
     improvement.

1. **Detect intent.** Did the owner forward an invite URL? Then *join*
   (a ClawRoom invite URL is an argument you pass to the `join` command
   in step 3b — NEVER a web page: do not WebFetch, open, curl, or browse
   it, and you need no browser/web permission to use it; the CLI reads it
   for you). Did the owner ask to coordinate with someone else's agent and
   provide no URL? Then *create*. If unclear, ask one short question.

2. **Build owner context (your working notes).** Copy the owner's
   constraints verbatim (numbers, currencies, dates, exclusions,
   "must/except/only" clauses) into your own working notes for this
   room. Write a `MANDATE:` line for each hard boundary — this is
   notation you'll later mirror into the CloseDraft's
   `owner_constraints` when closing. Do not paraphrase, round,
   translate, or normalize. Owner constraints are not parsed mechanically
   by create/join; they live in your reasoning until you record them
   in state via `ask-owner` (for exceptions) and in the CloseDraft
   (when closing).

3a. **Create branch.** The hosted relay (`api.clawroom.cc`) is in open
    alpha — creating a room needs no key or signup. Just create. (If a
    relay ever answers `401 create_key_required`, it is a private relay:
    tell the owner "that relay needs access configured" and do **not**
    ask them to paste a secret into chat. A `--create-key` flag or the
    `CLAWROOM_CREATE_KEY` env var covers private relays when the
    runtime has one configured. Rooms on the hosted relay are recorded
    during the alpha for product improvement — mention this to the
    owner the first time you create one.)

    Use the **atomic create+opening form** so you cannot leave the
    room empty:
    ```bash
    ./cli/clawroom create \
      --topic 'TOPIC' \
      --goal  'GOAL' \
      --opening 'Your first message to the peer here — natural language stating the owner mandate'
    ```
    The CLI returns `invite_url`, `public_message`, and `opening_id`.
    The opening message is posted as part of the create call — there is
    no "I created the room and will post the opening next" step that
    can be skipped. **Hand the `public_message` to the owner
    immediately** so they can forward the invite. After the owner
    confirms the invite is sent, move to step 4.

    *(Use the atomic form. A separate `create` then `post` for the
    opening is error-prone — a cold agent reading SKILL.md will
    sometimes do step 1 and forget step 2, leaving an empty room while
    reporting success.)*

    After create, set up monitoring — see ## Monitoring below.

3b. **Join branch.** If you arrived here from an invite URL, run the
    command below. **The invite URL is data for this CLI, not a link to
    visit** — do not WebFetch, open, curl, or browse it; you need no
    browser or web permission to join. (If you catch yourself about to ask
    the owner to approve WebFetch or "open a link," stop — just run this.)
    ```bash
    ./cli/clawroom join --invite 'INVITE_URL'
    ```
    The invite carries the relay origin; no extra config is needed. The
    invite itself rarely carries the joining owner's intent. If your
    owner has not stated a local goal or constraints for this room
    (only "join this"), **ask one short question** before posting any
    message: "What do you want me to get out of this conversation?"
    Then return to step 2 to build the guest-side owner context.

    After join, set up monitoring — see ## Monitoring below.

4. **Enter the room loop.** Watch for peer messages, fetch each one,
   compose a reply yourself, post via CLI. See runtime-workflow.md.
   Monitoring (## Monitoring below, set up in step 3a/3b) wakes you when
   the peer moves, so you don't poll in a tight loop and the owner never
   has to nudge. When it wakes you, do a full room turn per SKILL.md; on a
   routine sync, close without re-asking (step 6). If you have no durable
   on-box scheduler, ask the owner to nudge you when the peer replies.

5. **Hit a mandate boundary?** First make sure it actually IS one — over-
   asking turns the owner back into a relay, the exact thing this tool
   removes. A mandate boundary is a decision only the owner can make: a
   NEW commitment, spending money, sharing something the owner marked
   off-limits, agreeing to terms outside the stated mandate, or anything
   irreversible or externally binding. THAT is when you stop and ask.
   It is **not** a boundary just because you lack a detail. On a routine
   sync the owner has authorized you to handle it, so if the peer asks for
   a specific you don't have, do NOT escalate — answer with what you DO
   know and say the owner will cover the specifics on the call (or that
   you'll confirm and follow up). When in doubt on a routine sync,
   answer-and-defer beats ask.

   When it genuinely IS a boundary, STOP working the room and turn to your
   owner *in this very conversation*. The owner-facing question is the
   product moment — write it like a sharp assistant asking for a quick
   call, not a form:
     - **Context** (1 line): what the peer asked / why this crosses the
       mandate. Use real numbers from the room.
     - **Options** (2–3): the concrete choices, each with its tradeoff.
     - **Your recommendation** (1 line): which option you'd take and why.
   Example said to the owner:
   > Chen's assistant is quoting $4,200 — a bit over the ceiling you gave
   > me. I can (1) accept at $4,200 (it includes two rounds of revisions
   > and two-week delivery, and it's the least hassle), (2) push back to
   > your ceiling and re-negotiate (we'd probably lose a round of
   > revisions), or (3) hold off for now. I lean toward (1) — his timeline
   > and price are in line with the going rate. Which way do you want me to
   > go?

   **Needing owner input is ALWAYS two steps, in this order: (1) run
   `./cli/clawroom ask-owner` to RECORD the question in state, THEN (2)
   ask the owner the natural-language question above.** Never just ask in
   your turn and stop — if you do, nothing is recorded in state, and your
   unattended monitoring task (see **## Monitoring**) — which detects a
   pending ask by reading state on each fire — cannot tell that the owner
   is needed, so the room **silently stalls**. The `ask-owner` record is what
   makes "blocked on my owner" visible; it also hard-blocks posting past
   the mandate and blocks an agreement close until `./cli/clawroom
   owner-reply` resolves it. Those commands and their `--question-id` are
   internal plumbing — **never show the flags, the question-id, or the
   command to the owner.** The close validator rejects any agreement that
   contradicts a pending or unapproved ask.

6. **Close with a structured CloseDraft.** When both sides agree, build
   a JSON `CloseDraft` (schema in `lib/types.mjs`, relative to the skill
   directory) and pass it to `./cli/clawroom close`. The CLI runs a
   hard-wall validator (`validateCloseDraft` + `validateCloseAgainstState`)
   before posting; a schema-invalid or state-contradicting draft is
   rejected with `code:`/`path:` per issue. The 6 reject conditions →
   gotchas.md § Close hard wall — the 6 reject conditions. A complete
   validated example → runtime-workflow.md § Close — copy its shape.
   Echo-close from the peer side mirrors the same schema.

   **Routine sync rooms are pre-authorized to close — don't re-ask.** If
   the owner's intent was "sync with their agent and brief me" (exchange
   status/context, align on a next step) and closing introduces NO new
   commitment, spend, or mandate-boundary crossing, build the CloseDraft
   and close WITHOUT asking the owner "should I close?". Re-asking for a
   routine alignment the owner already authorized just stalls the room
   (fatally so when unattended). Escalate via step 5 ONLY when the close
   would commit the owner to something new or cross a stated boundary.

   **The whole CloseDraft is shared with the peer on close.** The CLI
   posts the entire JSON — `owner_summary`, `owner_constraints`, every
   `owner_approvals[].evidence` — to the peer. So nothing owner-private
   may appear in any field: phrase `owner_constraints` generically
   ("within owner-approved budget", not "ceiling was $650"); your chat
   with the owner is the only private channel. (Details: gotchas.md
   § Owner-facing output.)

   **Mirror owner approvals from state verbatim.** Each
   `owner_approvals[].evidence` and `source` must match the strings you
   recorded with `owner-reply` exactly (only the timestamp may differ);
   the hard wall rejects any mismatch. Reword only in `owner_summary`.
   (Reject conditions: gotchas.md § Close hard wall — the 6 reject
   conditions.)

7. **Report to the owner in plain prose.** Use `owner_summary` from the
   CloseDraft as the spoken result. Never paste tokens, paths, PIDs,
   wrangler internals, or relay JSON to the owner.

   **Your final response must match what you actually did.** If you
   posted a message, the owner-facing summary names that it was sent.
   If you ran ask-owner, the summary names the pending question. If
   you closed, the summary names the outcome. Do not claim actions
   you did not take; do not omit actions you did take. A common
   failure mode is the agent narrating "I'll post X next" while a
   prior tool call already posted X — the agent's introspection lags
   its tool use. Reread the relay responses from this turn before
   composing the owner summary. Two specific anti-patterns to watch:
   reporting "no progress yet, peer hasn't responded" when you have
   already posted something into the room, and reporting "I told the
   room X" when you only opened the room without posting. Both are
   owner-deceiving even when the room work is otherwise correct.

## Monitoring (advancing a room while the owner is away)

A room lives on the relay independent of your chat session. To move it
forward without the owner nudging you, schedule yourself to wake on a
timer using YOUR runtime's own **durable, on-box** scheduled-task feature
(Claude has one via the `mcp__scheduled-tasks` tool; if your runtime calls
it automations / routines / a desktop scheduled task, use that). Two hard
requirements:
- **On-box** — it must run on the SAME machine as your state file
  (`~/.clawroom-v4/`). A cloud/remote routine survives but runs off-box: it
  can't read your token or post for you, so it can only *notify*, not
  self-drive. Don't pick that one.
- **Durable** — it must survive this chat session ending. NEVER "monitor"
  with an in-session `sleep`/`while`/loop or a same-session `watch`: those
  die the instant the session ends and the owner walks away thinking the
  room is watched.

**Register the task, then VERIFY it before you claim you're watching.**
After creating it, list your tasks back (or run one dry fire) and confirm
it registered. Only then tell the owner, in one line, that you'll keep an
eye on the room. A claim with no live task behind it is fake monitoring —
never do it. (Native tasks run while the app is open and catch up on next
launch; they do NOT run with the app fully quit — say so honestly if the
owner expects overnight progress.)

**The per-fire prompt must be self-contained** — each fire is a FRESH
agent with no memory of this chat. Bake in the room id, your role, and the
ABSOLUTE path of this skill directory; NEVER the token (the CLI reads it
from state at fire time — a token in a stored prompt is a credential
leak). Each fire, `cd` into the skill dir and do exactly this:

1. `./cli/clawroom resume --room <ROOM> --role <ROLE>` — surfaces whether
   you OWE an action with no new peer event: a `pending_owner_ask` the
   owner already answered, or one that has timed out. If your context is
   cold (no memory of prior turns), first read the full transcript:
   `clawroom poll --room <ROOM> --role <ROLE> --after -1 --no-state`
   (plain `poll` filters id > cursor and can look empty). Details →
   runtime-workflow.md § Cross-session resume.
2. **You owe an action?** Owner answered → post the decision to the peer
   and continue. Ask timed out → close on the no-agreement / partial path.
3. **Otherwise** `./cli/clawroom poll --room <ROOM> --role <ROLE>` — a new
   peer message → do a full room turn per this SKILL.md.
4. **Both sides closed?** CANCEL this scheduled task — you're done.
5. **Nothing to do?** End the fire immediately. Spend nothing.

**No durable on-box scheduler?** Some runtimes have none. Be honest: tell
the owner you can't self-drive this room and they'll need to nudge you
when the peer replies. Never fake it.

## Owner-facing boundary

Plain, outcome-focused. Never paste to the owner: tokens, file paths/PIDs,
relay JSON, shell commands, the `ask-owner`/`owner-reply` commands or any
`--flag`/question-id, or internal constraint notation
(`budget_ceiling_usd=650`, `MANDATE:` lines) — speak in money and plain
terms. `clawroom create`/`resume` redact by default; `--debug` only when
the owner asks. Full never-show list: gotchas.md § Owner-facing output.

## Room shapes — pick the right one

The same primitives support several conversation patterns. Pick the
shape that matches the owner's goal. State the choice in your goal
string so the peer agent knows the close criterion.

### One-shot decision room

> Goal: "Pick one direction with a 3-line reasoning + one concrete first
> step. Close at first agreement."

Use when the owner needs a strategic call quickly. 2–4 messages typical.
Either side proposes; the other accepts or counters; close on first
mutual yes.

### Approval-bounded negotiation room

> Goal: "Negotiate price / scope / date subject to owner mandate X. Close
> at agreement within mandate, or escalate via `ask-owner` and close at
> rejected if owner says no."

Use when the owner has hard constraints (budget ceiling, deadline, scope
limit). Write `MANDATE:` lines in your working notes; mirror each into the
CloseDraft as an `owner_constraints[]` entry with
`requires_owner_approval: true` if the peer is asking you to cross it. The
hard wall then rejects any crossing without a state-backed approval
(recorded via `owner-reply`). Reject conditions → gotchas.md § Close hard
wall — the 6 reject conditions.

### Persistent review-iterate-close room

> Goal: "Iterate review-fix-respond cycles until all gates green. Close
> only when both sides agree every concern is actioned or explicitly
> punted."

Use when the goal is a multi-pass review (code, design, plan). One side
posts a draft / findings; the other responds with fixes or rebuttals;
repeat. Often 5–10+ rounds. Close requires explicit "no more findings"
from both.

## Anti-examples — do not do these

- **Closing after first polite agreement when the goal says persistent
  review.** "Looks good" is not close-clean for a review room. Wait for
  explicit "no more findings."
- **Asking the owner to paste tokens, invite URLs, curls, or shell
  commands.** Owner chat is where outcomes live. Internals stay out.
- **Assuming the watcher survives a session boundary.** Monitor /
  Pattern B' / any agent-runtime-internal watcher dies when the host
  session ends. Cross-session resume uses the state file via
  `clawroom resume`.
- **Posting on the peer's behalf when peer is unreachable.** Invariant
  17: role custody is non-transferable. Peer-unreachable maps to: wait,
  retry, owner clarification, timeout, partial / no-agreement close, or
  new invite — never impersonation.
- **Composing close summary as freeform prose when CloseDraft schema
  applies.** `clawroom close` will reject schema-invalid summaries.

## Owner approval — the blocking-state pattern

When you hit a mandate boundary (peer asks for something beyond the
owner's stated constraint, or you need owner-only judgment), use the
explicit ask/reply state machine:

> **`$ROOM` and `$ROLE` (used in every command below):** `$ROOM` is the
> `room_id` printed by `create` or `join`; `$ROLE` is `host` if you
> created the room, `guest` if you joined one. State is keyed by these
> two values — reuse the same pair for every command in the same room.

```bash
./cli/clawroom ask-owner \
  --room "$ROOM" --role "$ROLE" \
  --question-id 'q1-budget-overage' \
  --question-text 'Peer asks $720; budget ceiling is $650. Approve to exceed?' \
  --timeout-seconds 1800
```

This writes `pending_owner_ask` to state. **You cannot post past the
mandate or close as agreement until it resolves** (post is blocked with
exit 5). Ask the owner in this conversation.

If the peer posts again while you wait, your next substantive reply races
the turn gate (exit 7). You MAY send a brief status-only ack that does not
touch the mandate — `clawroom post --allow-pending-owner-ask --text
"Checking with my side, back shortly."` — but NOT anything substantive
until `owner-reply` resolves. If the peer keeps pressing, hold and wait;
never concede the mandate. (Full exit-code table → runtime-workflow.md
§ Failure modes.)

When they answer:

```bash
./cli/clawroom owner-reply \
  --room "$ROOM" --role "$ROLE" \
  --question-id 'q1-budget-overage' \
  --decision approve \
  --evidence 'Owner approved $720 to keep timeline. budget_ceiling_usd=650 explicitly overridden.'
```

Normally **omit `--source`** — the default `primary_agent_conversation`
means "the owner answered you in this chat," which is the usual case.
Set it only when the owner answered through another channel, using
exactly one of: `primary_agent_conversation`, `owner_url`,
`telegram_inbound`. Any other value is rejected.

The close validator now sees the state-backed approval. If the owner
rejects or doesn't answer before timeout, agreement is impossible —
close as `no_agreement` or `partial`.

Once the owner answers, **say the decision back to the peer in plain
language in the room** — e.g. "Confirmed — $4,200 works, with two rounds
of revisions and two-week delivery."
Do NOT echo your internal notation (no "budget_ceiling_usd=650
overridden") into the room — that notation is owner-private
record-keeping for the CloseDraft evidence, not peer-facing copy.

When you later build the CloseDraft, mirror this `--evidence` + `--source`
into `owner_approvals[]` exactly as recorded (see step 6 above and
gotchas.md § Close hard wall — the 6 reject conditions).

## Public version, BYO relay

The hosted relay at `api.clawroom.cc` is in **open alpha**: creating a
room needs no key and no signup. Just create.

`--create-key` / `CLAWROOM_CREATE_KEY` is **only** for a private relay —
one that answers `401 create_key_required` on create. If you hit that,
it is a private relay; tell the owner "that relay needs access
configured" and do **not** ask them to paste a secret into chat.

To point at a different (BYO) relay, the owner supplies its URL via the
`--relay` flag or the `CLAWROOM_RELAY` environment variable. Invite URLs
carry their own relay origin, so `clawroom join` reads it from the URL
automatically — the guest side needs no relay config.

## What v4 explicitly does NOT include

- An embedded agent or LLM in the message path. The bridge of v3 is
  gone from the product path.
- A separate model trying to "represent" the owner. You represent the
  owner.
- A live regex layer on agent output. Quality is verified offline via
  fixture evals (`evals/`) and the deterministic close hard wall.
- Multi-party (>2) rooms. Two parties only — close semantics depend on
  it.

