# Hermes Agent Connector

## Purpose

`aip-connector-hermes-agent` maps the stable Hermes API-server surface to AIP
capabilities and can, when explicitly enabled, register each Hermes instance as
a governed AIP router/operator principal. Product-specific Hermes DTOs remain
inside the connector. AIP core types and lifecycle semantics are not coupled to
Hermes.

The implementation was checked against the official Hermes Agent checkout and
revision recorded in [upstream-baselines.md](upstream-baselines.md). The
authoritative upstream contract is `gateway/platforms/api_server.py`.

## Published Surface

For every configured endpoint `<endpoint>`, the connector publishes:

- health and API discovery;
- model, skill, and toolset discovery;
- Chat Completions and Responses API operations;
- structured run start, status, events, approval, and stop operations;
- persisted session CRUD, messages, fork, chat, and streaming chat operations;
- stored response lookup and deletion;
- `cap:hermes_agent:<endpoint>:operator` for governed long-running operator
  work.

The operator also owns the stable AIP principal
`agent:hermes_operator:<endpoint>`. A connector-owned delegation router is
registered with `aip-gateway` only when delegation is explicitly enabled.
Static native HTTP and NATS routes retain precedence over connector routers.

The connector implements `InboundConnector`, `OutboundConnector`,
`ChannelConnector`, and `EscalationConnector`. Channel ingress uses the same
strict AIP envelope mapper as ordinary inbound events. Channel egress sends a
normalized `ActionResult` to the deployment-selected persisted Hermes session;
multi-endpoint deployments must supply both `endpoint_id` and `session_id` in
trusted connector context.

An endpoint may be bound to one verified AIP tenant through
`HermesAgentEndpoint::with_tenant_id`. Frozen invocations and connector-owned
delegations then reject a missing, expired, or different tenant before any
provider request is sent.

## Operator Lifecycle

The connector uses Hermes structured runs instead of the stateless chat route:

```text
AIP action/delegation
  -> durable start claim
  -> POST /v1/runs
  -> durable action-to-run binding
  -> GET /v1/runs/{run_id}/events
  -> status polling after disconnect or process restart
  -> completed | failed | cancelled | waiting_for_approval | outcome_unknown
```

State is stored in the runtime-owned `ProfileStateStore` namespace
`aip.connector.hermes_agent.operator_runs.v1`. Postgres deployments therefore
share the same atomic CAS backend for AIP delegation outbox state and Hermes run
correlation.

### Start Idempotency

Hermes `POST /v1/runs` does not provide a durable provider idempotency key. The
connector consequently implements the strongest safe boundary available:

1. persist the AIP action and canonical request fingerprint;
2. acquire an exclusive CAS start claim before the HTTP request;
3. persist the returned `run_id` before consuming events;
4. replay terminal results and poll already-bound runs without starting a new
   run;
5. mark the outcome unknown when the process or transport fails between provider
   acceptance and durable run-id binding.

An unknown start outcome is never retried automatically. This may require
operator reconciliation, but it prevents a duplicate agent run and duplicate
side effects.

### Streaming and Recovery

Hermes structured-run SSE has no replay cursor and a disconnected subscriber
may lose intermediate events. AIP therefore publishes events incrementally
while connected, persists the last observed event and AIP stream sequence, and
uses `GET /v1/runs/{run_id}` as the authoritative recovery path. Terminal run
status is retained by the referenced Hermes revision for one hour.

Raw Hermes SSE operations use explicit terminal classifiers. `run.failed`,
`response.failed`, `response.incomplete`, and terminal `error` events produce a
failed AIP action; `run.cancelled` produces a cancelled action. Closing a stream
without an explicit terminal event is an incomplete failure, not a successful
result.

### Provider Approval

A Hermes `approval.request` event returns `requires_human` and persists a
redacted pending approval. Resumption uses a new AIP operator action:

```json
{
  "resume": {
    "source_action_id": "act_...",
    "choice": "once",
    "resolve_all": false
  }
}
```

Each response is bound to the resume action id, canonical fingerprint, and a
monotonic approval generation. A durable command ledger prevents a replayed
response from approving a later Hermes prompt. A transport failure after
sending an approval is outcome-unknown and is not replayed.

The operator capability itself requires normal AIP approval before execution.
Connector-owned delegation additionally requires a terminal approved record in
the runtime `ApprovalStore` by default. The durable decision must match the
child `action_id`, capability id, decision payload, and expiration time. Payload
claims alone are never accepted as authority.

Approval resume owns a fenced queue lease for the entire resumed action. A
heartbeat renews both the action lease and any transferred idempotency
reservation while Hermes is running. Losing either fence cancels the active
execution and prevents terminal settlement by the stale worker. This is
required because a provider-backed Hermes run can exceed the initial lease TTL.

