{
  "schemaVersion": "1.0",
  "title": "Stream Hermes chat and Responses",
  "description": "Use this guide to submit one streaming Hermes action, render incremental output, and reach a verified AIP result. It covers chatstream and responsesstream on an endpoint already admitted to the tenant catalogue.",
  "canonical": "https://getaip.org/docs/connectors/hermes-agent/guides/chat-and-response-streaming",
  "route": "/docs/connectors/hermes-agent/guides/chat-and-response-streaming",
  "source": "docs/connectors/hermes-agent/guides/chat-and-response-streaming.md",
  "protocol": "Agent Interoperability Protocol",
  "protocolVersion": "1.0",
  "section": "Connectors",
  "documentType": "Connector",
  "language": "en",
  "downloads": {
    "md": "/docs/download/connectors/hermes-agent/guides/chat-and-response-streaming.md",
    "txt": "/docs/download/connectors/hermes-agent/guides/chat-and-response-streaming.txt",
    "json": "/docs/download/connectors/hermes-agent/guides/chat-and-response-streaming.json",
    "pdf": "/docs/download/connectors/hermes-agent/guides/chat-and-response-streaming.pdf"
  },
  "content": {
    "format": "text/markdown",
    "markdown": "---\ntitle: Stream Hermes chat and Responses\ndescription: >-\n  Follow durable AIP chunks, render Hermes deltas, and settle a streaming\n  action without treating partial text or a closed connection as completion\nkind: how-to\naudience: application-developer\nappliesTo: \"1.x\"\nwritingStandard: \"aip-docs/1.0\"\nlastReviewedRevision: \"97be86e9efedf07ecf1783b03800f683f107fb04\"\nconnector: hermes-agent\ncapabilityIds:\n  - cap:hermes_agent:<endpoint_id>:chat_stream\n  - cap:hermes_agent:<endpoint_id>:responses_stream\n---\n\n# Stream Hermes chat and Responses\n\nUse this guide to submit one streaming Hermes action, render incremental\noutput, and reach a verified AIP result. It covers `chat_stream` and\n`responses_stream` on an endpoint already admitted to the tenant catalogue.\n\nStreaming can execute models, tools, memory writes, and message delivery.\nPartial text, a terminal-looking provider frame, and a final AIP result are\ndifferent evidence. Never replay a stream merely because one delivery\nconnection closed.\n\n## Understand the four views\n\nOne model turn appears in four nested views.\n\n| View | Owner | What it proves |\n|---|---|---|\n| Hermes SSE frame | Hermes endpoint | One upstream role, text, tool, finish, or terminal signal |\n| AIP `StreamChunk` | AIP runtime | One parsed provider frame was recorded for this action and sequence |\n| Native follow SSE frame | `aipd` | A stored event or chunk was delivered to this consumer |\n| Final `ActionResult` | AIP runtime | The connector classified the collected stream as completed, failed, or cancelled |\n\nThe outer follow stream can disconnect while the action and Hermes request\ncontinue. Conversely, an upstream Hermes stream can end while the AIP follow\nconnection remains open long enough to deliver the final failure.\n\nUse the AIP action ID as the recovery identity. Use provider IDs inside events\nto interpret Chat, Responses, tool, and continuity state. Do not substitute one\nfor the other.\n\n## 1. Choose Chat or Responses\n\n| Need | Capability | Terminal provider signal |\n|---|---|---|\n| Stream an OpenAI-compatible message turn | `cap:hermes_agent:<endpoint_id>:chat_stream` | `data: [DONE]` after a finish chunk |\n| Stream a stored or chainable Responses turn | `cap:hermes_agent:<endpoint_id>:responses_stream` | `response.completed` or `response.failed` |\n\nChoose Responses with `store: true` when an application needs a provider\nrecord that can be reconciled after cancellation or upstream disconnect.\nChoose Chat when its message shape and explicit Hermes session continuity are\nthe intended interface.\n\nBoth capabilities are stable, medium risk, retry-unsafe, and not subject to a\nconnector retry. Both advertise synchronous, asynchronous, streaming, and\ncancellation support, but `async_expected` is false. The provider does not\ncontinue as a background delivery service after its HTTP stream is gone.\n\nThe published capability timeout is 300 seconds. The connector's default HTTP\nclient permits up to 900 seconds, but a trusted AIP deadline or explicit\ncancellation can stop the request earlier. Use the final AIP state as the\noutcome boundary; do not infer success from elapsed time.\n\n## 2. Allocate stable AIP identity\n\nGenerate and retain the AIP action ID before submission. If the application\nuses the optional idempotency field, allocate one key for this exact request\nand retain it with the action ID.\n\nAn optional key does not make model or tool execution safe to retry. Provider\nstreaming bypasses the in-memory idempotency cache used by non-streaming Chat\nand Responses calls.\n\nRecord at least:\n\n- the endpoint-qualified capability ID;\n- the AIP action ID and optional idempotency key;\n- the tenant and authenticated principal used for submission;\n- the chosen continuity fields;\n- the durable follow cursor returned by `aipd`.\n\nDo not generate a replacement action ID after an uncertain submission. Query\nthe original ID first.\n\n## 3. Prepare a bounded Chat stream\n\nUse one Chat input branch and an explicit Hermes session ID:\n\n```json\n{\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"Return a concise operational summary.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"Summarize the current incident notes.\"\n    }\n  ],\n  \"model\": \"hermes-agent\",\n  \"session_id\": \"incident-review-2030-01\",\n  \"session_key\": \"support:incident-review\",\n  \"max_events\": 512\n}\n```\n\nThe connector forces `stream` to `true`. It removes continuity and trusted\ntransport fields from provider JSON and sends them as headers.\n\nUse an explicit `session_id` from the first turn. When it is absent, Hermes\nderives one and returns it only in an HTTP header. The current connector keeps\nprovider SSE data but does not expose those response headers in its aggregate.\n\n`max_events` limits parsed provider frames collected by the connector. It does\nnot limit output tokens, tool calls, AIP follow pages, or model execution. The\ndefault is 4,096 frames. A low value is a safety cutoff, not normal completion.\n\n## 4. Prepare a recoverable Responses stream\n\nKeep the provider body separate from connector controls:\n\n```json\n{\n  \"body\": {\n    \"model\": \"hermes-agent\",\n    \"input\": \"Summarize the current incident notes.\",\n    \"instructions\": \"Return three concise findings.\",\n    \"store\": true,\n    \"truncation\": \"auto\"\n  },\n  \"session_key\": \"support:incident-review\",\n  \"max_events\": 512\n}\n```\n\nThe connector forces the provider body's `stream` field to `true`. Put\n`max_events`, `session_key`, and the AIP idempotency value outside `body`.\n\nWith `store: true`, Hermes persists an `in_progress` snapshot immediately\nafter `response.created`. It later replaces that snapshot with completed,\nfailed, or incomplete provider state. Capture the response ID from the first\nevent so `response_get` can reconcile the provider record.\n\nFor chaining, set `previous_response_id`, `conversation`, or explicit\n`conversation_history` according to the capability reference. Do not combine\n`conversation` and `previous_response_id`.\n\n## 5. Submit and follow the same action\n\nPlace the chosen input in `request.json`, then start the stable action:\n\n```sh\nexport AIP_CAPABILITY_ID='cap:hermes_agent:replace_endpoint:chat_stream'\nexport AIP_ACTION_ID='act_replace_with_stable_id_001'\n\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIP_TOKEN_FILE\" \\\n  action call \"$AIP_URL\" \\\n  \"$AIP_CAPABILITY_ID\" \\\n  --action-id \"$AIP_ACTION_ID\" \\\n  --input @request.json\n```\n\nIn a separate consumer, follow durable events and chunks for that same ID:\n\n```sh\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIP_TOKEN_FILE\" \\\n  action events \"$AIP_URL\" \\\n  \"$AIP_ACTION_ID\" \\\n  --include-chunks \\\n  --follow\n```\n\nAn application can use the equivalent bearer-authenticated route:\n\n```text\nGET /aip/v1/actions/{action_id}/events?include_chunks=true&follow=true\n```\n\nThe follow loop polls the durable view every 500 milliseconds and emits an\nouter keepalive every 15 seconds. Hermes independently writes upstream\nkeepalive comments after 30 seconds of inactivity. The connector ignores those\ncomments; they do not consume `max_events` or create AIP chunks.\n\n## 6. Decode each durable chunk\n\nAn outer `aip.stream.chunk` follow event contains an AIP chunk. Its structured\npayload has this shape:\n\n```json\n{\n  \"action_id\": \"act_replace_with_stable_id_001\",\n  \"sequence\": 1,\n  \"kind\": \"data\",\n  \"data\": {\n    \"hermes_sse\": {\n      \"event\": null,\n      \"id\": null,\n      \"data\": {\n        \"choices\": [\n          {\n            \"delta\": {\n              \"content\": \"The incident\"\n            },\n            \"finish_reason\": null\n          }\n        ]\n      },\n      \"raw_data\": \"{\\\"choices\\\":[{\\\"delta\\\":{\\\"content\\\":\\\"Hi\\\"}}]}\",\n      \"terminal\": false\n    }\n  }\n}\n```\n\nThe connector assigns `StreamChunk.sequence` from zero in provider-frame\norder. The embedded Responses `sequence_number`, when present, is a different\nprovider sequence. Never compare or persist one as the other.\n\nThe runtime persists a chunk before publication returns. An identical repeat\nof the same AIP sequence is accepted as replay. Conflicting content,\nnon-increasing new sequences, and chunks after an AIP terminal chunk are\nrejected.\n\nUse `data.hermes_sse.data` as the provider event. Keep `raw_data` for controlled\ndiagnostics only; it can contain prompts, output, tool arguments, tool results,\nand personal data.\n\n## 7. Render Chat events\n\nHandle Chat frames in this order.\n\n| Provider frame | Application action |\n|---|---|\n| Initial `choices[0].delta.role` | Open an assistant message; render no text |\n| `choices[0].delta.content` | Append content to that message |\n| `event: hermes.tool.progress` | Update the correlated tool status from its payload |\n| Finish chunk | Save `finish_reason`, usage, and any `hermes` or `error` object |\n| `data: [DONE]` | Mark the provider stream explicit-terminal, then wait for AIP result |\n\nChat content deltas contribute to the final aggregate `text`, but the current\nconnector does not copy them into `StreamChunk.part`. A renderer that reads\nonly `part` will display no Chat text. Read the embedded `choices` delta.\n\n`hermes.tool.progress` carries tool name, correlation ID, and running or\ncompleted state. Its provider event name does not match the connector's\ngeneric `tool.started`, `tool.completed`, or `tool.failed` matcher. The AIP\nchunk is therefore normally `kind: data`, not `kind: tool`.\n\nNever discard `kind: data` chunks when tools matter. De-duplicate tool updates\nby their provider correlation ID and AIP sequence.\n\n## 8. Validate the Chat finish\n\nThe final Chat data chunk can carry one of three `finish_reason` values.\n\n| Finish reason | Meaning | Application outcome |\n|---|---|---|\n| `stop` | Hermes reported ordinary completion | Candidate success; still require final AIP completion |\n| `length` | Usable output was truncated | Partial result; do not present it as complete |\n| `error` | Agent execution failed | Failed business result even if some text exists |\n\nThe provider writes `[DONE]` after all three. The connector treats `[DONE]` as\nan explicit terminal frame, but its final outcome classifier does not inspect\nChat `finish_reason`. An `ActionResult` can therefore have status `completed`\nwhile the retained finish chunk says `length` or `error`.\n\nAccept a Chat stream only when all of these are true:\n\n1. the final AIP result is `completed`;\n2. aggregate `terminal` is true and `truncated` is false;\n3. a retained finish chunk has `finish_reason: stop`;\n4. no retained provider error contradicts that finish;\n5. any required tool effects have application-level confirmation.\n\nTreat missing finish evidence as an incomplete outcome. Do not infer success\nfrom accumulated `text`.\n\n## 9. Render Responses events\n\nResponses uses named provider events with its own `sequence_number`.\n\n| Event | Application action |\n|---|---|\n| `response.created` | Capture `response.id` and initial `in_progress` state |\n| `response.output_item.added` | Open a message, function call, or function-call output item |\n| `response.output_text.delta` | Append `delta` to the named message/content index |\n| `response.output_text.done` | Verify the full text for that content item |\n| `response.output_item.done` | Close the identified item and preserve its final payload |\n| `response.completed` | Read the full terminal response and usage |\n| `response.failed` | Read redacted failure data and settle as failed |\n\nResponses text deltas appear in both embedded `data.delta` and, when\nnon-empty, `StreamChunk.part`. Prefer the structured provider identifiers so\nmultiple output items cannot be merged accidentally.\n\nFunction calls and their outputs use `response.output_item.*` names. These\nevents are retained as AIP `data` chunks rather than generic `tool` chunks.\nPair them with `call_id`, `item.id`, and `output_index` from the provider\npayload.\n\nThe connector recognizes `response.completed` as Done and `response.failed`\nas Error. It also understands an upstream `response.incomplete` name, although\nthe pinned Responses writer normally persists incomplete state on disconnect\nrather than delivering that terminal event over the broken stream.\n\n## 10. Read the final AIP result\n\nThe connector returns this aggregate inside `ActionResult.output`:\n\n```json\n{\n  \"endpoint_id\": \"replace_endpoint\",\n  \"operation\": \"responses_stream\",\n  \"http_status\": 200,\n  \"events\": [],\n  \"text\": \"Three findings are ready for review.\",\n  \"terminal\": true,\n  \"truncated\": false\n}\n```\n\nThe actual `events` array contains every parsed provider frame, including its\nraw data. `text` is best-effort convenience output. It is not a substitute for\nthe terminal provider response, finish reason, tool state, or final AIP status.\n\n| Aggregate state | AIP result | Decision |\n|---|---|---|\n| `truncated: true` | Failed with `connector.hermes_agent.stream_truncated` | Preserve partial evidence; do not replay automatically |\n| `terminal: false` | Failed with `connector.hermes_agent.stream_incomplete` | Reconcile original action and provider state |\n| Terminal `response.failed`, `response.incomplete`, or `error` | Failed with `connector.hermes_agent.stream_failed` | Inspect typed error and retained provider event |\n| Terminal `run.cancelled` | Cancelled | Confirm external effects separately |\n| Other explicit terminal | Completed | Apply Chat or Responses business checks before accepting |\n\nFor Chat, apply the additional `finish_reason` rule from the preceding step.\nFor Responses, require the terminal response object's status to agree with the\nAIP result.\n\n## 11. Choose the event limit safely\n\nOmitting `max_events` uses the connector default of 4,096 parsed frames. The\nfinal aggregate retains all collected events, so choose a lower application\nbound only when it still leaves substantial headroom for text and tool frames.\n\nThe connector publishes and stores the frame that reaches the limit, then sets\n`truncated` true and `terminal` false. It checks the limit before accepting\nterminal completion. A terminal provider frame that lands exactly at the\nconfigured limit can therefore be published as Done while the final AIP result\nis Failed for truncation.\n\nThe final `ActionResult` wins over a conflicting last chunk. Do not retry with\na larger limit after model or tool work may have occurred. Use the original\naction evidence to reconcile the effect.\n\nAn upstream close without an explicit terminal produces a Failed result. With\nruntime publication active, the connector also appends an AIP Error chunk with\nreason `upstream_stream_closed_without_terminal_event`. The truncation branch\ndoes not append that extra terminal Error chunk, so always read the result.\n\n## 12. Recover a follow disconnect\n\nClosing or losing the native `aipd` follow SSE does not cancel the action or\nthe upstream Hermes request.\n\nOn reconnect:\n\n1. keep the original action ID;\n2. resume the same action route with its last retained route cursor;\n3. tolerate a repeated outer event or chunk;\n4. order chunks by AIP `sequence` and reject conflicting content;\n5. read the final action status or result after the follow stream terminates.\n\nPersist only a cursor returned by the same route and authorization context.\nThe exact cursor formats, paging bounds, and expiry decisions belong to the\nstreaming cursor and cancellation reference later in this documentation set.\n\nDo not resume a provider SSE URL directly. Chat and Responses frames do not\nprovide a usable replay cursor in this connector path.\n\n## 13. Cancel the original action\n\nClosing the renderer is not cancellation. Request cancellation explicitly:\n\n```sh\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIP_TOKEN_FILE\" \\\n  action cancel \"$AIP_URL\" \\\n  \"$AIP_ACTION_ID\" \\\n  --reason 'The requester no longer needs this stream'\n```\n\nThe runtime records cancellation and wakes the action's trusted cancellation\ntoken. The connector drops the active HTTP stream. When the Hermes writer\nobserves the disconnect, it attempts `agent.interrupt()` and cancels its task.\n\nThis is cooperative stop, not effect rollback. A model request, tool call,\nmessage, memory write, or stored response may already exist. Read the original\nAIP result and provider state before deciding that another action is safe.\n\nFor `responses_stream` with `store: true`, cancellation can leave a retrievable\n`incomplete` response snapshot. For Chat, preserve explicit session identity\nand inspect the session according to the session capability reference; do not\nassume partial transcript persistence.\n\n## 14. Reconcile a stored Responses stream\n\nAs soon as `response.created` arrives, persist its `response.id`. After an\nuncertain terminal outcome, call `response_get` once with that ID:\n\n```json\n{\n  \"response_id\": \"resp_0123456789abcdef0123456789ab\"\n}\n```\n\nInterpret the stored status:\n\n- `completed` supplies a terminal provider object for review;\n- `failed` supplies retained failure state;\n- `incomplete` preserves partial output after disconnect or cancellation;\n- `404` means the response was not stored, was evicted or deleted, or the\n  request reached another response store.\n\nDo not submit a replacement stream merely because `response_get` returns\nincomplete. Tool or delivery effects can precede that snapshot.\n\n## 15. Handle failures\n\n| Symptom | Meaning | Decision |\n|---|---|---|\n| No AIP chunks yet | Action may be queued, rejected, or before the first provider frame | Read action status; do not resubmit |\n| Follow SSE disconnected | Delivery failed; action state is unknown to this consumer | Resume from durable cursor and read result |\n| Provider HTTP `429` | Hermes concurrent agent-run gate is full | Preserve original action; follow its typed result |\n| `stream_truncated` | Connector frame cutoff was reached | Keep partial evidence; do not retry model work |\n| `stream_incomplete` | Upstream closed without explicit terminal | Reconcile action and stored Responses state |\n| `stream_failed` | Provider delivered a named terminal failure | Inspect retained terminal event and typed error |\n| AIP result Completed but Chat finish is `length` or `error` | Connector terminal and Chat business finish disagree | Treat as partial or failed, not success |\n| Cancellation returned Cancelled | Runtime stopped waiting and attempted upstream interruption | Verify provider and external effects |\n| `response_get` returns `404` | No retained object is reachable at this store | Check original event ID, `store`, instance, and result |\n\nMatch protocol error codes, not free-form provider messages. Streaming calls\nare retry-unsafe even when the visible text is empty.\n\n## 16. Verify the client\n\nBefore accepting the integration, use controlled evidence to verify that the\nclient:\n\n1. retains the action ID before submission;\n2. follows the same ID with chunks included;\n3. orders and de-duplicates by AIP chunk sequence;\n4. renders Chat from `choices` rather than relying on `part`;\n5. handles tool events carried as `kind: data`;\n6. preserves Responses item and call identifiers;\n7. saves `response.id` from `response.created` when storage is enabled;\n8. requires explicit provider terminal evidence and final AIP state;\n9. rejects Chat `length` and `error` finishes as complete success;\n10. handles a terminal frame exactly on `max_events` as truncation;\n11. resumes follow delivery without creating a new action;\n12. sends explicit cancellation and then reconciles effects;\n13. redacts raw frames, prompts, tools, outputs, IDs, and error data.\n\nThis is a source-aligned client checklist. It is not live model, latency,\navailability, cancellation, or production qualification evidence.\n\n## Related documentation\n\n- [Chat and Responses capabilities](../capabilities/chat-and-responses.md)\n- [Hermes capability index](../capabilities/README.md)\n- [Persistent sessions](../capabilities/sessions.md)\n- [Use native AIP](../../../guides/use-native-aip.md)\n- [Transport bindings](../../../reference/transport-bindings.md)\n- [Errors and retry decisions](../../../reference/errors.md)\n",
    "text": "Stream Hermes chat and Responses\n\nUse this guide to submit one streaming Hermes action, render incremental\noutput, and reach a verified AIP result. It covers chatstream and\nresponsesstream on an endpoint already admitted to the tenant catalogue.\n\nStreaming can execute models, tools, memory writes, and message delivery.\nPartial text, a terminal-looking provider frame, and a final AIP result are\ndifferent evidence. Never replay a stream merely because one delivery\nconnection closed.\n\nUnderstand the four views\n\nOne model turn appears in four nested views.\n\n| View | Owner | What it proves |\n\n| Hermes SSE frame | Hermes endpoint | One upstream role, text, tool, finish, or terminal signal |\n| AIP StreamChunk | AIP runtime | One parsed provider frame was recorded for this action and sequence |\n| Native follow SSE frame | aipd | A stored event or chunk was delivered to this consumer |\n| Final ActionResult | AIP runtime | The connector classified the collected stream as completed, failed, or cancelled |\n\nThe outer follow stream can disconnect while the action and Hermes request\ncontinue. Conversely, an upstream Hermes stream can end while the AIP follow\nconnection remains open long enough to deliver the final failure.\n\nUse the AIP action ID as the recovery identity. Use provider IDs inside events\nto interpret Chat, Responses, tool, and continuity state. Do not substitute one\nfor the other.\n1. Choose Chat or Responses\n\n| Need | Capability | Terminal provider signal |\n\n| Stream an OpenAI-compatible message turn | cap:hermesagent::chatstream | data: [DONE] after a finish chunk |\n| Stream a stored or chainable Responses turn | cap:hermesagent::responsesstream | response.completed or response.failed |\n\nChoose Responses with store: true when an application needs a provider\nrecord that can be reconciled after cancellation or upstream disconnect.\nChoose Chat when its message shape and explicit Hermes session continuity are\nthe intended interface.\n\nBoth capabilities are stable, medium risk, retry-unsafe, and not subject to a\nconnector retry. Both advertise synchronous, asynchronous, streaming, and\ncancellation support, but asyncexpected is false. The provider does not\ncontinue as a background delivery service after its HTTP stream is gone.\n\nThe published capability timeout is 300 seconds. The connector's default HTTP\nclient permits up to 900 seconds, but a trusted AIP deadline or explicit\ncancellation can stop the request earlier. Use the final AIP state as the\noutcome boundary; do not infer success from elapsed time.\n2. Allocate stable AIP identity\n\nGenerate and retain the AIP action ID before submission. If the application\nuses the optional idempotency field, allocate one key for this exact request\nand retain it with the action ID.\n\nAn optional key does not make model or tool execution safe to retry. Provider\nstreaming bypasses the in-memory idempotency cache used by non-streaming Chat\nand Responses calls.\n\nRecord at least:\n• the endpoint-qualified capability ID;\n• the AIP action ID and optional idempotency key;\n• the tenant and authenticated principal used for submission;\n• the chosen continuity fields;\n• the durable follow cursor returned by aipd.\n\nDo not generate a replacement action ID after an uncertain submission. Query\nthe original ID first.\n3. Prepare a bounded Chat stream\n\nUse one Chat input branch and an explicit Hermes session ID:\n\n{\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"Return a concise operational summary.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"Summarize the current incident notes.\"\n    }\n  ],\n  \"model\": \"hermes-agent\",\n  \"sessionid\": \"incident-review-2030-01\",\n  \"sessionkey\": \"support:incident-review\",\n  \"maxevents\": 512\n}\n\nThe connector forces stream to true. It removes continuity and trusted\ntransport fields from provider JSON and sends them as headers.\n\nUse an explicit sessionid from the first turn. When it is absent, Hermes\nderives one and returns it only in an HTTP header. The current connector keeps\nprovider SSE data but does not expose those response headers in its aggregate.\n\nmaxevents limits parsed provider frames collected by the connector. It does\nnot limit output tokens, tool calls, AIP follow pages, or model execution. The\ndefault is 4,096 frames. A low value is a safety cutoff, not normal completion.\n4. Prepare a recoverable Responses stream\n\nKeep the provider body separate from connector controls:\n\n{\n  \"body\": {\n    \"model\": \"hermes-agent\",\n    \"input\": \"Summarize the current incident notes.\",\n    \"instructions\": \"Return three concise findings.\",\n    \"store\": true,\n    \"truncation\": \"auto\"\n  },\n  \"sessionkey\": \"support:incident-review\",\n  \"maxevents\": 512\n}\n\nThe connector forces the provider body's stream field to true. Put\nmaxevents, sessionkey, and the AIP idempotency value outside body.\n\nWith store: true, Hermes persists an inprogress snapshot immediately\nafter response.created. It later replaces that snapshot with completed,\nfailed, or incomplete provider state. Capture the response ID from the first\nevent so responseget can reconcile the provider record.\n\nFor chaining, set previousresponseid, conversation, or explicit\nconversationhistory according to the capability reference. Do not combine\nconversation and previousresponseid.\n5. Submit and follow the same action\n\nPlace the chosen input in request.json, then start the stable action:\n\nexport AIPCAPABILITYID='cap:hermesagent:replaceendpoint:chatstream'\nexport AIPACTIONID='actreplacewithstableid001'\n\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIPTOKENFILE\" \\\n  action call \"$AIPURL\" \\\n  \"$AIPCAPABILITYID\" \\\n  --action-id \"$AIPACTIONID\" \\\n  --input @request.json\n\nIn a separate consumer, follow durable events and chunks for that same ID:\n\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIPTOKENFILE\" \\\n  action events \"$AIPURL\" \\\n  \"$AIPACTIONID\" \\\n  --include-chunks \\\n  --follow\n\nAn application can use the equivalent bearer-authenticated route:\n\nGET /aip/v1/actions/{actionid}/events?includechunks=true&follow=true\n\nThe follow loop polls the durable view every 500 milliseconds and emits an\nouter keepalive every 15 seconds. Hermes independently writes upstream\nkeepalive comments after 30 seconds of inactivity. The connector ignores those\ncomments; they do not consume maxevents or create AIP chunks.\n6. Decode each durable chunk\n\nAn outer aip.stream.chunk follow event contains an AIP chunk. Its structured\npayload has this shape:\n\n{\n  \"actionid\": \"actreplacewithstableid001\",\n  \"sequence\": 1,\n  \"kind\": \"data\",\n  \"data\": {\n    \"hermessse\": {\n      \"event\": null,\n      \"id\": null,\n      \"data\": {\n        \"choices\": [\n          {\n            \"delta\": {\n              \"content\": \"The incident\"\n            },\n            \"finishreason\": null\n          }\n        ]\n      },\n      \"rawdata\": \"{\\\"choices\\\":[{\\\"delta\\\":{\\\"content\\\":\\\"Hi\\\"}}]}\",\n      \"terminal\": false\n    }\n  }\n}\n\nThe connector assigns StreamChunk.sequence from zero in provider-frame\norder. The embedded Responses sequencenumber, when present, is a different\nprovider sequence. Never compare or persist one as the other.\n\nThe runtime persists a chunk before publication returns. An identical repeat\nof the same AIP sequence is accepted as replay. Conflicting content,\nnon-increasing new sequences, and chunks after an AIP terminal chunk are\nrejected.\n\nUse data.hermessse.data as the provider event. Keep rawdata for controlled\ndiagnostics only; it can contain prompts, output, tool arguments, tool results,\nand personal data.\n7. Render Chat events\n\nHandle Chat frames in this order.\n\n| Provider frame | Application action |\n\n| Initial choices[0].delta.role | Open an assistant message; render no text |\n| choices[0].delta.content | Append content to that message |\n| event: hermes.tool.progress | Update the correlated tool status from its payload |\n| Finish chunk | Save finishreason, usage, and any hermes or error object |\n| data: [DONE] | Mark the provider stream explicit-terminal, then wait for AIP result |\n\nChat content deltas contribute to the final aggregate text, but the current\nconnector does not copy them into StreamChunk.part. A renderer that reads\nonly part will display no Chat text. Read the embedded choices delta.\n\nhermes.tool.progress carries tool name, correlation ID, and running or\ncompleted state. Its provider event name does not match the connector's\ngeneric tool.started, tool.completed, or tool.failed matcher. The AIP\nchunk is therefore normally kind: data, not kind: tool.\n\nNever discard kind: data chunks when tools matter. De-duplicate tool updates\nby their provider correlation ID and AIP sequence.\n8. Validate the Chat finish\n\nThe final Chat data chunk can carry one of three finishreason values.\n\n| Finish reason | Meaning | Application outcome |\n\n| stop | Hermes reported ordinary completion | Candidate success; still require final AIP completion |\n| length | Usable output was truncated | Partial result; do not present it as complete |\n| error | Agent execution failed | Failed business result even if some text exists |\n\nThe provider writes [DONE] after all three. The connector treats [DONE] as\nan explicit terminal frame, but its final outcome classifier does not inspect\nChat finishreason. An ActionResult can therefore have status completed\nwhile the retained finish chunk says length or error.\n\nAccept a Chat stream only when all of these are true:\n1. the final AIP result is completed;\n2. aggregate terminal is true and truncated is false;\n3. a retained finish chunk has finishreason: stop;\n4. no retained provider error contradicts that finish;\n5. any required tool effects have application-level confirmation.\n\nTreat missing finish evidence as an incomplete outcome. Do not infer success\nfrom accumulated text.\n9. Render Responses events\n\nResponses uses named provider events with its own sequencenumber.\n\n| Event | Application action |\n\n| response.created | Capture response.id and initial inprogress state |\n| response.outputitem.added | Open a message, function call, or function-call output item |\n| response.outputtext.delta | Append delta to the named message/content index |\n| response.outputtext.done | Verify the full text for that content item |\n| response.outputitem.done | Close the identified item and preserve its final payload |\n| response.completed | Read the full terminal response and usage |\n| response.failed | Read redacted failure data and settle as failed |\n\nResponses text deltas appear in both embedded data.delta and, when\nnon-empty, StreamChunk.part. Prefer the structured provider identifiers so\nmultiple output items cannot be merged accidentally.\n\nFunction calls and their outputs use response.outputitem. names. These\nevents are retained as AIP data chunks rather than generic tool chunks.\nPair them with callid, item.id, and outputindex from the provider\npayload.\n\nThe connector recognizes response.completed as Done and response.failed\nas Error. It also understands an upstream response.incomplete name, although\nthe pinned Responses writer normally persists incomplete state on disconnect\nrather than delivering that terminal event over the broken stream.\n10. Read the final AIP result\n\nThe connector returns this aggregate inside ActionResult.output:\n\n{\n  \"endpointid\": \"replaceendpoint\",\n  \"operation\": \"responsesstream\",\n  \"httpstatus\": 200,\n  \"events\": [],\n  \"text\": \"Three findings are ready for review.\",\n  \"terminal\": true,\n  \"truncated\": false\n}\n\nThe actual events array contains every parsed provider frame, including its\nraw data. text is best-effort convenience output. It is not a substitute for\nthe terminal provider response, finish reason, tool state, or final AIP status.\n\n| Aggregate state | AIP result | Decision |\n\n| truncated: true | Failed with connector.hermesagent.streamtruncated | Preserve partial evidence; do not replay automatically |\n| terminal: false | Failed with connector.hermesagent.streamincomplete | Reconcile original action and provider state |\n| Terminal response.failed, response.incomplete, or error | Failed with connector.hermesagent.streamfailed | Inspect typed error and retained provider event |\n| Terminal run.cancelled | Cancelled | Confirm external effects separately |\n| Other explicit terminal | Completed | Apply Chat or Responses business checks before accepting |\n\nFor Chat, apply the additional finishreason rule from the preceding step.\nFor Responses, require the terminal response object's status to agree with the\nAIP result.\n11. Choose the event limit safely\n\nOmitting maxevents uses the connector default of 4,096 parsed frames. The\nfinal aggregate retains all collected events, so choose a lower application\nbound only when it still leaves substantial headroom for text and tool frames.\n\nThe connector publishes and stores the frame that reaches the limit, then sets\ntruncated true and terminal false. It checks the limit before accepting\nterminal completion. A terminal provider frame that lands exactly at the\nconfigured limit can therefore be published as Done while the final AIP result\nis Failed for truncation.\n\nThe final ActionResult wins over a conflicting last chunk. Do not retry with\na larger limit after model or tool work may have occurred. Use the original\naction evidence to reconcile the effect.\n\nAn upstream close without an explicit terminal produces a Failed result. With\nruntime publication active, the connector also appends an AIP Error chunk with\nreason upstreamstreamclosedwithoutterminalevent. The truncation branch\ndoes not append that extra terminal Error chunk, so always read the result.\n12. Recover a follow disconnect\n\nClosing or losing the native aipd follow SSE does not cancel the action or\nthe upstream Hermes request.\n\nOn reconnect:\n1. keep the original action ID;\n2. resume the same action route with its last retained route cursor;\n3. tolerate a repeated outer event or chunk;\n4. order chunks by AIP sequence and reject conflicting content;\n5. read the final action status or result after the follow stream terminates.\n\nPersist only a cursor returned by the same route and authorization context.\nThe exact cursor formats, paging bounds, and expiry decisions belong to the\nstreaming cursor and cancellation reference later in this documentation set.\n\nDo not resume a provider SSE URL directly. Chat and Responses frames do not\nprovide a usable replay cursor in this connector path.\n13. Cancel the original action\n\nClosing the renderer is not cancellation. Request cancellation explicitly:\n\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIPTOKENFILE\" \\\n  action cancel \"$AIPURL\" \\\n  \"$AIPACTIONID\" \\\n  --reason 'The requester no longer needs this stream'\n\nThe runtime records cancellation and wakes the action's trusted cancellation\ntoken. The connector drops the active HTTP stream. When the Hermes writer\nobserves the disconnect, it attempts agent.interrupt() and cancels its task.\n\nThis is cooperative stop, not effect rollback. A model request, tool call,\nmessage, memory write, or stored response may already exist. Read the original\nAIP result and provider state before deciding that another action is safe.\n\nFor responsesstream with store: true, cancellation can leave a retrievable\nincomplete response snapshot. For Chat, preserve explicit session identity\nand inspect the session according to the session capability reference; do not\nassume partial transcript persistence.\n14. Reconcile a stored Responses stream\n\nAs soon as response.created arrives, persist its response.id. After an\nuncertain terminal outcome, call responseget once with that ID:\n\n{\n  \"responseid\": \"resp0123456789abcdef0123456789ab\"\n}\n\nInterpret the stored status:\n• completed supplies a terminal provider object for review;\n• failed supplies retained failure state;\n• incomplete preserves partial output after disconnect or cancellation;\n• 404 means the response was not stored, was evicted or deleted, or the\n  request reached another response store.\n\nDo not submit a replacement stream merely because responseget returns\nincomplete. Tool or delivery effects can precede that snapshot.\n15. Handle failures\n\n| Symptom | Meaning | Decision |\n\n| No AIP chunks yet | Action may be queued, rejected, or before the first provider frame | Read action status; do not resubmit |\n| Follow SSE disconnected | Delivery failed; action state is unknown to this consumer | Resume from durable cursor and read result |\n| Provider HTTP 429 | Hermes concurrent agent-run gate is full | Preserve original action; follow its typed result |\n| streamtruncated | Connector frame cutoff was reached | Keep partial evidence; do not retry model work |\n| streamincomplete | Upstream closed without explicit terminal | Reconcile action and stored Responses state |\n| streamfailed | Provider delivered a named terminal failure | Inspect retained terminal event and typed error |\n| AIP result Completed but Chat finish is length or error | Connector terminal and Chat business finish disagree | Treat as partial or failed, not success |\n| Cancellation returned Cancelled | Runtime stopped waiting and attempted upstream interruption | Verify provider and external effects |\n| responseget returns 404 | No retained object is reachable at this store | Check original event ID, store, instance, and result |\n\nMatch protocol error codes, not free-form provider messages. Streaming calls\nare retry-unsafe even when the visible text is empty.\n16. Verify the client\n\nBefore accepting the integration, use controlled evidence to verify that the\nclient:\n1. retains the action ID before submission;\n2. follows the same ID with chunks included;\n3. orders and de-duplicates by AIP chunk sequence;\n4. renders Chat from choices rather than relying on part;\n5. handles tool events carried as kind: data;\n6. preserves Responses item and call identifiers;\n7. saves response.id from response.created when storage is enabled;\n8. requires explicit provider terminal evidence and final AIP state;\n9. rejects Chat length and error finishes as complete success;\n10. handles a terminal frame exactly on maxevents as truncation;\n11. resumes follow delivery without creating a new action;\n12. sends explicit cancellation and then reconciles effects;\n13. redacts raw frames, prompts, tools, outputs, IDs, and error data.\n\nThis is a source-aligned client checklist. It is not live model, latency,\navailability, cancellation, or production qualification evidence.\n\nRelated documentation\n• Chat and Responses capabilities (../capabilities/chat-and-responses.md)\n• Hermes capability index (../capabilities/README.md)\n• Persistent sessions (../capabilities/sessions.md)\n• Use native AIP (../../../guides/use-native-aip.md)\n• Transport bindings (../../../reference/transport-bindings.md)\n• Errors and retry decisions (../../../reference/errors.md)\n"
  },
  "integrity": {
    "algorithm": "sha256",
    "sourceDigest": "efe8bb3ed08f4ce28aa95f222972a5100b8ec431f22abb591fcad4e6c568f926"
  }
}
