# Host Bridge and Host Profiles
The AKK Host Bridge lets a compatible controller Host use AKK without
forking this repository. The Host starts one foreground MCP/stdio process,
binds it to its trusted current session, and owns that process for the whole
integration lifetime.
```text
controller Host
└─ foreground AKK Host Bridge (MCP over stdio)
├─ the existing 19 semantic AKK tools
├─ one startup-selected Host Profile v1
├─ Host-owned monitor and Terminal Watch lifecycle
└─ the existing Session, Turn, Store, callback, and terminal core
```
The Bridge is an adapter around the existing tool and state model, not a
second implementation. The OpenClaw native plugin remains unchanged and does
not route its callbacks through `command_json_v1`.
## Choose an integration level
AKK defines three integration levels:
1. **Bundled Profile.** AKK can ship a reviewed Profile for a common Host. A
user selects its reserved ID and writes no integration code. The registry
is currently empty, so this repository does not yet claim a bundled Host
Profile; `agent-knock-knock host-profile list` is authoritative.
2. **User Profile.** A compatible Host uses a local JSON file such as
[`command-json-starter.json`](../examples/host-profiles/command-json-starter.json).
This needs no AKK fork, production-code change, or upstream pull request.
3. **External thin connector.** If the Host cannot satisfy the v1 drivers, a
separate Host-specific package translates its API at the stable Bridge
boundary. This requires programming, but it still does not fork or copy
AKK core.
A level-3 connector owns two small edges: it starts AKK's MCP/stdio Bridge and
maps the Host's tool API to that MCP client; it also exposes a non-shell
callback executable that reads the body from stdin, injects it into the exact
Host session, and returns the exact delivery/message acknowledgement JSON.
The connector remains an external package and does not implement AKK's tools,
Store, Turn, Watch, or callback-outbox state machine.
Configuration-only integration at level 1 or 2 requires all of the following:
- The Host can launch and own a foreground MCP server over stdio, keeping the
child's stdout exclusively for MCP messages.
- The Host can put the trusted current session ID in a named child-process
environment variable. It must not obtain that identity from a model tool
argument.
- The Host has an absolute executable that can inject a callback body into
that exact session without a shell.
- That executable accepts the exact AKK delivery and message IDs and emits a
bounded JSON acknowledgement that echoes both IDs.
- Any callback credentials can be supplied in the Host process environment
and named in a narrow Profile allowlist.
If one of those conditions is impossible—for example, the Host exposes only a
custom authenticated RPC API, cannot act as an MCP client, or cannot return an
exact acknowledgement—use an external thin connector. A Profile does not load
arbitrary JavaScript or native adapter code.
## Start from the example
Create the packaged starter Profile and change its Host identity, executable,
argument shape, environment names, and acknowledgement pointers:
```sh
agent-knock-knock host-profile example > ./my-agent.json
```
This command works from a normal npm/global installation; a source checkout is
not required.
The example assumes Host `my-agent` version `1.8.0`, controller session
environment variable `MY_AGENT_SESSION_ID`, and callback credential
`MY_AGENT_TOKEN`. It also explicitly forwards `PATH` because npm-installed
command shims commonly use `#!/usr/bin/env node`. Validate both the document
and its Host compatibility:
```sh
agent-knock-knock host-profile validate ./my-agent.json \
--host my-agent \
--host-version 1.8.0
```
`--host` and `--host-version` must be supplied together when compatibility is
checked. The Bridge and Host Profile doctor always require both. Run the doctor
from the same trusted Host environment that will launch the Bridge:
```sh
MY_AGENT_SESSION_ID='trusted-session-id' \
MY_AGENT_TOKEN='credential-from-host-runtime' \
agent-knock-knock doctor \
--host-profile ./my-agent.json \
--host my-agent \
--host-version 1.8.0
```
The doctor checks Profile parsing, exact Host/version compatibility, trusted
session resolution, and whether the callback path and resolved symlink target
are an executable, non-shell regular file. It does not send a callback.
The Host then launches this exact long-running command as its MCP/stdio server:
```sh
MY_AGENT_SESSION_ID='trusted-session-id' \
MY_AGENT_TOKEN='credential-from-host-runtime' \
agent-knock-knock host-bridge \
--profile /absolute/path/to/my-agent.json \
--host my-agent \
--host-version 1.8.0
```
Use an absolute Profile path in the Host's server configuration unless that
configuration also fixes the child working directory; relative paths are
resolved from the Bridge process working directory.
In production, the Host's process configuration should inject those
environment values rather than placing credentials in a checked-in script.
The optional `--store-dir
` selects the normal AKK Store location. The
Profile is selected once when the process starts; it is not a model-visible
tool parameter.
Use the following command to inspect built-in availability independently of
local files:
```sh
agent-knock-knock host-profile list
```
## Host Profile v1 reference
The packaged, editor-readable structural contract is
[`schemas/host-profile-v1.schema.json`](../schemas/host-profile-v1.schema.json).
Its resolvable raw-GitHub `$id` is also the value used by starter Profiles.
`host-profile validate` remains the authoritative semantic validator for
constraints JSON Schema cannot express directly, such as requiring one
arbitrary mapping value to mean `accepted`, requiring two configured JSON
Pointers to differ, and the complete normalized-path/aggregate-argv checks.
Runtime parsing is strict and fail-closed: every object rejects unknown fields,
unsupported versions are rejected, strings and arrays are bounded, and user
Profiles cannot use the reserved `builtin-` namespace or replace a registered
built-in ID. A Profile is administrator-controlled, code-equivalent
configuration because it selects an executable.
Top-level fields have these meanings:
| Field | Meaning |
| --- | --- |
| `$schema` | Recommended editor/schema hint. If present, it must be the exact v1 schema URL. |
| `schema`, `version` | Exact protocol identity: `agent-knock-knock/host-profile`, version `1`. |
| `id`, `revision` | Stable Profile identity and administrator-controlled revision. |
| `compatibility` | Exact Host ID plus one to four explicit SemVer comparators, such as `>=1.8.0 <2.0.0`. |
| `controllerContext` | Trusted controller-session resolver and binding scope; v1 supports only `environment_v1`. |
| `callback` | Callback delivery configuration; v1 supports only `command_json_v1`. |
The command-line Host ID must exactly equal `compatibility.host`, and its
`--host-version` must be an exact SemVer version satisfying every comparator
in `compatibility.range`. Changing the selected file after Bridge startup
causes child operations to fail closed on its captured fingerprint; update the
revision when appropriate and restart the Bridge deliberately.
### `environment_v1`
```json
{
"driver": "environment_v1",
"sessionIdVariable": "MY_AGENT_SESSION_ID",
"scope": "startup_v1"
}
```
`sessionIdVariable` names the environment variable from which the Bridge reads
the trusted controller session at startup. The variable name must be uppercase
and safe. Dynamic-loader and shell startup variables such as `DYLD_*`,
`LD_PRELOAD`, `NODE_OPTIONS`, and `BASH_ENV` are rejected. The resolved session
is captured into AKK's private callback route; the model cannot provide or
override it. Names beginning `AKK_HOST_PROFILE_` are reserved for the private
Bridge relay and cannot be used as session or callback allowlist variables.
`scope` is optional. When omitted, its effective value is `startup_v1`, which
preserves the original Host Profile v1 document and fingerprint behavior.
`startup_v1` binds every callback to the one trusted controller session
captured when the Host Bridge starts; callback delivery requires the persisted
route to match that session exactly.
`route_bound_v1` is an extension point for a Host-native thin connector that
serves multiple Host sessions in one process. Profile identity, revision,
transport, and fingerprint remain trusted and exact, but
`${controller.session_id}` is populated from the immutable controller session
already persisted in each callback route. It is never taken from a callback
tool argument. The generic MCP/stdio `host-bridge` command deliberately rejects
this scope because that standalone Bridge owns exactly one startup session.
Use `host-profile validate` to inspect `standalone_bridge_compatible`;
`doctor --host-profile` reports an explicit `standalone_bridge_scope` error for
a route-bound Profile.
Long-lived work such as Terminal Watch captures the complete route (Profile
identity, revision, transport, and controller session) when the user creates
the Watch. A later shared lifecycle pass may deliver that route, but must not
rebuild it from the lifecycle process's current Profile. After a native Host
restart, a new Profile revision therefore fails closed instead of adopting an
old Watch or routing it to a replacement Agent.
### `command_json_v1`
`command_json_v1` starts `callback.executable` directly with an argv array and
`shell: false`. The executable must be a normalized absolute path to a
non-shell program. It must be present and executable before the Bridge starts.
The argv templates support only these placeholders:
| Placeholder | Value | Allowed destination |
| --- | --- | --- |
| `${controller.session_id}` | Startup-trusted session, or the persisted route session for `route_bound_v1` | argv |
| `${envelope.delivery_id}` | Exact callback delivery ID | argv |
| `${envelope.message_id}` | Exact callback event/message ID | argv |
| `${envelope.idempotency_key}` | Stable logical-delivery deduplication key | argv |
| `${envelope.body}` | User-facing callback message body | stdin only |
The arguments must include the session, delivery, and message placeholders.
The idempotency placeholder is optional for Hosts whose callback CLI supports
an explicit deduplication key, and is strongly recommended when available.
Arbitrary environment expansion, command substitution, shell syntax, malformed
interpolation, and putting the callback body in argv are rejected. The `stdin`
field must be exactly `${envelope.body}`: the callback process receives the
event body text on stdin, not a serialized callback envelope.
`callback.environment.allow` is the complete pass-through allowlist for the
callback child. Only variables both named there and present in the Bridge
environment are forwarded. Unlisted variables are omitted; unsafe shell and
dynamic-loader variables cannot be allowlisted. Keep secrets out of the
Profile itself and list only the credential names the callback executable
actually needs.
If the executable starts with a `/usr/bin/env` shebang, `PATH` must be in the
allowlist so the interpreter can be resolved; doctor reports an error when it
is missing. This intentionally widens interpreter lookup, so prefer a native
binary or an absolute interpreter shebang and remove `PATH` when the Host CLI
supports that form.
Both execution limits are mandatory. `timeoutMs` is an integer from 1 through
30,000. `maxOutputBytes` is an integer from 1 through 1,048,576 and bounds
captured process output. The starter uses 8 seconds and 64 KiB.
### Exact acknowledgement
The callback executable writes one JSON document to stdout. The Profile uses
JSON Pointers to find its disposition, acceptance ID, acknowledged delivery
ID, and acknowledged message ID. For the starter Profile, an accepted result
looks like this:
```json
{
"result": {
"status": "accepted",
"acceptance_id": "my-agent-acceptance-42"
},
"request": {
"delivery_id": "the-exact-akk-delivery-id",
"message_id": "the-exact-akk-message-id"
}
}
```
The host-specific value at `disposition.jsonPointer` is translated through
`mapping` into exactly one generic outcome:
| Generic outcome | Meaning |
| --- | --- |
| `accepted` | The Host accepted the exact callback. A non-empty acceptance ID and exact echoed delivery/message IDs are required. |
| `retryable_failure` | The Host explicitly says a later delivery attempt may succeed. |
| `permanent_failure` | The Host explicitly rejects the delivery permanently. |
| `uncertain` | A side effect may have occurred, but exact acceptance is not proven. |
Every mapped acknowledgement must echo the exact delivery and message IDs.
An unknown mapping value, malformed or missing JSON, mismatched IDs, timeout,
or output-limit failure cannot be upgraded to success. A timeout or invalid
acknowledgement after a possible side effect is classified as uncertain and is
not blindly retried. This preserves the existing callback outbox's recovery,
lease, deduplication, and settlement behavior.
## Trust boundary
The Host Profile, Host ID/version, trusted session, executable, argv,
credentials, environment allowlist, acknowledgement rules, and Profile
selection all belong to the administrator/Host boundary. None is accepted from
the 19 model-facing semantic tool calls. MCP inputs are validated against the
existing closed tool schemas before the shared tool implementation runs.
The Bridge or Host-native connector privately passes a fingerprinted Profile
selection and route to its AKK CLI and monitor children. The Store receives
only the immutable callback route/envelope data required by the existing
callback contract; it does not
persist arbitrary callback commands or credential values. Callback stdout is
interpreted only through the configured bounded pointers and is not treated as
new executable configuration.
Logs go to stderr so stdout remains an MCP protocol stream. Do not wrap the
Bridge or callback command in a shell, and do not let a model edit the selected
Profile or the Host process environment.
## Lifecycle and shutdown
After MCP connects, the Bridge runs the shared Host-owned lifecycle: one
startup reconciliation followed by non-overlapping periodic sweeps for managed
Turn monitors and Terminal Watches. The two phases have independent error
boundaries, so one phase failure does not suppress the other.
The Bridge remains a foreground child owned by one Host. Closing its stdin or
sending `SIGINT`/`SIGTERM` stops new MCP work, cancels future lifecycle sweeps,
drains in-flight lifecycle and accepted tool work, and exits. The Bridge does
not daemonize, install a service, elect a supervisor, coordinate multiple
Hosts, or move pending work from one Host to another.
AKK may still run the existing per-Turn monitor worker processes used by its
core task lifecycle. Those workers are task workers, not a standalone Bridge
supervisor. Callback delivery requires the configured Host callback executable
and its receiving Host session to be available; the v1 design does not promise
Watch or callback continuity after the owning Host exits.
OpenClaw continues to use its native in-process adapter, callback transport,
and lifecycle registration exactly as before. Existing OpenClaw users do not
need a Host Profile, a callback command, or an MCP/stdio Bridge migration.