The standalone daemon remains fail-closed for governed action execution until a
deployment installs a trusted identity directory with
`--trusted-identity-file` or `AIPD_TRUSTED_IDENTITY_FILE`. The JSON array maps
transport-authenticated principal ids to non-zero revisions and optional
verified tenant, opaque credential, sanitized identity, and expiration data.
The daemon rejects duplicate, revoked, expired, and zero-revision entries.

Approval decisions additionally require an approval authority directory with
`--approval-authority-file` or `AIPD_APPROVAL_AUTHORITY_FILE`. The JSON file
contains serialized
`AuthorityMembership` records, must be a regular file smaller than 1 MiB, and
must not be group- or world-writable. Duplicate, expired, revoked, and
zero-revision memberships are rejected at startup. This directory contains
authorization policy, not bearer credentials; production deployments should
still mount it read-only from a policy-controlled source.

### Cancellation

Cancellation intent is persisted before `POST /v1/runs/{run_id}/stop`. The
connector then polls run status for the configured grace interval. Only a
provider-reported `cancelled` state is recorded as cancelled. A completion or
failure racing with cancellation remains the authoritative terminal result. If
Hermes does not expose a terminal state, the binding becomes outcome-unknown.

## Router Policy

Delegation is fail-closed. `HermesOperatorPolicy` requires all of the following:

- `delegation_enabled = true`;
- an explicit delegated capability-prefix allowlist;
- an explicit delegated scope-prefix allowlist;
- a runtime-owned approval store when delegated approval is required;
- bounded delegation depth, input size, events, execution time, claim TTL, and
  cancellation reconciliation time.

The literal prefix `*` is an explicit unrestricted opt-in for one dimension. It
should not be used in production when narrower prefixes are available.

Hermes model output is not authoritative for a delegated business result. The
delegation payload contains an exact `aip_call_arguments` object, including the
protocol-assigned child action id, immutable action contract, approval,
transaction, and validated delegation chain. Hermes is instructed to invoke
the stable `aip_call` MCP tool exactly once with that object.

After the Hermes run completes, `RuntimeHermesDelegatedResultResolver` reads the
native durable AIP action record and result. It verifies the exact action id,
capability, input, execution mode, idempotency key, timeout, conversation,
memory context, delegation chain, federation, callback, observability,
compliance, approval, and transaction fields. It also requires the
transport-authenticated Hermes principal, `action:write` authority, trusted
runtime identity projection, and tenant partition to match the delegation.
Only that verified native result becomes the child `ActionResult`. Final model
prose is ignored. A missing action, altered contract, wrong principal, wrong
tenant, or inconsistent lifecycle state fails closed with a typed non-secret
protocol error.

The runtime rejects self-hops, discontinuous delegation chains, repeated
principals, and cycles before dispatch. A permanent connector policy denial is
settled once and is not retried by the durable delegation outbox.

`aipd` exposes the policy through CLI options and equivalent environment
variables. A minimal restricted configuration is:

```bash
AIPD_HERMES_ENDPOINTS='hermes-1=http://hermes-1:8642'
AIPD_HERMES_API_KEY='replace-with-secret-manager-reference'
AIPD_HERMES_OPERATOR_DELEGATION_ENABLED=true
AIPD_HERMES_OPERATOR_CAPABILITY_PREFIXES='cap:support_sandbox:'
AIPD_HERMES_OPERATOR_SCOPE_PREFIXES='support.'
AIPD_HERMES_OPERATOR_MODEL='deployment/model-route'
```

Trusted instructions should be mounted as a file and selected with
`--hermes-operator-instructions-file` or
`AIPD_HERMES_OPERATOR_INSTRUCTIONS_FILE`. Do not concatenate tenant or user
input into this file.

## AIP MCP Inside Hermes

The connector can make Hermes an AIP operator, but Hermes must also be given an
AIP tool path when it is expected to call other capabilities. Configure the
same `aipd` deployment as a Streamable HTTP MCP server in the Hermes profile:

```yaml
mcp_servers:
  aip:
    url: "http://aipd:8080/mcp"
    headers:
      Authorization: "Bearer ${AIP_MCP_TOKEN}"
    timeout: 300
    connect_timeout: 15
    supports_parallel_tool_calls: false
    tools:
      include:
        - aip_capabilities
        - aip_call
        - aip_action_status
        - aip_action_result
        - aip_action_events
        - aip_action_cancel
```

