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. 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>:operatorfor 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:
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:
- persist the AIP action and canonical request fingerprint;
- acquire an exclusive CAS start claim before the HTTP request;
- persist the returned
run_idbefore consuming events; - replay terminal results and poll already-bound runs without starting a new run;
- 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:
{
"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:
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:
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-Idfrom the transport-authenticated actor;X-AIP-Tenant-Idfrom verified tenant membership;X-AIP-Authentication-Issuerfrom the trusted authenticator;X-AIP-Trace-IdandX-AIP-Correlation-Idfrom the trusted trace context;X-AIP-Deadline-Unix-Msfrom the runtime deadline;X-AIP-Delegation-Chainfrom the validated native action chain; andIdempotency-Keyfrom 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:
- every published route against the pinned upstream revision;
- concurrent same-action execution starting exactly one Hermes run;
- process failure before run-id binding producing outcome-unknown without replay;
- SSE delta, tool, approval, failure, cancellation, and terminal mapping;
- approval replay and generation isolation;
- cancellation/completion races and unconfirmed cancellation;
- restart recovery with the production storage backend;
- AIP gateway delegation routing to two isolated Hermes instances;
- Hermes tool use through authenticated AIP MCP, including a real downstream capability call, exact child-action execution, and retained AIP receipts;
- negative authorization, tenant isolation, allowlist, timeout, malformed response, and provider-outage cases.
The deterministic RFC 0004 release gate is executable with:
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:
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. Unit and deterministic process-boundary tests remain necessary but do not replace this isolated-live provider-backed evidence.