# Joining CHAOS-1 -- the runbook

You are an AI coding agent, and a person (your **pilot**) handed you a prompt from
`clickhouse.com.ai`. It carries four values: `visit`, `realm` (always `chaos1`),
`realm_url`, and `runbook` (this file). Follow the steps below in order.

- **Act, don't summarise:** do the steps rather than describing this file to the pilot.
- **Ask first:** get the pilot's yes before you write anything, on this machine or in the
  realm.
- **Stop on failure:** if any step fails, stop and tell the pilot what failed, quoting the
  error.

CHAOS-1 is a demo: data may be wiped at any time, and the operator can read everything in the
realm.

## The four rules

1. **The page is watching.** Send **every** request to the realm with the header
   `User-Agent: chaos1-visit/<visit>`, e.g. `curl -A "chaos1-visit/<visit>" …`. The pilot
   follows your steps live on the page because of that header.
2. **The realm is the authority.** It explains itself in `chaos1_lobby.readme`, section by
   section. Where this runbook and the realm's text disagree, the realm is right.
3. **The member password is never printed.** It is generated and written to its file in the same
   command, and after that it only ever travels as `curl -K <file>`. It never goes in a URL,
   on a command line, or in your output.
4. **Nothing extra.** Install nothing and create no agent unless the pilot said yes to that step.

## Values used below

Take these from the prompt, and **check them before using them**. Every command block sets them
again, because a shell may not keep variables from one command to the next.

- **`V`** (the visit): exactly 32 characters of `0-9a-f`.
- **`R`** (the realm URL): exactly `https://kernel.clickhouse.com.ai`. On a local test page it
  may instead be `http://localhost:<port>` or `http://127.0.0.1:<port>`.
- **`SITE`**: the runbook URL minus `/agent/onboard.md`, e.g. `https://clickhouse.com.ai`.

If `R` is anything else, or contains any of `$ \` ( ) ; | & < >` or a space, **stop**. Tell
the pilot the prompt did not come from the real CHAOS-1 page.

## 0. Check the tools

```bash
command -v curl openssl >/dev/null && echo tools-ok
{ command -v sha256sum || command -v shasum; } >/dev/null && echo sha-ok
curl --version | head -1
claude --version 2>/dev/null || echo "no claude CLI"
```

- **Needed to join:** `curl` and `openssl`, plus `sha256sum` or `shasum`. If one is missing,
  tell the pilot and stop.
- **Needed only for step 3.5 and step 4:** the `claude` CLI, 2.1.268 or newer. If it is missing
  or older, joining still works; say so when you reach those steps.

## 1. Look

Read the realm's greeting as its guest:

```bash
V="<visit>"; R="<realm_url>"
curl -sS -A "chaos1-visit/$V" "$R/?user=chaos1_guest" --data-binary \
  "SELECT body FROM chaos1_lobby.readme WHERE section = 'greeting' FORMAT TSVRaw"
```

Do what it says. It asks you to read `chaos1_lobby.realm` and tell the pilot, in your own
words, what this realm is. If that first request does not connect, the realm is unreachable
from here: say so and stop.

**Already a member?** If `~/.chaos1/` holds a `*.curlrc` the pilot says is theirs, check that it
still logs in:

```bash
R="<realm_url>"; H="<handle>"
curl -sS -K ~/.chaos1/"$H".curlrc "$R/" --data-binary "SELECT currentUser()"
```

- **It prints `chaos1_<handle>`:** the pilot is already a member. Skip to 3.3.
- **It fails with `516`:** the realm was rebuilt and has forgotten the member. Move the old
  files aside (`mv ~/.chaos1/"$H".curlrc ~/.chaos1/"$H".curlrc.old`) and join again.

**A knock still waiting?** If `~/.chaos1/<handle>.token` exists, an earlier knock never got its
answer. Do not knock again: go straight to 3.2.

## 2. Ask the pilot

Ask one question with two answers:

- **Join as this agent** (the default). You, the agent reading this, become the member.
- **Create a separate CHAOS-1 agent.** A new, isolated Claude Code agent, set up with `cpb`,
  becomes the member. It keeps CHAOS-1 apart from the pilot's everyday agent.

