Files
pulse/docs/AGENT_SUBSTRATE.md
T
pulse-triage[bot] 0615b692ed Keep external-agent setup read-only and outcomes evidence-bound
Replace the credential-argument probe recommendation with private, bounded reads. Explain least-scope client setup, private configuration and preserved proxy access; distinguish scope-only writes, terminal events and partial verification from recovered workloads. Keep the adapter entry point and shipped guide consistent without changing runtime behaviour.

Contract-Neutral: Existing external-agent documentation and its copied/rendered regression checks only; no endpoint, scope, policy, adapter or runtime contract changes.
Change-source: pulse-maintainer
2026-10-09 23:05:09 +01:00

220 lines
13 KiB
Markdown

# Pulse agent integrations
Pulse exposes a set of HTTP endpoints so an external agent can read your
infrastructure and act on it with the same context Pulse Patrol and the Pulse
Assistant have. Claude Desktop, Claude Code, OpenCode, other MCP clients, and
plain HTTP consumers can all drive it.
This page explains what those endpoints offer and what ships to help you
connect to them. Open **Settings → API Access → Agent integrations** to inspect
your running instance's manifest and generate a client configuration.
**Start read-only.** Connecting an external client is a separate trust decision:
it can read infrastructure context and, with broader credentials, change
monitoring or act on workloads. A tool listing or readiness badge is not an
execution grant or proof of recovery. Use only a client and model provider
permitted to receive the data in those responses.
## Connect without granting control
1. Inspect the manifest on the running server, not just this page. Its
`requiredScopes` is the union for the whole advertised surface, **not the
minimum for a read-only client**. Read each capability's `scope`, `actionMode`
and `approvalPolicy`; `scope_only` writes do not use the action approval loop.
2. Create a dedicated, suitably expiring **`monitoring:read`** token for fleet
and resource context. Keep its organisation/resource restrictions. Do not
grant `monitoring:write`, `settings:write`, `ai:execute` or action scopes to
remove a denied-tool error or make an all-scope readiness indicator pass.
A client can list tools its token cannot call.
3. Keep the credential in your client's private, user-local secret configuration
or supported secret store. `pulse-mcp` reads `PULSE_API_TOKEN` from its
environment, not a token flag. Do not enter a real token in a project-shared
`.mcp.json` or `opencode.json`, command argument, URL, repository or transcript.
Generated snippets contain placeholders; they do not secure the saved file.
Local administrators and the client process can still read its environment.
4. Use loopback only when the client runs on the Pulse host. For another machine,
configure your existing verified **HTTPS** origin; keep certificate and
authentication checks enabled. A URL copied from the browser is not proof
that a desktop client can reach or trust it. Do not expose a backend port or
weaken a proxy boundary merely to connect a client.
5. Read existing context for one intended resource and check the organisation,
canonical ID and observation time privately. Names and VMIDs can repeat
across installations. A successful response does not establish complete or
current provider collection. Keep findings, notes, command output and
infrastructure identities out of public diagnostic logs and reports.
For a bounded HTTP check, prepare the private header file and define `pulse_api`
**in the same Bash session** using [API authentication](API.md#-authentication).
That helper saves responses to new owner-only files, ignores curl defaults and
uses deadlines without redirects or retries. It is example code, not an installed
Pulse command. Adapt its origin for remote HTTPS as that guide describes.
```bash
pulse_api GET /api/agent/capabilities
pulse_api GET /api/agent/fleet-context
```
These are separate read-only checks, not an action walkthrough. Discovery is
public; only the protected fleet read checks token access. Inspect each saved
response privately. A 401/403, redirect or incomplete response is not an empty
fleet. Do not run a workload action, change operator state, induce a finding or
enable Debug just to test connectivity. Revoke an unused or exposed token in
**API Access**; deleting a client configuration alone does not revoke it.
## What the endpoints offer
**Discovery.** A manifest at `/api/agent/capabilities` lists every
agent-consumable capability with its name, description, HTTP method and path,
required auth scope, response shape, stable error codes, and a deduplicated
`requiredScopes` summary for the whole surface. It also carries the Pulse
Intelligence Core, Patrol, Assistant, and MCP surface contract, including
which affordances each supported operator surface exposes. The manifest needs
no token, so an agent can introspect Pulse before you issue it credentials.
**Depth.** `/api/agent/resource-context/{id}` returns the situated picture of
one resource in a single read. That covers identity, operator-set state,
active findings, pending approvals, and recent actions including refused
dispatches and verification probe outcomes. Stable token prefixes such as
`plan_drift:` and `resource_remediation_locked:` reach the wire verbatim, so
an agent can branch on codes rather than on human-readable text.
**Breadth.** `/api/agent/fleet-context` returns a thin per-resource rollup
across the whole organisation, covering identity, operator flags, per-severity
finding counts, and pending-approval count. It answers "where do I focus" in
one read, with the per-resource endpoint available for follow-up depth.
**Write.** There are two different write surfaces. The operator-state intent loop
(`/api/resources/{id}/operator-state`) records per-resource commitments such
as intentionally offline, never auto-remediate, maintenance window, and
criticality. The action governance loop (`/api/actions/plan`,
`/api/actions/{id}/decision`, `/api/actions/{id}/execute`) plans, approves,
and executes capability invocations against a resource through the canonical
audit store. Operator-state writes require `monitoring:write` and use
`scope_only` policy, not action-plan approval. PUT replaces the whole saved
record: read it first, preserve unrelated fields and coordinate other writers.
Deleting it can remove maintenance and remediation protections as well as notes.
See [operator-state semantics](API.md#resource-maintenance-and-operator-state).
Action planning, approval and execution have separate scopes and permission
checks; a scope does not bypass actor binding, separation of duties, step-up or
live readiness. The legacy `ai:execute` scope also permits these action stages,
so it is not a read-only substitute. Follow the
[separate action steps](API.md#unified-action-planning), inspect the target,
blast radius and `planHash`, and retain any refusal. Do not grant an external
client all stages unless that is the authority you intend to delegate.
Pulse derives attribution from the authenticated identity, not a caller-supplied
actor. Validation failures emit the `operator_state_invalid` and
`invalid_action_request` codes. Lifecycle conflicts on the action loop emit
`action_not_pending`, `action_not_approved`, `action_already_executing`,
`action_execution_final`, and `action_dry_run_only`, so an agent can branch on
the specific conflict instead of retrying blindly.
**Push.** `/api/agent/events` is an SSE stream that fires `finding.created`,
`approval.pending`, and `action.completed` as state changes. `action.completed`
means a terminal action record, **including failed or refused actions**, not
necessarily a successful dispatch. Inspect its result and verification evidence;
the event name alone does not confirm the intended outcome. An idle stream is
normal and is not a reason to create a finding or execute something.
## Read action outcomes without repeating them
Keep HTTP success, planning, approval, dispatch and post-action verification
separate. An allowed plan has not executed; an approval is not an execution
receipt; a successful dispatch or command exit does not prove workload recovery.
| Verification evidence | Meaning |
| --- | --- |
| `unknown`, absent evidence or legacy record | No established post-action check |
| `unverified` or `ran: false` | The intended state was not established; for example, no usable check or an unreachable agent |
| `failed` | A postcondition was checked and did not match |
| `verified`, with a successful check that ran | That particular postcondition matched at its recorded time, not every application or filesystem health requirement |
Where the response exposes `ran`, `success`, `ranAt` and `note`, read them
together, not `success` alone. Review the existing resource context and, with
the required licensed audit access, its action history. Retain the same action
identity after a timeout or lost response: **a change may already have happened**.
Do not resend execution, invent another request ID or repeat an action to obtain
better evidence. Use the workload's established safe checks independently of
Pulse; verification can be unavailable without proving that nothing ran.
## What ships to help you connect
- **Settings, then API Access, then Agent integrations** is the in-app
surface. It reads `/api/agent/capabilities` from your running instance,
lists the declared capabilities by category, shows the surface contract and
affordance badges, and shows each capability's method, path, scope, and
stable error codes. It also generates client-ready `pulse-mcp` configuration
snippets with your instance's URL already filled in, covering OpenCode's
native `opencode.json` shape and the `mcpServers` shape used by
Claude-style clients. API tokens are minted on the same tab, so one place
covers both what agents can do and which token unlocks it.
- **`cmd/pulse-mcp`** is the MCP server adapter. Wire it into any MCP client
that can launch a local server. It projects each manifest capability into
one MCP tool with an auto-derived input schema, so capabilities added to
Pulse extend the MCP surface without an adapter change. Run it with
`--emit-notifications` to translate Pulse's SSE events into JSON-RPC
notifications on the stdio channel, which lets an autonomous MCP-bound agent
react to push events without holding a separate HTTP connection. The
one-line installers `install-mcp.sh` and `install-mcp.ps1` fetch the
matching binary from the latest Pulse release, verify the checksum manifest
against Pulse's pinned release key, and then verify the binary's checksum.
They refuse installation when any integrity evidence is unavailable or
invalid. Building from source stays available.
- **`cmd/agent-probe`** is a worked source example for discovery, triage,
depth and push, not a production diagnostic tool. Its current credential input
is `--token`, which exposes the token in process arguments, and it prints
resource context and event bodies. **Do not use it with a real token or
production data.** Use an isolated synthetic fixture for development, or the
private-response HTTP checks above for operator reads. Its final summary and
an idle event window do not prove that an event was received or a workload
recovered.
## Contracts and limits
- **The manifest matches the implementation.** Every error code an
agent-surface handler can emit is declared in the manifest, and every code
the manifest declares is one a handler can emit. Drift in either direction
is covered by source contract tests. Use the manifest served by the running
version; a test pass or a newer repository page does not update an installed
server or establish an external client's compatibility.
- **Discovery needs no token.** `/api/agent/capabilities` serves without
credentials by design. The capabilities it describes keep their own auth
scopes, so introspection does not grant access to anything.
- **Error codes come in two layers.** Capability-specific codes such as
`resource_not_found`, `operator_state_not_set`, and
`operator_state_invalid` are declared per capability in the manifest.
Cross-cutting codes such as `invalid_org`, `org_suspended`, and
`access_denied` come from the auth and multi-tenant middleware and apply to
every authenticated endpoint.
- **The surfaces compose.** Discovery, triage, depth, and the operator-state
write loop are exercised together through the real HTTP boundary on every
build, rather than only in isolation. These fixture checks are not evidence
of ordinary installed use, complete provider collection or recovered workloads.
## Known rough edges
- **Unsigned macOS binary.** The installer verifies release checksums, but the
first launch of the macOS `pulse-mcp` binary can still show a Gatekeeper
warning because the binary is not notarised. Homebrew and other
package-manager distribution may follow.
- **Field acceptance is separate.** The in-app panel, adapters and contract
fixtures do not establish every external client's compatibility, scale or
privacy behaviour. Report a consequential integration gap with the running
version, client type, capability name and relevant redacted error. Keep tokens,
client configuration, full responses and model transcripts private; do not
recreate an action or send production data just to make a report.
## Where to read more
- [Configuration](CONFIGURATION.md) for API tokens and their scopes.
- [API reference](API.md) for the wider Pulse HTTP surface.
- [AI features](AI.md) for how Patrol and the Assistant use the same context.
- `cmd/pulse-mcp/README.md` in the repository for adapter setup examples
covering OpenCode, Claude Desktop, and Claude Code.