# awb-agent-manager

Runtime Host for [AI Workflow Board](../../README.md). It connects to AWB over
SSE + REST, owns managed Agent execution, advertises runtime capabilities, and
supports Hermes over the official ACP stdio protocol alongside registered CLI
adapters.

agent-manager owns SSE event delivery, persistent ticket/chat sessions,
subagent supervision, fs browser, instance heartbeats, and CLI lifecycle
management.

The package, binary, config directory, and API retain the `agent-manager` name
for compatibility. Architecturally it is an execution-plane service: AWB owns
durable Agent identity, authorization, work state, skills, collaboration
policy, and audit history. There is no editor/plugin execution fallback.

See [the Runtime Host reference](../../docs/agent-manager.md) and
[the Hermes runtime guide](../../docs/hermes-runtime.md).

```
┌─────────────────────────────────────────────────────────────────┐
│  AWB server (NestJS)                                            │
│   ├── /api/agent-manager/*    pairing, agent identity RPC       │
│   ├── /api/admin/agent-manager/*    instance dashboard, command │
│   └── SSE event stream  ─────────────┐                          │
└─────────────────────────────────────┬─┘                          │
                                      │ HTTP (Bearer key)          │
                                      ▼                            │
┌─────────────────────────────────────────────────────────────────┐│
│  awb-agent-manager  (this package)                              ││
│   ├── EventStream       SSE consumer + reconnect                ││
│   ├── EventDispatcher   route → ticket / chat / fs / command    ││
│   ├── ManagedAgents     isolate / start / stop Agent runtimes   ││
│   ├── HermesRuntime     one ACP process per durable AWB Agent   ││
│   └── InstanceHeartbeat per-process registry ping ──────────────┘
└─────────────────────────────────────────────────────────────────┘
       │ stdio
       ▼
   hermes-acp / claude / codex / antigravity / pi / custom CLI
```

## Install

### npm (recommended)

```bash
npm i -g --ignore-scripts awb-agent-manager
awb-agent-manager --version
```

`--ignore-scripts` matches what self-update does. The SLSA provenance gate
covers **our** tarball, but the ~95 transitive dependencies below it are
re-resolved from the registry at install time, and `npm i -g` would otherwise
run any `preinstall`/`postinstall` they carry as your user — no CVE required.
This package's published tree has **zero** install-script packages
(`scripts/audit-published-deps.mjs` re-checks that every night), and bin
linking is npm core rather than a lifecycle script, so the flag costs nothing.

Published to the public npm registry as
[`awb-agent-manager`](https://www.npmjs.com/package/awb-agent-manager) (unscoped);
`npm i -g` always pulls the latest release. Publishing is automated by
[`.github/workflows/publish-agent-manager.yml`](../../.github/workflows/publish-agent-manager.yml):
a push to `main` that touches the agent-manager **source** triggers a publish
whose version is **computed at publish time** as the npm registry `latest` +
patch (see [`scripts/compute-publish-version.mjs`](scripts/compute-publish-version.mjs)),
and that version is stamped into the tarball but **not committed back to
`main`**. So the `version` field in this `package.json` is only a *seed floor*
for the first-ever publish and is expected to trail npm's `latest` — a lower
value here is **by design, not drift** (ticket 433f6cbd removed the old
manual-bump model).

> **npm is the only distribution channel.** Self-update never fetches, checks
> out, or builds from a git remote — it verifies the published SLSA provenance
> and then runs `npm install -g awb-agent-manager@<verified version>`. When npm
> isn't reachable the admin badge reads "manual updates only" and nothing is
> updated automatically.

### Update channel

`AWB_AGENT_MANAGER_UPDATE_CHANNEL` selects what self-update tracks:

| Value | Behavior |
|---|---|
| _(unset)_ / `latest` | Track the published release line (default). |
| any dist-tag (e.g. `next`) | Track a pre-release line published by the same provenance-signed workflow. |
| exact version (e.g. `1.6.99`) | Pin to one published build. |
| `off` | Disable auto-update entirely; the admin badge reads "(pinned)". |

The value is validated against the npm dist-tag/version charset — anything else
falls back to `latest`, so the env var can't inject arguments into the
`npm view` / `npm install -g` calls.

### Testing an unpublished build

Use npm's own local-tarball install — no git checkout, no registry publish:

```bash
npm run build -w apps/agent-manager
npm pack -w apps/agent-manager                  # → awb-agent-manager-<v>.tgz
npm i -g ./awb-agent-manager-<v>.tgz
export AWB_AGENT_MANAGER_UPDATE_CHANNEL=off     # keep your build installed
awb-agent-manager --version
```

The install still classifies as `npm-global`, so everything except auto-update
behaves exactly like a released build. Drop the env var (or set it back to
`latest`) to rejoin the release line. For a shared pre-release, publish to a
dist-tag instead (`npm publish --tag next`) and point testers at
`AWB_AGENT_MANAGER_UPDATE_CHANNEL=next` — that path keeps the provenance gate
armed, which a local tarball does not.

### Docker

```bash
docker run --rm -it \
  -v "$HOME/.config/awb-agent-manager:/data" \
  -e AWB_AGENT_MANAGER_HOME=/data \
  ghcr.io/parnmanas/awb-agent-manager:latest
```

The image bundles `node:22-alpine` plus the manager binary. Mount a host
directory for `AWB_AGENT_MANAGER_HOME` so config + lockfile survive container
restarts. Bind-mount each agent's working directory the same way (e.g.
`-v $HOME/repos:/repos`) and configure those paths inside AWB.