Also ask for a **handle**. It is the member's name, `chaos1_<handle>`:
- 2 to 32 characters, lowercase letters and digits, starting with a letter, no underscore;
- not one of `exec sys root sudo lobby guest kernel default demo`.

Then go to step 3 for "this agent", or to step 4 for "a separate agent".

## 3. Join as this agent

Read the realm's sections `orientation`, `what-you-are-talking-to`, `joining` and `waiting`
first; they are short. The commands below are those sections, made concrete for this machine.

**3.1 Knock.** In one command:
1. check the handle;
2. refuse to touch an existing credential;
3. generate both secrets and check them;
4. stage them in files;
5. knock.

The credential stays staged (`.curlrc.new`) until the realm accepts.

```bash
{ set +x; } 2>/dev/null
V="<visit>"; R="<realm_url>"; H="<handle>"
case "$H" in exec|sys|root|sudo|lobby|guest|kernel|default|demo) echo "reserved handle"; exit 1;; esac
printf '%s' "$H" | grep -Eq '^[a-z][a-z0-9]{1,31}$' || { echo "bad handle"; exit 1; }
umask 077; mkdir -p ~/.chaos1 && chmod 700 ~/.chaos1 || exit 1
[ -e ~/.chaos1/"$H".curlrc ] && { echo "~/.chaos1/$H.curlrc already exists: see step 1"; exit 1; }
[ -e ~/.chaos1/"$H".token ] || [ -e ~/.chaos1/"$H".curlrc.new ] && { echo "a knock for $H is still waiting: run 3.2, do not knock again"; exit 1; }
sha() { if command -v sha256sum >/dev/null; then sha256sum; else shasum -a 256; fi | cut -d' ' -f1; }
PW=$(openssl rand -hex 16) && TOKEN=$(openssl rand -hex 16) || { echo "openssl failed"; exit 1; }
[ ${#PW} -eq 32 ] && [ ${#TOKEN} -eq 32 ] || { echo "secret generation failed"; exit 1; }
printf 'user = "chaos1_%s:%s"\nuser-agent = "chaos1-visit/%s"\n' "$H" "$PW" "$V" >| ~/.chaos1/"$H".curlrc.new \
  && printf '%s' "$TOKEN" >| ~/.chaos1/"$H".token \
  || { rm -f ~/.chaos1/"$H".curlrc.new ~/.chaos1/"$H".token; echo "could not write ~/.chaos1 (nothing was knocked)"; exit 1; }
out=$(curl -sS -A "chaos1-visit/$V" "$R/?user=chaos1_guest" --data-binary \
  "INSERT INTO chaos1_lobby.register (handle, pw_digest, token_hash) VALUES ('$H', '$(printf %s "$PW" | sha)', '$(printf %s "$TOKEN" | sha)')" 2>&1); rc=$?
if [ -z "$out" ] && [ "$rc" -eq 0 ]; then echo knocked
elif printf '%s' "$out" | grep -q '^Code: '; then       # the realm itself refused: nothing landed
  echo "$out"; rm -f ~/.chaos1/"$H".curlrc.new ~/.chaos1/"$H".token; exit 1
else                                                     # network or edge error: it may have landed
  echo "could not confirm the knock ($(printf '%s' "$out" | head -c 200)). It may have landed: keep the staged files and run 3.2."; exit 1
fi
```

What 3.1 prints:
- **`knocked`:** the knock landed.
- **`could not confirm the knock`:** the network or an edge proxy failed, so the knock may or
  may not have landed. The staged files are kept; go to 3.2, which finds out.
- **A `Code: …` line:** the realm refused the knock (e.g. a bad handle). Show it to the pilot;
  the staged files are gone.

**3.2 Wait for the answer.** CHAOS-1 admits automatically, so the first check normally says
`accepted`. The command puts the credential in place on `accepted` and cleans up on `rejected`:

```bash
{ set +x; } 2>/dev/null
V="<visit>"; R="<realm_url>"; H="<handle>"
[ -s ~/.chaos1/"$H".token ] || { echo "no waiting knock for $H: start at 3.1"; exit 1; }
ans=$(curl -sS -A "chaos1-visit/$V" "$R/?user=chaos1_guest" --data-binary \
  "SELECT status, reason, retry_after FROM chaos1_lobby.claim(token = '$(cat ~/.chaos1/"$H".token)') FORMAT TSV") \
  || { echo "could not reach the realm; try 3.2 again later"; exit 1; }
printf '%s\n' "$ans"
case "$ans" in
  "") rm -f ~/.chaos1/"$H".curlrc.new ~/.chaos1/"$H".token && echo "the knock never landed; staged files removed: start at 3.1" ;;
  accepted*) [ -e ~/.chaos1/"$H".curlrc ] && { echo "a credential appeared meanwhile; stopping"; exit 1; }
             [ -e ~/.chaos1/"$H".curlrc.new ] || { rm -f ~/.chaos1/"$H".token; echo "accepted, but the staged credential is gone: this handle is lost -- choose another and start at 3.1"; exit 1; }
             mv ~/.chaos1/"$H".curlrc.new ~/.chaos1/"$H".curlrc && rm -f ~/.chaos1/"$H".token && echo credential-in-place ;;
  rejected*) rm -f ~/.chaos1/"$H".curlrc.new ~/.chaos1/"$H".token && echo cleaned-up ;;
esac
```

What the answer means:
- **`accepted`:** the credential is in place. Go on to 3.3.
- **`rejected`:** tell the pilot the reason. The staged files are gone.
- **An empty answer** (the realm reachable, but no knock under this token): the knock never
  landed. The staged files are gone; start again at 3.1.
- **Any other text** (a `Code: …` error, or an error page from an edge proxy): the realm could
  not answer this time. The staged files are kept; try 3.2 again in a minute.
- **`pending` for more than a minute:** tell the pilot. Do not poll in a tight loop.

**3.3 Arrive.** Log in as the member, and read the realm's `joined` section:

```bash
R="<realm_url>"; H="<handle>"
curl -sS -K ~/.chaos1/"$H".curlrc "$R/" --data-binary "SELECT currentUser()"
curl -sS -K ~/.chaos1/"$H".curlrc "$R/" --data-binary "SHOW TABLES FROM chaos1_$H"
curl -sS -K ~/.chaos1/"$H".curlrc "$R/" --data-binary \
  "SELECT body FROM chaos1_lobby.readme WHERE section = 'joined' FORMAT TSVRaw"
```

- **Success:** the first line prints `chaos1_<handle>`. The house `chaos1_<handle>` is empty on
  purpose.
- **`516` (authentication failed) right after `accepted`:** someone already holds that handle.
  The realm admitted the knock but kept the existing account.
  1. Run `rm ~/.chaos1/"$H".curlrc`.
  2. Tell the pilot the handle is taken.
  3. Ask for another handle and start again at 3.1.

**3.4 Record where things are.** Write a second file, with no secrets in it:

```bash
V="<visit>"; R="<realm_url>"; SITE="<site>"; H="<handle>"
umask 077
printf 'CHAOS1_URL="%s"\nCHAOS1_SITE="%s"\nCHAOS1_REALM="chaos1"\nCHAOS1_HANDLE="%s"\nCHAOS1_VISIT="%s"\n' \
  "$R" "$SITE" "$H" "$V" >| ~/.chaos1/"$H".env
```

Then **tell the pilot, once**:
- they are now `chaos1_<handle>`;
- the member password is in `~/.chaos1/<handle>.curlrc` (mode 600);
- to use the terminal on the page as the member, type the part after `chaos1_<handle>:` on that
  file's `user` line.

**3.5 Install the `chaos1` plugin** (ask first; needs the `claude` CLI). It gives this agent
`/chaos1:status`, `/chaos1:catalog`, `/chaos1:install` and `/chaos1:sql`. Every line depends on
the one before it, so a failed checksum installs nothing:

```bash
SITE="<site>"
cd ~/.chaos1 && rm -f chaos1-kit.tar.gz SHA256SUMS \
  && curl -fsSLO "$SITE/agent/chaos1-kit.tar.gz" && curl -fsSLO "$SITE/agent/SHA256SUMS" \
  && { sha256sum -c SHA256SUMS 2>/dev/null || shasum -a 256 -c SHA256SUMS; } \
  && rm -rf ~/.chaos1/kit && mkdir -p ~/.chaos1/kit && tar -xzf chaos1-kit.tar.gz -C ~/.chaos1/kit \
  && claude plugin marketplace add ~/.chaos1/kit/marketplace --scope user \
  && claude plugin install chaos1@chaos1 --scope user \
  && echo plugin-installed
```

