# AgentDocs agent setup

The homepage serves this guide to plain HTTP fetches and recognized agent
fetchers. Browser navigation gets the web app. Use `/?mode=agent` or `/llms.txt`
to request the guide explicitly, even if your fetcher sends browser headers.

Set up access on the machine where the user will run `ssh WORKSPACE.AGENT`. You manage the local keypair and SSH configuration; the user supplies an invite or approves access. The alias must log in as this named agent using its matching private key.

## 1. Create the local keypair

Inspect `~/.ssh/config` and run `ssh -G WORKSPACE.AGENT` first. Reuse an existing
alias's dedicated key only after verifying the host, username, and matching
public key. A previously working alias can stop authenticating after its key
is revoked or the workspace is reset; that does not mean the local key is bad.
Never overwrite an existing key to repair authentication.

For a new pair, use `~/.ssh/agentdocs.d/WORKSPACE.AGENT`. The `.d` directory avoids
colliding with an older private key named `~/.ssh/agentdocs`. If the chosen
directory path is itself a file, preserve it and choose another directory.
Create directories with mode 700 and private keys with mode 600. Only submit
the public `.pub` key. This POSIX-shell example creates a new unencrypted key
for unattended use, or verifies and reuses the existing pair; an existing
encrypted key requires its passphrase or a working ssh-agent arrangement:

```sh
(
  set -eu
  umask 077
  agentdocs_key_dir="$HOME/.ssh/agentdocs.d"
  agentdocs_key="$agentdocs_key_dir/WORKSPACE.AGENT"
  if [ -L "$agentdocs_key_dir" ] || { [ -e "$agentdocs_key_dir" ] && [ ! -d "$agentdocs_key_dir" ]; }; then
    echo "Key directory is a file; preserve it and choose another directory." >&2
    exit 1
  fi
  mkdir -p "$agentdocs_key_dir"
  chmod 700 "$agentdocs_key_dir"
  if [ -L "$agentdocs_key" ] || [ -L "$agentdocs_key.pub" ]; then
    echo "Key path is a symlink; inspect it and choose a dedicated file path." >&2
    exit 1
  fi
  if [ ! -e "$agentdocs_key" ]; then
    if [ -e "$agentdocs_key.pub" ]; then
      echo "Public key exists without its private key; recover the pair or choose a new path." >&2
      exit 1
    fi
    ssh-keygen -q -t ed25519 -N '' -f "$agentdocs_key"
  fi
  chmod 600 "$agentdocs_key"
  agentdocs_public=$(ssh-keygen -y -f "$agentdocs_key")
  agentdocs_public=$(printf '%s\n' "$agentdocs_public" | awk '{print $1 " " $2}')
  if [ -e "$agentdocs_key.pub" ]; then
    agentdocs_existing=$(awk '{print $1 " " $2}' "$agentdocs_key.pub")
    if [ "$agentdocs_existing" != "$agentdocs_public" ]; then
      echo "Public/private key mismatch; preserve both and resolve before enrollment." >&2
      exit 1
    fi
  else
    printf '%s\n' "$agentdocs_public" > "$agentdocs_key.pub"
  fi
  echo "Keypair verified: $agentdocs_key"
)
```

Check the command's actual exit status before reporting success. If you choose
a different key location, use that actual path in both enrollment and
`IdentityFile` below.

## 2. Enroll the public key

### With an owner-created invite

Fetch the invite URL, including its token, to get these instructions filled in for the assigned workspace and agent. Redeem it by sending JSON to:

`POST API_ORIGIN/api/agent-registrations/REGISTRATION_ID/redeem`

Set `Content-Type: application/json` and send:

```json
{"token":"INVITE_TOKEN","publicKey":"<full contents of ~/.ssh/agentdocs.d/WORKSPACE.AGENT.pub>"}
```

Use a JSON encoder when inserting the public key. A successful response contains `workspace`, `agentName`, `host`, `port`, `alias`, `username`, and `fingerprint`. The owner has already authorized this invite; no additional approval is needed. Save the enrolled identity before proceeding. If the invite is expired or already redeemed, report that result and obtain a new invite when needed; preserve the local keypair.

### Without an invite

POST JSON to `https://docbox.sh/api/agent-registrations` with only:

```json
{"workspace":"WORKSPACE","agentName":"AGENT","publicKey":"<full public .pub key>"}
```

Do not choose or send an `action`. The server determines whether to create the
workspace, add the named agent, or enroll another key for an existing agent.
The agent never submits the owner's email or handles their email verification
link. For a new workspace, the human verifies the email that will own it; for
an existing workspace, its owner must approve. Waitlist reservations still
require access approval.

A **200** response with `status: "approved"` means this exact key is already
enrolled for the requested workspace and agent. Use its connection details and
proceed to SSH verification; this response contains no session credential.

A **201** response with `status: "pending"` contains an `approvalUrl`, `id`,
`pollToken`, `expiresAt`, the connection details, and a server-selected `action`
explaining the proposed change. It creates an approval request, not a workspace
or an enrollment. Send the returned `approvalUrl` to the owner.

Wait using `GET https://docbox.sh/api/agent-registrations/REGISTRATION_ID/wait?token=POLL_TOKEN`.
Each request waits up to 25 seconds; repeat while `pending`, stop on `expired`,
and proceed only when `approved`. The server rechecks current workspace state
and ownership during approval, including when another setup completes first.
Preserve the local keypair throughout retries. A `key_already_enrolled` error
means this key belongs to a different workspace or agent; use a dedicated key
for this connection.