## First run — pairing with an AWB server

The manager bootstraps from a one-time pairing token minted by an AWB admin.
After redeeming, the manager stores its API key and agent identity in
`$AWB_AGENT_MANAGER_HOME/config.json` (default
`~/.config/awb-agent-manager/config.json`).

1. **Mint** — In the AWB UI: _Workspace → AI Agents → Agent Manager Runtime → Pair manager…_.
   The dialog returns a raw token (long-form) and a 6-char display code; copy
   either. Both are shown only once. TTL 10 minutes, single-use.
2. **Run the wizard** — On the host that will run the manager:

   ```bash
   awb-agent-manager setup
   ```

   You'll be prompted for:
   - AWB server URL (e.g. `https://awb.example.com:7700`)
   - Pairing token (paste from step 1)
   - CLI to drive (`claude` / `codex` / `antigravity` / `pi`, default `claude`)

   The wizard calls `/api/agent-manager/pair/redeem`, then writes
   `~/.config/awb-agent-manager/config.json` with mode 0600. Output:

   ```
     ✓ paired
       agent_id     <uuid>
       workspace_id <uuid>
       apiKey       awb_abcd***xyz9
     ✓ wrote ~/.config/awb-agent-manager/config.json (mode 0600)

     Next: run `awb-agent-manager` to start the manager.
   ```

   Non-interactive form (CI / Ansible — fails fast on missing fields):

   ```bash
   awb-agent-manager setup \
     --url https://awb.example.com:7700 \
     --token ABCXYZ123 \
     --cli claude \
     --non-interactive
   ```

   `instance_id` defaults to `<hostname>-<rand6>` — pass `--instance-id <id>`
   for a stable label across re-pairings on the same box. `--force`
   overwrites an existing config.json.

3. **Start** — `awb-agent-manager`. The process registers with the AWB
   instance dashboard and starts listening for `agent_manager_command` SSE
   events.

4. **Add managed agents** — Back in AWB, open _Workspace → AI Agents_ and
   choose _New Managed Agent_. Pick the CLI (`claude` / `codex` / `antigravity` / `pi` / `custom`),
   point at a working directory, and leave _Spawn on this manager after create_
   on for one-click setup. The manager provisions a per-agent apiKey, writes its
   on-disk config + mcp-config.json, and starts routing matching ticket /
   chat / mention events to subagents that run under that agent's identity.

   Managed Codex agents also receive native MCP entries in their isolated
   `CODEX_HOME/config.toml`. AWB is marked `required = true`, uses the
   per-process `AWB_API_KEY` bearer token, and fails the Codex run if the MCP
   endpoint cannot initialize. Manager restart/rehydrate and
   `refresh_mcp_config` both repair this native config automatically.

   **PI has no credential concept at all** (not even the optional per-agent
   credential every other adapter supports) — every spawn simply inherits
   whatever the operator already configured on the manager host via `pi
   /login` (including a credential-free local llama.cpp server). **PI has no
   *native* MCP client** — its own upstream philosophy is "No MCP, build an
   extension that adds MCP support" — so instead of a native `mcp.json`,
   `prepareCliHome` writes a small dependency-free pi extension
   (`~/.pi/agent/extensions/awb-mcp-bridge.ts`, regenerated on every
   spawn_agent) that hand-rolls the MCP `initialize`/`tools/list`/`tools/call`
   handshake against AWB's Streamable HTTP endpoint using only pi's own
   `pi.registerTool()` API and Node's built-in `fetch` — no
   `@modelcontextprotocol/sdk` or other npm dependency, so there is no
   per-spawn `npm install` network-failure mode. A managed PI agent therefore
   calls `get_ticket` / `add_comment` / `move_ticket` itself, same as
   claude/codex (see `cli-adapters/pi.ts` for the verified wire details and
   ticket d5a6100d for the end-to-end transcript).

   On manager restart, agents previously spawned this way auto-rehydrate
   from disk — no need to re-click Spawn.