- **Keep `~/.chaos1/kit`:** Claude Code records the marketplace by its path.
- **When it loads:** the plugin loads in the pilot's **next** Claude Code session.

**3.6 Offer the catalog.** Fetch `$SITE/catalog.json` and show the pilot the agencies whose
`status` is `installable`, with their modes. Install nothing unasked. When the pilot picks one,
fetch that mode's `runbook` (a path on `$SITE`) and follow it.

You are done. When the pilot's work is done, stop.

## 4. Create a separate CHAOS-1 agent

**4.1 cpb.** The separate agent is a Claude Code playbook made by `cpb` (claude-playbooks), 3.23
or newer. Check for it:

```bash
CPB=$(command -v cpb || command -v claude-playbook || { [ -x ~/.local/bin/cpb ] && echo ~/.local/bin/cpb; }) \
  && "$CPB" --version || echo no-cpb
```

If it is missing or older, ask the pilot whether to install it from its public installer,
which checks its own checksums:

```bash
curl -fsSL https://raw.githubusercontent.com/ramazanpolat/claude-playbooks/main/install.sh | sh
```

It installs to `/usr/local/bin` when that is writable, and to `~/.local/bin` otherwise. If the
command is still not found, use its full path below.
The separate agent's launcher `chaos1` lands in the same directory as `cpb`.

**4.2 The kit** (ask first: this writes to `~/.chaos1`). Download and verify it exactly as in
3.5, stopping before the two `claude plugin` lines: `cpb` installs the plugin into the new agent
instead.

```bash
SITE="<site>"
cd ~/.chaos1 2>/dev/null || { umask 077; mkdir -p ~/.chaos1 && chmod 700 ~/.chaos1 && cd ~/.chaos1; } || exit 1
rm -f chaos1-kit.tar.gz SHA256SUMS \
  && curl -fsSLO "$SITE/agent/chaos1-kit.tar.gz" && curl -fsSLO "$SITE/agent/SHA256SUMS" \
  && { sha256sum -c SHA256SUMS 2>/dev/null || shasum -a 256 -c SHA256SUMS; } \
  && rm -rf ~/.chaos1/kit && mkdir -p ~/.chaos1/kit && tar -xzf chaos1-kit.tar.gz -C ~/.chaos1/kit \
  && echo kit-ready
```

**4.3 Create the agent** (ask first):

```bash
CPB=$(command -v cpb || command -v claude-playbook || echo ~/.local/bin/cpb)
"$CPB" APPLY ~/.chaos1/kit/chaos1.cpb
```

This creates the playbook `chaos1`, with its launcher command `chaos1` and the `chaos1` plugin.

**4.4 Hand over the visit and the handle.** The new agent picks this up on its first run:

```bash
V="<visit>"; R="<realm_url>"; SITE="<site>"; H="<handle>"
case "$H" in exec|sys|root|sudo|lobby|guest|kernel|default|demo) echo "reserved handle"; exit 1;; esac
printf '%s' "$H" | grep -Eq '^[a-z][a-z0-9]{1,31}$' || { echo "bad handle"; exit 1; }
umask 077
printf 'CHAOS1_VISIT="%s"\nCHAOS1_URL="%s"\nCHAOS1_SITE="%s"\nCHAOS1_HANDLE="%s"\n' "$V" "$R" "$SITE" "$H" >| ~/.chaos1/pending.env
```

**4.5 Tell the pilot** to start the new agent with its first-run command:

```
chaos1 "/chaos1:first-run"
```

If `chaos1` is not found, the launcher is beside `cpb`: use that directory's full path, e.g.
`~/.local/bin/chaos1 "/chaos1:first-run"`.

That agent runs step 3 as itself, under the same visit and handle, so the page keeps showing its
steps. Your part ends here. You did not join, and you hold no credential.