The internal Hermes allowlist deliberately excludes `aip_delegate`. The
external AIP orchestrator owns first-class delegation and injects the complete
parent/child graph. Giving a model recursive access to `aip_delegate` can create
an unbounded delegation loop because the delegated prompt is itself an agent
task. Add that tool only behind a separate broker that propagates and validates
the original delegation chain, and grant `delegation:create` only to that
broker identity.

Each Hermes instance must use its own OAuth access token and AIP principal,
for example `agent:hermes_operator:hermes-1`. A normal internal operator token
needs only the MCP connection scope and the scopes required by its selected
read/query tools; it must not inherit the external orchestrator's delegation,
approval, or broad audit scopes. Supported protected-resource scopes and the
small set of scopes required to establish an MCP transport are separate
configuration fields.

The operator's `aip_call` must preserve the `action_id` and every supplied
execution-contract field. A generated replacement action id is a contract
violation: it cannot satisfy first-class delegation and is rejected by the
authoritative result resolver.

Hermes must not receive database credentials or direct access to systems of
record when the workflow is intended to prove the AIP path. Facts and side
effects must be obtained through AIP capabilities.

MCP configuration is a deployment responsibility because it contains endpoint,
tenant, and credential policy. The Rust connector never writes Hermes profile
files or transports bearer tokens through action payloads.

The lightweight Hermes image must install the official `mcp` extra from the
pinned upstream checkout. Installing the base package alone does not include
the Streamable HTTP MCP client modules used by `hermes mcp test`.

## Trusted Execution Projection

The frozen connector path consumes the non-serializable AIP
`ExecutionContext`. It sends only bounded, verified metadata to Hermes:

- `X-AIP-Principal-Id` from the transport-authenticated actor;
- `X-AIP-Tenant-Id` from verified tenant membership;
- `X-AIP-Authentication-Issuer` from the trusted authenticator;
- `X-AIP-Trace-Id` and `X-AIP-Correlation-Id` from the trusted trace context;
- `X-AIP-Deadline-Unix-Ms` from the runtime deadline;
- `X-AIP-Delegation-Chain` from the validated native action chain; and
- `Idempotency-Key` from the native action contract.

Payload `_aip` fields remain a legacy correlation input for the old untyped
connector API. They never override the authenticated actor, verified tenant,
trusted trace, deadline, or validated delegation chain in the frozen path.
Opaque credential handles and raw provider credentials are never serialized
into these headers.

## Network and Credential Safety

The default HTTP client:

- accepts only administrator-configured HTTP or HTTPS endpoint URLs;
- does not follow redirects, preventing bearer propagation to a redirected
  origin;
- uses bounded connection and request timeouts;
- never serializes endpoint API keys or prints them through `Debug`;
- maps provider and transport failures to typed AIP connector errors;
- bounds SSE frames, event counts, input bytes, and total operator duration.

Production deployments should use TLS, private service discovery, secret
manager injection, network egress policy, and independent Hermes service
accounts per tenant or trust domain.

## Qualification Requirements

A release claiming Hermes operator support must retain evidence for:

1. every published route against the pinned upstream revision;
2. concurrent same-action execution starting exactly one Hermes run;
3. process failure before run-id binding producing outcome-unknown without
   replay;
4. SSE delta, tool, approval, failure, cancellation, and terminal mapping;
5. approval replay and generation isolation;
6. cancellation/completion races and unconfirmed cancellation;
7. restart recovery with the production storage backend;
8. AIP gateway delegation routing to two isolated Hermes instances;
9. Hermes tool use through authenticated AIP MCP, including a real downstream
   capability call, exact child-action execution, and retained AIP receipts;
10. negative authorization, tenant isolation, allowlist, timeout, malformed
    response, and provider-outage cases.

The deterministic RFC 0004 release gate is executable with:

```bash
cargo test -p aip-connector-hermes-agent --test frozen_conformance
```

It runs atomic manifest admission and all twelve shared behavior families.
Identity, schema, idempotency, retry/DLQ, cancellation, streaming,
uncertain-outcome, approval/quorum, audit/receipts/redaction, and restart
recovery execute against the public connector and runtime boundaries.
Transactions and webhooks are reported as not applicable because the Hermes
manifest publishes neither contract.

The isolated-live two-Hermes driver is executable with:

```bash
AIPD_MCP_ACCESS_TOKEN='external-orchestrator-token' \
AIPD_NATIVE_BEARER_TOKEN='independent-approver-token' \
cargo run -p aip-connector-hermes-agent --bin aipd-hermes-operator-smoke -- \
  --endpoint-id hermes-1 \
  --endpoint-id hermes-2
```

The retained qualification record is
[hermes-agent-isolated-live.md](../testing/hermes-agent-isolated-live.md).
Unit and deterministic process-boundary tests remain necessary but do not
replace this isolated-live provider-backed evidence.