## Run as a background service

`awb-agent-manager service install` registers the manager so it starts on
boot/logon and auto-restarts on crash. The installer detects your host's
service manager and dispatches accordingly:

| Host                         | Backend                | Default unit path                                  |
|------------------------------|------------------------|----------------------------------------------------|
| Linux + systemd              | systemd unit           | `~/.config/systemd/user/awb-agent-manager.service` |
| Linux + Synology DSM         | rc.d boot script       | `/usr/local/etc/rc.d/awb-agent-manager.sh`         |
| Linux without systemd        | sysvinit               | `/etc/init.d/awb-agent-manager`                    |
| macOS                        | launchd                | `~/Library/LaunchAgents/com.awb.agent-manager.plist` |
| Windows                      | Task Scheduler         | task `awb-agent-manager` (logon trigger)           |

```bash
# user scope (no admin/sudo) — runs at logon, recommended for laptops
awb-agent-manager service install

# system scope — runs at boot, requires sudo / Administrator shell
awb-agent-manager service install --system

# preview without writing or running registrar
awb-agent-manager service install --dry-run

# force a specific backend (e.g. testing sysvinit on a systemd host)
awb-agent-manager service install --platform sysvinit

# remove
awb-agent-manager service uninstall [--system]
```

Notes:
- Linux user-mode systemd services stop at logout. Run
  `sudo loginctl enable-linger $USER` to keep the manager running after the
  installing user logs out.
- Synology DSM and bare sysvinit always install at system scope (the boot
  directories are root-owned). The `--system` flag is implied.
- Windows user-mode tasks fire at logon only. Re-run with `--system` from
  an elevated PowerShell for a boot-time task running as `LocalSystem`.
  The task launches through a hidden `wscript.exe` wrapper, so no npm/Node
  console window appears. When replacing a legacy task that was registered
  by an Administrator shell, the installer requests UAC approval automatically.
- macOS uses `launchctl bootstrap` on modern macOS and falls back to
  `launchctl load -w` on older releases. Logs land in `/tmp/awb-agent-manager.log`.

## Configuration

| Source                                           | Precedence       |
|--------------------------------------------------|------------------|
| `--config <path>` flag                           | 1 (highest)      |
| `$AWB_AGENT_MANAGER_HOME/config.json`            | 2                |
| `$XDG_CONFIG_HOME/awb-agent-manager/config.json` | 3 (Linux)        |
| `%APPDATA%\awb-agent-manager\config.json`        | 3 (Windows)      |
| `~/.config/awb-agent-manager/config.json`        | 4 (fallback)     |

Schema (`config.json`):

```json
{
  "url": "https://awb.example.com",
  "apiKey": "<bearer key from pairing>",
  "workspace_id": "<workspace uuid>",
  "agent_id": "<manager agent uuid>",
  "cli": "claude",
  "delegation": {
    "enabled": true,
    "max_concurrent_subagents": 4
  }
}
```

CLI flags (`awb-agent-manager --help`):

| Flag                    | Meaning                                                 |
|-------------------------|---------------------------------------------------------|
| `-c, --config <path>`   | Override config.json path                               |
| `-w, --workspace <id>`  | Override `workspace_id` from config                     |
| `-f, --force`           | Take over a lockfile owned by a stale or live owner     |
| `--dry-run`             | Load config, log what would happen, exit                |
| `-h, --help`            | Show full usage                                         |
| `-v, --version`         | Print version                                           |

Signals:

| Signal       | Behavior                                                  |
|--------------|-----------------------------------------------------------|
| `SIGTERM`/`SIGINT` | Graceful drain (stop subagents, release lock)       |
| `SIGHUP`     | Re-read `config.json` (delegation tunables hot-reload)    |
| `SIGUSR1`    | Self-update: verify provenance, drain sessions, `npm install -g` latest, re-exec (git checkout is fallback-only when npm is unavailable) |
| `SIGUSR2`    | Unconditional restart: re-exec in place, no version check / install / build. Use for on-disk config that's only read at startup (e.g. `--runtime-profile`), where SIGUSR1 would no-op because the package version didn't change |

## Development

```bash
# from this directory
npm install            # workspace install at the repo root also works
npm run build          # tsc → dist/
npm run dev            # tsx watch src/main.ts
node dist/main.js -h
```

The full AWB workspace builds via turbo from the repo root:

```bash
cd ../..               # submodules/ai-workflow-board
npm install
npm run build          # builds agent-manager + client + server
```

For deep reference (config schema, SSE event types, security model, internals)
see [`docs/agent-manager.md`](../../docs/agent-manager.md).