## 3. Configure SSH

Create or update the exact `Host WORKSPACE.AGENT` entry in `~/.ssh/config`. Preserve unrelated entries and place this specific entry before broad `Host *` defaults. Use the enrolled response's `alias` for WORKSPACE.AGENT, `host` as HostName, `port` for 22, and `username` as User. Set IdentityFile to the matching private key's actual location:

```sshconfig
Host WORKSPACE.AGENT
  HostName SSH_HOST
  User AGENT
  Port 22
  IdentityFile ~/.ssh/agentdocs.d/WORKSPACE.AGENT
  IdentitiesOnly yes
  RequestTTY no
  ConnectTimeout 10
```

Use mode 600 for the config file. `WORKSPACE.AGENT` is a local SSH alias; its HostName is the workspace's DNS hostname (normally `WORKSPACE.agentdocs.to`). The server identifies the workspace from the enrolled public key and the agent from the SSH username. The returned alias includes the environment when connecting to a preview, keeping its keys and configuration separate from other previews and production.

Keep OpenSSH host-key checking enabled. The enrollment response's `fingerprint` identifies your agent public key, not the server host key. Verify an unfamiliar server host key against a trusted operator-provided fingerprint before trusting it; stop on a changed host key.

## Run workspace commands

AgentDocs runs shell scripts over workspace documents. Start with `help`.
Quote the remote script so your local shell does not expand it. Pipes, `;`,
`&&`, `||`, variables, globbing, and redirection are supported. Documents live
in `/workspace`, the initial working directory; `ls`, `ls .`, and
`ls /workspace` list that directory. Other paths belong to a temporary virtual
filesystem. The document mount is read-only; mutations use explicit operations
that update the same shared text as the browser editor.

```sh
ssh -n WORKSPACE.AGENT 'rg -il needle -g "*.md" | sort | head -10'
ssh -n WORKSPACE.AGENT 'doc-create notes.md; doc-append notes.md "# Notes"'
ssh -n WORKSPACE.AGENT 'doc-replace-text notes.md "Notes" "Meeting notes"'
ssh -n WORKSPACE.AGENT 'doc-read notes.md'
```

To append a local Markdown file without quoting its contents:

```sh
ssh -n WORKSPACE.AGENT 'doc-create notes.md'
ssh WORKSPACE.AGENT 'doc-append notes.md --stdin' < local.md
ssh -n WORKSPACE.AGENT 'doc-read notes.md'
```

`--stdin` appends one Yjs operation; it cannot replace existing text. Input
preserves Unicode, quotes, backticks, and trailing newlines. Empty or invalid
UTF-8 input fails. Finite requests collect at most 2,000,000 bytes and require
EOF within 10 seconds, before any command executes. Oversized, timed-out, or
interrupted input commits nothing. Use `ssh -n` when no stdin is intended;
SSH clients using an API must end their input stream. Bare SSH, `help`, and
standalone `watch` do not wait for stdin. This is a finite shell, not an
interactive terminal. A request may contain only one `doc-append --stdin`
operation; this prevents buffered input from being consumed twice. Once an operation commits, a later disconnect does not
undo it; read back before retrying an append whose result is uncertain.

`doc-read` returns JSON with content and a version. `doc-insert` and
`doc-delete-text` take a UTF-16 offset and that exact version; stale edits
fail. `doc-replace-text` requires exactly one match for the expected text.
`doc-append` inserts at the current end. `doc-rename` preserves the document's
text identity, and `doc-remove` removes its path. Create refuses existing paths;
rename refuses existing destinations. Re-read after a conflict.

There is no upload or whole-file replacement command. Redirection, `tee`,
`cp`, `mv`, `rm`, and `sed -i` cannot mutate shared documents. Use temporary
paths for intermediate shell output. Read-only mutation attempts fail the entire
request, even if a later command succeeds. Pipelines use `pipefail` by default;
use `&&` or `set -e` to stop a sequence after other failures. `watch` streams committed updates and
file changes as a standalone SSH command. Native `rg` searches run within
`/workspace`. Network access and arbitrary host programs are unavailable.

After configuring your SSH alias, use this search workflow:

```sh
ssh WORKSPACE.AGENT help
ssh -n WORKSPACE.AGENT 'cat AGENTS.md'
ssh -n WORKSPACE.AGENT 'rg --files --hidden -g "*.md"'
ssh -n WORKSPACE.AGENT 'rg -n -i --hidden -g "*.md" "SEARCH_TERM" .'
```

Ripgrep exit code **0** means matches, **1** means no matches, and **2** means
a search error. Code 1 with no output is a completed search, not a broken
connection. Report no match after checking the intended files; do not retry
unrelated commands or create a matching file unless the owner requests it.

## Completion

Verify SSH directly, without piping its output through `head`, `tee`, or another
command that can mask its exit status. `ConnectTimeout 10` is built into OpenSSH
and works on macOS; no GNU `timeout` is needed. A zero exit status and workspace
help must both be present. `Permission denied` is always a failed setup.

Setup is complete when enrollment succeeds, `ssh -G WORKSPACE.AGENT` resolves the intended host, username and private key, and both `ssh WORKSPACE.AGENT` and `ssh WORKSPACE.AGENT help` exit successfully with workspace help. Report the working alias and private-key file path to the user; keep key contents private. Bare SSH displays help and closes the connection. Run each subsequent workspace command with `ssh -n WORKSPACE.AGENT 'COMMAND'`.
