{
  "schemaVersion": "1.0",
  "title": "Hermes persistent session capabilities",
  "description": "Use these nine endpoint-qualified capabilities when an application needs an addressable Hermes transcript that can be listed, inspected, renamed, branched, continued, or deleted.",
  "canonical": "https://getaip.org/docs/connectors/hermes-agent/capabilities/sessions",
  "route": "/docs/connectors/hermes-agent/capabilities/sessions",
  "source": "docs/connectors/hermes-agent/capabilities/sessions.md",
  "protocol": "Agent Interoperability Protocol",
  "protocolVersion": "1.0",
  "section": "Connectors",
  "documentType": "Connector",
  "language": "en",
  "downloads": {
    "md": "/docs/download/connectors/hermes-agent/capabilities/sessions.md",
    "txt": "/docs/download/connectors/hermes-agent/capabilities/sessions.txt",
    "json": "/docs/download/connectors/hermes-agent/capabilities/sessions.json",
    "pdf": "/docs/download/connectors/hermes-agent/capabilities/sessions.pdf"
  },
  "content": {
    "format": "text/markdown",
    "markdown": "---\ntitle: Hermes persistent session capabilities\ndescription: >-\n  Create, inspect, branch, continue, and delete persisted Hermes transcripts\n  without confusing transcript identity, memory scope, or safe replay\nkind: capability-reference\naudience: application-developer\nappliesTo: \"1.x\"\nwritingStandard: \"aip-docs/1.0\"\nlastReviewedRevision: \"97be86e9efedf07ecf1783b03800f683f107fb04\"\nconnector: hermes-agent\ncapabilityIds:\n  - cap:hermes_agent:<endpoint_id>:sessions_list\n  - cap:hermes_agent:<endpoint_id>:session_create\n  - cap:hermes_agent:<endpoint_id>:session_get\n  - cap:hermes_agent:<endpoint_id>:session_patch\n  - cap:hermes_agent:<endpoint_id>:session_delete\n  - cap:hermes_agent:<endpoint_id>:session_messages\n  - cap:hermes_agent:<endpoint_id>:session_fork\n  - cap:hermes_agent:<endpoint_id>:session_chat\n  - cap:hermes_agent:<endpoint_id>:session_chat_stream\n---\n\n# Hermes persistent session capabilities\n\nUse these nine endpoint-qualified capabilities when an application needs an\naddressable Hermes transcript that can be listed, inspected, renamed, branched,\ncontinued, or deleted.\n\nThe resource is a Hermes `SessionDB` row and its messages. It is not an AIP\nSession, a stored Responses object, or a structured Hermes run. The pinned\nHermes endpoint stores these resources in its shared SQLite `state.db`.\n\nSession chat can run tools and create external effects. A timeout or broken\nconnection is not evidence that nothing happened, and it is not safe to replay\nthe turn blindly.\n\n## Operations\n\nReplace `<endpoint_id>` with the normalized endpoint ID present in AIP\ndiscovery.\n\n| Operation | Provider request | Kind | Risk | Approval | Key | Retry |\n|---|---|---|---|---|---|---|\n| `sessions_list` | `GET /api/sessions` | Tool | Low | No | Optional | Safe |\n| `session_create` | `POST /api/sessions` | Tool | Medium | No | Required | Unsafe |\n| `session_get` | `GET /api/sessions/{session_id}` | Tool | Low | No | Optional | Safe |\n| `session_patch` | `PATCH /api/sessions/{session_id}` | Tool | Medium | No | Required | Unsafe |\n| `session_delete` | `DELETE /api/sessions/{session_id}` | Tool | High | Yes | Required | Unsafe |\n| `session_messages` | `GET /api/sessions/{session_id}/messages` | Tool | Low | No | Optional | Safe |\n| `session_fork` | `POST /api/sessions/{session_id}/fork` | Tool | Medium | No | Required | Unsafe |\n| `session_chat` | `POST /api/sessions/{session_id}/chat` | Agent | Medium | No | Optional | Unsafe |\n| `session_chat_stream` | `POST /api/sessions/{session_id}/chat/stream` | Agent | Medium | No | Optional | Unsafe |\n\nAll nine operations are stable and require the selected endpoint's Bearer\ncredential. The endpoint comes from the capability ID. Input cannot select a\ndifferent endpoint or provider URL.\n\nThe connector percent-encodes every dynamic `session_id` path segment and\nrejects carriage return, line feed, and NUL characters before making a request.\n\n## Choose the correct continuity model\n\n| Need | Use | Durable identity |\n|---|---|---|\n| Address and inspect a Hermes transcript | A capability on this page | Hermes `session_id` |\n| Continue ordinary Chat or a stored Responses chain | Chat and Responses capabilities | Chat `session_id`, Responses ID, or conversation name |\n| Poll work or resume a provider approval | Structured-run capabilities | Hermes `run_id` |\n| Carry protocol-level interaction context | AIP Session | AIP `SessionId` |\n| Scope long-term memory across transcripts | `session_key` on a chat call | `X-Hermes-Session-Key` value |\n\nThese identifiers are independent. A path `session_id` selects one persisted\ntranscript. A `session_key` scopes optional long-term memory and does not select,\ncreate, rename, or authorize that transcript.\n\nAn AIP Action ID and idempotency key govern one AIP invocation. Neither is a\nHermes transcript ID. See\n[Actions and Sessions](../../../concepts/actions-and-sessions.md) for the\nprotocol-level distinction.\n\n## Shared AIP contract\n\n| Contract field | Read operations | Create, patch, and fork | Delete | Chat operations |\n|---|---|---|---|---|\n| Side effects | `read`, `external_network` | `read`, `write`, `external_network` | `read`, `delete`, `external_network` | `read`, `write`, `send_message`, `external_network` |\n| Published data class | Internal; no PII | Confidential; contains PII | Restricted; contains PII | Confidential; contains PII |\n| Redaction | Required | Required | Required | Required |\n| Idempotency | Optional | Required | Required | Optional |\n| Retry support | Yes | No | No | No |\n| Retry safety | Safe | Unsafe | Unsafe | Unsafe |\n| Expected completion | Sync | Sync | Sync | Sync or streaming |\n| Transaction | None | None | None | None |\n| Compensation | Rollback not supported | Rollback not supported | Rollback not supported; approval required | Rollback not supported |\n\nEvery operation supports synchronous AIP execution. `session_chat_stream` also\nadvertises streaming and cancellation, with streaming expected completion. It\ndoes not advertise async support, and `async_expected` is false.\n\nThe contract declares a 60-second timeout for non-streaming operations and a\n300-second timeout for the stream. The connector's HTTP timeout is 300 seconds\nfor `session_chat` and 30 seconds for every other JSON operation. The streaming\nrequest does not override the default connector client's 900-second timeout.\nTrusted execution is also bounded by its AIP deadline and cancellation token.\n\nExpected latency is 5 seconds for non-streaming operations and 30 seconds for\nthe stream. The maximum queue-delay hint is 10 seconds, and the availability\ntarget is `99.9%`. These are contract declarations, not live performance or\nqualification evidence.\n\n## List sessions\n\nUse `sessions_list` to enumerate eligible rows from the endpoint's shared\nsession database:\n\n```json\n{\n  \"limit\": 50,\n  \"offset\": 0,\n  \"source\": \"api_server\",\n  \"include_children\": false\n}\n```\n\n`limit` defaults to 50 and is capped at 200. `offset` defaults to zero and is\ncapped at 1,000,000. The provider orders results by most recent activity.\n\nBy default, compression continuations and delegate children are hidden. Branch\nsessions remain listable. Set `include_children` to `true` only for an\nadministrative view that can distinguish those row types.\n\nArchived rows are also excluded. The pinned session-list route has no query\nparameter that includes or selects archived rows.\n\nThe optional `source` filter is exact. A session created through this API uses\n`api_server`, but the same database can contain CLI, gateway, cron, and other\nHermes sessions. An unfiltered list is not an inventory of AIP-created rows.\n\nThe connector also accepts a `query` object and forwards its scalar keys. Use\nthe typed top-level fields where possible. Supplying the same field both inside\n`query` and at the top level produces duplicate query parameters.\n\nA result has this provider shape:\n\n```json\n{\n  \"object\": \"list\",\n  \"data\": [\n    {\n      \"id\": \"support-case-2030\",\n      \"source\": \"api_server\",\n      \"model\": \"hermes-agent\",\n      \"title\": \"Support case 2030\",\n      \"message_count\": 4,\n      \"last_active\": 1893456120.0,\n      \"preview\": \"Review the current case notes\",\n      \"has_system_prompt\": true,\n      \"has_model_config\": false\n    }\n  ],\n  \"limit\": 50,\n  \"offset\": 0,\n  \"has_more\": false\n}\n```\n\n`has_more` is `true` whenever the returned page length equals `limit`. It is not\nan exact total-count calculation and can be true on the final full page. Avoid\na zero limit, advance `offset` by the returned count, and stop on a shorter or\nempty page.\n\nPagination is offset-based over a changing last-activity order, not a snapshot\nor cursor. Concurrent activity can move a row between pages. De-duplicate by\nsession ID, retain `_lineage_root_id` when present, and re-list when a complete\nadministrative inventory matters.\n\nWhen a root was ended by context compression, the list can project it to its\nlatest continuation. The visible `id`, counts, title, preview, and activity then\nbelong to the tip. `_lineage_root_id` retains the original root identity, while\n`started_at` remains the root's original start time.\n\n## Create a session\n\n`session_create` accepts a provider-native object. A top-level `body` wrapper\nkeeps transport fields separate:\n\n```json\n{\n  \"body\": {\n    \"id\": \"support-case-2030\",\n    \"title\": \"Support case 2030\",\n    \"model\": \"hermes-agent\",\n    \"system_prompt\": \"Keep responses factual and concise.\"\n  }\n}\n```\n\nPut the required AIP idempotency key on the Action, not inside `body`. The\nconnector unwraps `body`, sends the endpoint credential, and forwards the key\nas `Idempotency-Key`.\n\nThe provider selects the first non-empty `id` or `session_id`. If neither is\ntruthy, including when an empty string is supplied, it generates\n`api_<epoch-seconds>_<8-hex>`. A selected ID must remain non-empty after\ntrimming, be no longer than 256 characters, contain no control characters, and\ncontain no `..`, slash, backslash, or leading Windows drive prefix.\n\nTitles are trimmed, control characters are removed, whitespace is collapsed,\nand empty text becomes no title. A title must be unique across the shared\ndatabase and cannot exceed 100 characters. Invalid titles make creation fail;\nthe handler removes the row it just created before returning the error.\n\nThe optional model defaults to the endpoint's advertised model. A\n`system_prompt` must be a string. Unknown provider fields are not rejected by\nthe handler, but they do not become durable fields merely because the open AIP\nschema accepts them.\n\nCreate stores `model` and `system_prompt` as session metadata. The pinned\nsession-chat handler does not use the create request's model as a per-turn model\nroute, and a stored system prompt is not a substitute for the current turn's\n`system_message`. Model selection and prompt restoration still follow Hermes\nruntime policy.\n\nThe response exposes a client-safe projection. It reports\n`has_system_prompt` and `has_model_config` rather than returning their stored\ncontents.\n\n## Get or patch metadata\n\nRead one exact row with:\n\n```json\n{\n  \"session_id\": \"support-case-2030\"\n}\n```\n\n`session_get` does not follow a compression continuation. It returns the exact\nrow selected by the path. Use the list projection or the `session_id` returned\nby `session_messages` to discover a current tip.\n\nPatch only `title` and `end_reason`:\n\n```json\n{\n  \"session_id\": \"support-case-2030\",\n  \"body\": {\n    \"title\": \"Support case 2030 — reviewed\",\n    \"end_reason\": \"client_closed\"\n  }\n}\n```\n\nA null or empty title clears it. An empty `end_reason` is ignored. A non-empty\nreason sets `ended_at` and `end_reason` only when the session is still open.\nThe first close reason wins; patch does not reopen a closed session.\n\nAn empty patch body is a successful no-op and returns the current safe session\nprojection.\n\nClosure is metadata, not an authorization state. The pinned chat handlers check\nthat the row exists but do not reject an ended session or reopen it. An\napplication that treats `end_reason` as final must prevent further chat itself.\n\nThe provider rejects every other patch field with\n`unsupported_session_field`. It does not expose an API patch for the stored\nmodel, system prompt, token counts, parent, or message history.\n\n## Read active messages\n\nCall `session_messages` with the selected `session_id`. Unlike `session_get`,\nthis operation resolves a compression chain before reading. Always inspect the\ntop-level `session_id` in its result:\n\n```json\n{\n  \"object\": \"list\",\n  \"session_id\": \"support-case-2030-continuation\",\n  \"data\": [\n    {\n      \"id\": 41,\n      \"session_id\": \"support-case-2030-continuation\",\n      \"role\": \"user\",\n      \"content\": \"Summarize the verified findings.\",\n      \"timestamp\": 1893456100.0\n    },\n    {\n      \"id\": 42,\n      \"session_id\": \"support-case-2030-continuation\",\n      \"role\": \"assistant\",\n      \"content\": \"Three findings are verified.\",\n      \"finish_reason\": \"stop\",\n      \"timestamp\": 1893456120.0\n    }\n  ]\n}\n```\n\nMessages are returned in database insertion order. The route returns active\nrows only and has no pagination parameter. It can expose content, tool calls,\ntool results, reasoning, token counts, and finish metadata, but it omits private\ndatabase columns outside the client-safe allowlist.\n\nAfter resolution, the route reads only the selected descendant row. It does not\nconcatenate messages from compression ancestors into one API response.\n\nSoft-deleted or compaction-archived rows are not returned. The API does not\nprovide a switch to include them.\n\n## Fork a transcript\n\nUse `session_fork` to create a child containing a copy of the source's active\nmessages:\n\n```json\n{\n  \"session_id\": \"support-case-2030\",\n  \"body\": {\n    \"id\": \"support-case-2030-alternative\",\n    \"title\": \"Support case 2030 — alternative\"\n  }\n}\n```\n\nThe provider marks the source as ended with `branched` when it is still open.\nIt creates an `api_server` child, copies the source model and system prompt,\nsets `parent_session_id`, and copies active messages. It does not copy inactive\nhistory or the complete source row.\n\nFork does not require an open source. When the source already has another close\nreason, the first reason remains. The child is still created, but the default\nchild-filtered list may not recognize it as a visible branch. Retain its ID and\nread it directly, or inspect an administrative list with children included.\n\nIf no child ID is supplied, the provider generates one. If no title is\nsupplied, it derives the next unique lineage title, such as `Case #2`.\n\nFork is not atomic in the pinned provider. The source is ended, the child is\ncreated, and messages are copied before the title is validated. A duplicate or\ninvalid title can therefore return HTTP 400 after durable changes occurred.\nAfter any fork error, list and inspect both source and intended child before\ndeciding whether another Action is safe.\n\nAn absent or empty fork ID triggers provider generation. A selected\nwhitespace-only ID, or one containing CR, LF, or NUL, is rejected. The fork\nhandler does not apply the create handler's full path-safety or 256-character\nchecks. Applications should apply the stricter create-ID rules to both\noperations.\n\n## Continue a session with chat\n\n`session_chat` runs one turn against the stored active history:\n\n```json\n{\n  \"session_id\": \"support-case-2030\",\n  \"message\": \"Summarize only the findings supported by the transcript.\",\n  \"system_message\": \"Return at most three bullets.\",\n  \"session_key\": \"support:case-review\"\n}\n```\n\nThe provider requires a visible `message` or `input`. The connector also maps a\nnon-empty top-level `prompt` or `query` to `message`. A non-empty\n`system_message` takes precedence over `instructions` and applies as an\nephemeral system instruction for this turn.\n\nText can be a string or a list of text and image parts. Hermes accepts\n`text`/`input_text` and `image_url`/`input_image`, with HTTP, HTTPS, or\n`data:image/...` image URLs. File parts and other content types fail with HTTP\n400.\n\nA string and each individual text part are capped at 65,536 characters, and the\ncontent list is capped at 1,000 parts. The pinned multimodal normalizer does not\nimpose a second aggregate text cap across all accepted parts. The\n10,000,000-byte request limit remains the outer bound.\n\n`session_key` becomes `X-Hermes-Session-Key`. Hermes accepts that header only\nwhen its API key is configured, because the key selects a long-term-memory\nscope. It does not replace path-based transcript identity.\n\nThe agent receives the exact path session ID and attempts to load its active\nconversation. If that history read raises, the handler logs the failure and\ncontinues with empty history.\n\nThe agent is wired to the shared `SessionDB` and attempts to persist the new\nturn. Session creation and message-append failures are logged and do not\nnecessarily fail the model response. A successful chat result therefore does\nnot prove that old history was loaded or that the new turn is durable. Verify\nwith `session_messages`.\n\nContext compression can rotate the effective transcript during the turn. Use\nthe returned `session_id` for the next call.\n\nA successful provider result has this shape:\n\n```json\n{\n  \"object\": \"hermes.session.chat.completion\",\n  \"session_id\": \"support-case-2030\",\n  \"message\": {\n    \"role\": \"assistant\",\n    \"content\": \"Three findings are supported by the transcript.\"\n  },\n  \"usage\": {\n    \"input_tokens\": 240,\n    \"output_tokens\": 12,\n    \"total_tokens\": 252\n  }\n}\n```\n\nThe connector places that object in the completed AIP ActionResult output. A\nprovider response header also carries the effective session ID, but the JSON\nfield is the portable value available in connector output.\n\nModel execution may call tools, send messages, or mutate external systems.\nNeither closing the HTTP connection nor deleting the transcript compensates\nthose effects.\n\n## Bound streaming behavior\n\n`session_chat_stream` accepts the same turn fields and returns the connector's\nstream aggregate. On a successful turn, the provider emits `run.started`,\n`message.started`, assistant deltas, tool progress, `assistant.completed`,\n`run.completed`, then `done`. Each data event carries a generated run ID,\nsequence, session ID, and timestamp.\n\nThe provider writes an SSE keepalive comment after 30 seconds without an event.\nThe connector retains at most 4,096 parsed events by default. A positive\n`max_events` can lower that bound; reaching it returns `truncated: true` and\n`terminal: false`.\n\nDo not interpret truncation, disconnect, or cancellation as rollback. The\nstreaming guide and cursor reference own detailed publication, terminal, and\ncancellation decisions.\n\n## Understand persistence and lineage\n\nThe provider lazily opens `HERMES_HOME/state.db`. It uses the same SQLite store\nas other local Hermes interfaces, with WAL and application-level write retries.\nThere is no separate AIP session database inside this connector.\n\nIf the database cannot be opened, resource and session-chat routes return\n`session_db_unavailable` with HTTP 503 before starting their task. The pinned\nsession API does not fall back to an in-memory transcript store.\n\nDeleting a session removes its row and messages. Delegate descendants are\ncascade-deleted. Branch and compression children are preserved and have their\n`parent_session_id` cleared.\n\nThe API handler does not pass a transcript-directory path into `SessionDB`\ndeletion. Separate `.json`, `.jsonl`, and `request_dump_*` files are therefore\nnot removed by this route. Deletion also does not remove long-term memory,\nstored Responses objects, external tool effects, or provider data outside this\ndatabase.\n\nSQLite persistence is local to the configured Hermes home. The pinned code\ndoes not replicate session rows across endpoint instances. Route a given\nsession to the endpoint that owns its database, or provide a deployment-level\nshared-storage design and validate its concurrency behavior separately.\n\n## Protect transcript data\n\nThe AIP manifest classifies `sessions_list`, `session_get`, and\n`session_messages` as Internal and `contains_pii: false` because they are marked\nread-only. That mechanical classification is weaker than the actual outputs.\n\nLists can contain `user_id` and a message preview. Session rows expose activity\nand cost metadata. Message reads can contain user text, assistant text,\nreasoning, tool arguments, and tool results. Treat all three outputs as\nconfidential and potentially PII-bearing, and redact them from logs and traces.\n\nThe safe provider projection hides full stored system prompts and model\nconfiguration, but it is not a general content-redaction layer. Apply tenant\nauthorization before exposing any session selected by caller-controlled ID.\n\nThe Bearer credential authenticates access to the Hermes API, not ownership of\nan individual session row. The pinned handlers do not compare the AIP principal\nor tenant with a row's `user_id` before list, read, patch, fork, chat, or delete.\nIsolate the endpoint and `state.db` at the intended tenant boundary, or add a\nseparate row-authorization layer before public exposure.\n\n## Apply idempotency and retry rules\n\n`session_create`, `session_patch`, `session_fork`, and `session_delete` require\nan AIP idempotency key. The runtime scopes it to the capability, retains it for\n24 hours, and uses `ReturnOriginalResult` on collision.\n\nWithin that window, reuse of the same scoped key returns the stored result even\nwhen the new Action has different input. This collision mode does not\nrevalidate the retained input hash. Use a fresh key for every changed session,\nbody, or business intent.\n\n`session_delete` also requires tenant-policy approval. That approval lasts 15\nminutes and requires a reason, input snapshot, and policy-decision evidence.\n\nHermes does not enforce `Idempotency-Key` on these session handlers. The AIP\nruntime is the replay boundary. Bypassing AIP or losing its idempotency store\nremoves that protection.\n\nOnly list, get, and messages are published as retry-supported and safe. For a\ntransport failure or remote 5xx on a mutation, reconcile provider state before\nsubmitting a new Action. For chat, inspect messages and downstream systems, but\ndo not assume that absence of a final assistant row proves that no tool ran.\n\nThe generic connector marks non-read transport failures, deadlines, remote\n429, and remote 5xx as uncertain outcomes. The fork title failure described\nabove is a stricter exception: the provider returns 400 after possible writes,\nwhile generic connector metadata classifies that status as a definitive remote\nrejection. Reconcile it manually.\n\nThat non-retryable 400 result can also be settled under the fork's idempotency\nkey. Reusing the key then returns the stored failure; it does not inspect or\nrepair the partially created child.\n\nSee [Approvals and Policy](../../../concepts/approvals-and-policy.md) for the\ndelete approval record and [Errors and Retry Decisions](../../../reference/errors.md)\nfor the shared AIP failure model.\n\n## Handle provider failures\n\n| Signal | Meaning | Application decision |\n|---|---|---|\n| `400 invalid_session_id` | A selected create or fork ID is unsafe, too long where checked, or whitespace-only | Correct the ID; inspect state first after fork |\n| `400 invalid_title` | Title is invalid or already used | Choose a valid unique title; inspect state first after fork |\n| `400 unsupported_session_field` | Patch contains a non-public field | Send only `title` and `end_reason` |\n| `400 missing_message` or content error | Session chat has no usable text/image or uses an unsupported part | Correct the current turn; do not alter prior history |\n| `401 invalid_api_key` | Endpoint credential is absent or wrong | Repair endpoint authentication; do not retry unchanged |\n| `403` for `X-Hermes-Session-Key` | Hermes has no API key configured for memory scoping | Configure authenticated access or omit the memory key |\n| `404 session_not_found` | The exact path row does not exist | Re-list, resolve any compression tip, and verify endpoint routing |\n| `409 session_exists` | Create or fork child ID already exists | Read the existing row before choosing a new identity |\n| `413 body_too_large` | JSON body exceeds 10,000,000 bytes | Reduce current input; do not split one mutation into blind retries |\n| `503 session_db_unavailable` | Hermes could not open its shared session database | Restore storage, then reconcile before mutation retry |\n| Connector timeout or transport failure | Completion is unknown for non-read work | Inspect session and external effects before any new Action |\n\nThe pinned session-chat handlers increment the in-flight run counter but do not\ncall the provider's shared concurrency-limit check used by ordinary Chat,\nResponses, and structured runs. Do not infer that the published queue-delay or\navailability hints enforce a session-specific concurrency ceiling.\n\n## Verify an integration\n\nBefore relying on this family in an application:\n\n1. Discover all nine capability IDs for the intended endpoint and verify the\n   pinned connector revision.\n2. Confirm the endpoint credential and tenant routing described in\n   [Authentication and Endpoints](../getting-started/authentication-and-endpoints.md).\n3. Create a uniquely named session with an AIP idempotency key, then get it and\n   locate it in a filtered list.\n4. Patch its title, read the result, and verify that unknown fields are rejected.\n5. Submit one harmless chat turn, verify the returned effective session ID, and\n   read the active message sequence.\n6. Fork the session with a unique child ID and title. Verify the parent end\n   state, child parent ID, and copied active transcript.\n7. Exercise delete only with tenant-policy approval and a fresh idempotency key.\n   Verify preserved branches and any retained external transcript artifacts.\n8. Record Action IDs, idempotency keys, endpoint identity, status, and redacted\n   evidence without logging transcript content or credentials.\n\nFor general model requests, compare\n[Chat and Responses](chat-and-responses.md). Return to the\n[Hermes capability index](README.md) when another operation family owns the\ntask.\n",
    "text": "Hermes persistent session capabilities\n\nUse these nine endpoint-qualified capabilities when an application needs an\naddressable Hermes transcript that can be listed, inspected, renamed, branched,\ncontinued, or deleted.\n\nThe resource is a Hermes SessionDB row and its messages. It is not an AIP\nSession, a stored Responses object, or a structured Hermes run. The pinned\nHermes endpoint stores these resources in its shared SQLite state.db.\n\nSession chat can run tools and create external effects. A timeout or broken\nconnection is not evidence that nothing happened, and it is not safe to replay\nthe turn blindly.\n\nOperations\n\nReplace  with the normalized endpoint ID present in AIP\ndiscovery.\n\n| Operation | Provider request | Kind | Risk | Approval | Key | Retry |\n\n| sessionslist | GET /api/sessions | Tool | Low | No | Optional | Safe |\n| sessioncreate | POST /api/sessions | Tool | Medium | No | Required | Unsafe |\n| sessionget | GET /api/sessions/{sessionid} | Tool | Low | No | Optional | Safe |\n| sessionpatch | PATCH /api/sessions/{sessionid} | Tool | Medium | No | Required | Unsafe |\n| sessiondelete | DELETE /api/sessions/{sessionid} | Tool | High | Yes | Required | Unsafe |\n| sessionmessages | GET /api/sessions/{sessionid}/messages | Tool | Low | No | Optional | Safe |\n| sessionfork | POST /api/sessions/{sessionid}/fork | Tool | Medium | No | Required | Unsafe |\n| sessionchat | POST /api/sessions/{sessionid}/chat | Agent | Medium | No | Optional | Unsafe |\n| sessionchatstream | POST /api/sessions/{sessionid}/chat/stream | Agent | Medium | No | Optional | Unsafe |\n\nAll nine operations are stable and require the selected endpoint's Bearer\ncredential. The endpoint comes from the capability ID. Input cannot select a\ndifferent endpoint or provider URL.\n\nThe connector percent-encodes every dynamic sessionid path segment and\nrejects carriage return, line feed, and NUL characters before making a request.\n\nChoose the correct continuity model\n\n| Need | Use | Durable identity |\n\n| Address and inspect a Hermes transcript | A capability on this page | Hermes sessionid |\n| Continue ordinary Chat or a stored Responses chain | Chat and Responses capabilities | Chat sessionid, Responses ID, or conversation name |\n| Poll work or resume a provider approval | Structured-run capabilities | Hermes runid |\n| Carry protocol-level interaction context | AIP Session | AIP SessionId |\n| Scope long-term memory across transcripts | sessionkey on a chat call | X-Hermes-Session-Key value |\n\nThese identifiers are independent. A path sessionid selects one persisted\ntranscript. A sessionkey scopes optional long-term memory and does not select,\ncreate, rename, or authorize that transcript.\n\nAn AIP Action ID and idempotency key govern one AIP invocation. Neither is a\nHermes transcript ID. See\nActions and Sessions (../../../concepts/actions-and-sessions.md) for the\nprotocol-level distinction.\n\nShared AIP contract\n\n| Contract field | Read operations | Create, patch, and fork | Delete | Chat operations |\n\n| Side effects | read, externalnetwork | read, write, externalnetwork | read, delete, externalnetwork | read, write, sendmessage, externalnetwork |\n| Published data class | Internal; no PII | Confidential; contains PII | Restricted; contains PII | Confidential; contains PII |\n| Redaction | Required | Required | Required | Required |\n| Idempotency | Optional | Required | Required | Optional |\n| Retry support | Yes | No | No | No |\n| Retry safety | Safe | Unsafe | Unsafe | Unsafe |\n| Expected completion | Sync | Sync | Sync | Sync or streaming |\n| Transaction | None | None | None | None |\n| Compensation | Rollback not supported | Rollback not supported | Rollback not supported; approval required | Rollback not supported |\n\nEvery operation supports synchronous AIP execution. sessionchatstream also\nadvertises streaming and cancellation, with streaming expected completion. It\ndoes not advertise async support, and asyncexpected is false.\n\nThe contract declares a 60-second timeout for non-streaming operations and a\n300-second timeout for the stream. The connector's HTTP timeout is 300 seconds\nfor sessionchat and 30 seconds for every other JSON operation. The streaming\nrequest does not override the default connector client's 900-second timeout.\nTrusted execution is also bounded by its AIP deadline and cancellation token.\n\nExpected latency is 5 seconds for non-streaming operations and 30 seconds for\nthe stream. The maximum queue-delay hint is 10 seconds, and the availability\ntarget is 99.9%. These are contract declarations, not live performance or\nqualification evidence.\n\nList sessions\n\nUse sessionslist to enumerate eligible rows from the endpoint's shared\nsession database:\n\n{\n  \"limit\": 50,\n  \"offset\": 0,\n  \"source\": \"apiserver\",\n  \"includechildren\": false\n}\n\nlimit defaults to 50 and is capped at 200. offset defaults to zero and is\ncapped at 1,000,000. The provider orders results by most recent activity.\n\nBy default, compression continuations and delegate children are hidden. Branch\nsessions remain listable. Set includechildren to true only for an\nadministrative view that can distinguish those row types.\n\nArchived rows are also excluded. The pinned session-list route has no query\nparameter that includes or selects archived rows.\n\nThe optional source filter is exact. A session created through this API uses\napiserver, but the same database can contain CLI, gateway, cron, and other\nHermes sessions. An unfiltered list is not an inventory of AIP-created rows.\n\nThe connector also accepts a query object and forwards its scalar keys. Use\nthe typed top-level fields where possible. Supplying the same field both inside\nquery and at the top level produces duplicate query parameters.\n\nA result has this provider shape:\n\n{\n  \"object\": \"list\",\n  \"data\": [\n    {\n      \"id\": \"support-case-2030\",\n      \"source\": \"apiserver\",\n      \"model\": \"hermes-agent\",\n      \"title\": \"Support case 2030\",\n      \"messagecount\": 4,\n      \"lastactive\": 1893456120.0,\n      \"preview\": \"Review the current case notes\",\n      \"hassystemprompt\": true,\n      \"hasmodelconfig\": false\n    }\n  ],\n  \"limit\": 50,\n  \"offset\": 0,\n  \"hasmore\": false\n}\n\nhasmore is true whenever the returned page length equals limit. It is not\nan exact total-count calculation and can be true on the final full page. Avoid\na zero limit, advance offset by the returned count, and stop on a shorter or\nempty page.\n\nPagination is offset-based over a changing last-activity order, not a snapshot\nor cursor. Concurrent activity can move a row between pages. De-duplicate by\nsession ID, retain lineagerootid when present, and re-list when a complete\nadministrative inventory matters.\n\nWhen a root was ended by context compression, the list can project it to its\nlatest continuation. The visible id, counts, title, preview, and activity then\nbelong to the tip. lineagerootid retains the original root identity, while\nstartedat remains the root's original start time.\n\nCreate a session\n\nsessioncreate accepts a provider-native object. A top-level body wrapper\nkeeps transport fields separate:\n\n{\n  \"body\": {\n    \"id\": \"support-case-2030\",\n    \"title\": \"Support case 2030\",\n    \"model\": \"hermes-agent\",\n    \"systemprompt\": \"Keep responses factual and concise.\"\n  }\n}\n\nPut the required AIP idempotency key on the Action, not inside body. The\nconnector unwraps body, sends the endpoint credential, and forwards the key\nas Idempotency-Key.\n\nThe provider selects the first non-empty id or sessionid. If neither is\ntruthy, including when an empty string is supplied, it generates\napi. A selected ID must remain non-empty after\ntrimming, be no longer than 256 characters, contain no control characters, and\ncontain no .., slash, backslash, or leading Windows drive prefix.\n\nTitles are trimmed, control characters are removed, whitespace is collapsed,\nand empty text becomes no title. A title must be unique across the shared\ndatabase and cannot exceed 100 characters. Invalid titles make creation fail;\nthe handler removes the row it just created before returning the error.\n\nThe optional model defaults to the endpoint's advertised model. A\nsystemprompt must be a string. Unknown provider fields are not rejected by\nthe handler, but they do not become durable fields merely because the open AIP\nschema accepts them.\n\nCreate stores model and systemprompt as session metadata. The pinned\nsession-chat handler does not use the create request's model as a per-turn model\nroute, and a stored system prompt is not a substitute for the current turn's\nsystemmessage. Model selection and prompt restoration still follow Hermes\nruntime policy.\n\nThe response exposes a client-safe projection. It reports\nhassystemprompt and hasmodelconfig rather than returning their stored\ncontents.\n\nGet or patch metadata\n\nRead one exact row with:\n\n{\n  \"sessionid\": \"support-case-2030\"\n}\n\nsessionget does not follow a compression continuation. It returns the exact\nrow selected by the path. Use the list projection or the sessionid returned\nby sessionmessages to discover a current tip.\n\nPatch only title and endreason:\n\n{\n  \"sessionid\": \"support-case-2030\",\n  \"body\": {\n    \"title\": \"Support case 2030 — reviewed\",\n    \"endreason\": \"clientclosed\"\n  }\n}\n\nA null or empty title clears it. An empty endreason is ignored. A non-empty\nreason sets endedat and endreason only when the session is still open.\nThe first close reason wins; patch does not reopen a closed session.\n\nAn empty patch body is a successful no-op and returns the current safe session\nprojection.\n\nClosure is metadata, not an authorization state. The pinned chat handlers check\nthat the row exists but do not reject an ended session or reopen it. An\napplication that treats endreason as final must prevent further chat itself.\n\nThe provider rejects every other patch field with\nunsupportedsessionfield. It does not expose an API patch for the stored\nmodel, system prompt, token counts, parent, or message history.\n\nRead active messages\n\nCall sessionmessages with the selected sessionid. Unlike sessionget,\nthis operation resolves a compression chain before reading. Always inspect the\ntop-level sessionid in its result:\n\n{\n  \"object\": \"list\",\n  \"sessionid\": \"support-case-2030-continuation\",\n  \"data\": [\n    {\n      \"id\": 41,\n      \"sessionid\": \"support-case-2030-continuation\",\n      \"role\": \"user\",\n      \"content\": \"Summarize the verified findings.\",\n      \"timestamp\": 1893456100.0\n    },\n    {\n      \"id\": 42,\n      \"sessionid\": \"support-case-2030-continuation\",\n      \"role\": \"assistant\",\n      \"content\": \"Three findings are verified.\",\n      \"finishreason\": \"stop\",\n      \"timestamp\": 1893456120.0\n    }\n  ]\n}\n\nMessages are returned in database insertion order. The route returns active\nrows only and has no pagination parameter. It can expose content, tool calls,\ntool results, reasoning, token counts, and finish metadata, but it omits private\ndatabase columns outside the client-safe allowlist.\n\nAfter resolution, the route reads only the selected descendant row. It does not\nconcatenate messages from compression ancestors into one API response.\n\nSoft-deleted or compaction-archived rows are not returned. The API does not\nprovide a switch to include them.\n\nFork a transcript\n\nUse sessionfork to create a child containing a copy of the source's active\nmessages:\n\n{\n  \"sessionid\": \"support-case-2030\",\n  \"body\": {\n    \"id\": \"support-case-2030-alternative\",\n    \"title\": \"Support case 2030 — alternative\"\n  }\n}\n\nThe provider marks the source as ended with branched when it is still open.\nIt creates an apiserver child, copies the source model and system prompt,\nsets parentsessionid, and copies active messages. It does not copy inactive\nhistory or the complete source row.\n\nFork does not require an open source. When the source already has another close\nreason, the first reason remains. The child is still created, but the default\nchild-filtered list may not recognize it as a visible branch. Retain its ID and\nread it directly, or inspect an administrative list with children included.\n\nIf no child ID is supplied, the provider generates one. If no title is\nsupplied, it derives the next unique lineage title, such as Case #2.\n\nFork is not atomic in the pinned provider. The source is ended, the child is\ncreated, and messages are copied before the title is validated. A duplicate or\ninvalid title can therefore return HTTP 400 after durable changes occurred.\nAfter any fork error, list and inspect both source and intended child before\ndeciding whether another Action is safe.\n\nAn absent or empty fork ID triggers provider generation. A selected\nwhitespace-only ID, or one containing CR, LF, or NUL, is rejected. The fork\nhandler does not apply the create handler's full path-safety or 256-character\nchecks. Applications should apply the stricter create-ID rules to both\noperations.\n\nContinue a session with chat\n\nsessionchat runs one turn against the stored active history:\n\n{\n  \"sessionid\": \"support-case-2030\",\n  \"message\": \"Summarize only the findings supported by the transcript.\",\n  \"systemmessage\": \"Return at most three bullets.\",\n  \"sessionkey\": \"support:case-review\"\n}\n\nThe provider requires a visible message or input. The connector also maps a\nnon-empty top-level prompt or query to message. A non-empty\nsystemmessage takes precedence over instructions and applies as an\nephemeral system instruction for this turn.\n\nText can be a string or a list of text and image parts. Hermes accepts\ntext/inputtext and imageurl/inputimage, with HTTP, HTTPS, or\ndata:image/... image URLs. File parts and other content types fail with HTTP\n400. A string and each individual text part are capped at 65,536 characters, and the\ncontent list is capped at 1,000 parts. The pinned multimodal normalizer does not\nimpose a second aggregate text cap across all accepted parts. The\n10,000,000-byte request limit remains the outer bound.\n\nsessionkey becomes X-Hermes-Session-Key. Hermes accepts that header only\nwhen its API key is configured, because the key selects a long-term-memory\nscope. It does not replace path-based transcript identity.\n\nThe agent receives the exact path session ID and attempts to load its active\nconversation. If that history read raises, the handler logs the failure and\ncontinues with empty history.\n\nThe agent is wired to the shared SessionDB and attempts to persist the new\nturn. Session creation and message-append failures are logged and do not\nnecessarily fail the model response. A successful chat result therefore does\nnot prove that old history was loaded or that the new turn is durable. Verify\nwith sessionmessages.\n\nContext compression can rotate the effective transcript during the turn. Use\nthe returned sessionid for the next call.\n\nA successful provider result has this shape:\n\n{\n  \"object\": \"hermes.session.chat.completion\",\n  \"sessionid\": \"support-case-2030\",\n  \"message\": {\n    \"role\": \"assistant\",\n    \"content\": \"Three findings are supported by the transcript.\"\n  },\n  \"usage\": {\n    \"inputtokens\": 240,\n    \"outputtokens\": 12,\n    \"totaltokens\": 252\n  }\n}\n\nThe connector places that object in the completed AIP ActionResult output. A\nprovider response header also carries the effective session ID, but the JSON\nfield is the portable value available in connector output.\n\nModel execution may call tools, send messages, or mutate external systems.\nNeither closing the HTTP connection nor deleting the transcript compensates\nthose effects.\n\nBound streaming behavior\n\nsessionchatstream accepts the same turn fields and returns the connector's\nstream aggregate. On a successful turn, the provider emits run.started,\nmessage.started, assistant deltas, tool progress, assistant.completed,\nrun.completed, then done. Each data event carries a generated run ID,\nsequence, session ID, and timestamp.\n\nThe provider writes an SSE keepalive comment after 30 seconds without an event.\nThe connector retains at most 4,096 parsed events by default. A positive\nmaxevents can lower that bound; reaching it returns truncated: true and\nterminal: false.\n\nDo not interpret truncation, disconnect, or cancellation as rollback. The\nstreaming guide and cursor reference own detailed publication, terminal, and\ncancellation decisions.\n\nUnderstand persistence and lineage\n\nThe provider lazily opens HERMESHOME/state.db. It uses the same SQLite store\nas other local Hermes interfaces, with WAL and application-level write retries.\nThere is no separate AIP session database inside this connector.\n\nIf the database cannot be opened, resource and session-chat routes return\nsessiondbunavailable with HTTP 503 before starting their task. The pinned\nsession API does not fall back to an in-memory transcript store.\n\nDeleting a session removes its row and messages. Delegate descendants are\ncascade-deleted. Branch and compression children are preserved and have their\nparentsessionid cleared.\n\nThe API handler does not pass a transcript-directory path into SessionDB\ndeletion. Separate .json, .jsonl, and requestdump files are therefore\nnot removed by this route. Deletion also does not remove long-term memory,\nstored Responses objects, external tool effects, or provider data outside this\ndatabase.\n\nSQLite persistence is local to the configured Hermes home. The pinned code\ndoes not replicate session rows across endpoint instances. Route a given\nsession to the endpoint that owns its database, or provide a deployment-level\nshared-storage design and validate its concurrency behavior separately.\n\nProtect transcript data\n\nThe AIP manifest classifies sessionslist, sessionget, and\nsessionmessages as Internal and containspii: false because they are marked\nread-only. That mechanical classification is weaker than the actual outputs.\n\nLists can contain userid and a message preview. Session rows expose activity\nand cost metadata. Message reads can contain user text, assistant text,\nreasoning, tool arguments, and tool results. Treat all three outputs as\nconfidential and potentially PII-bearing, and redact them from logs and traces.\n\nThe safe provider projection hides full stored system prompts and model\nconfiguration, but it is not a general content-redaction layer. Apply tenant\nauthorization before exposing any session selected by caller-controlled ID.\n\nThe Bearer credential authenticates access to the Hermes API, not ownership of\nan individual session row. The pinned handlers do not compare the AIP principal\nor tenant with a row's userid before list, read, patch, fork, chat, or delete.\nIsolate the endpoint and state.db at the intended tenant boundary, or add a\nseparate row-authorization layer before public exposure.\n\nApply idempotency and retry rules\n\nsessioncreate, sessionpatch, sessionfork, and sessiondelete require\nan AIP idempotency key. The runtime scopes it to the capability, retains it for\n24 hours, and uses ReturnOriginalResult on collision.\n\nWithin that window, reuse of the same scoped key returns the stored result even\nwhen the new Action has different input. This collision mode does not\nrevalidate the retained input hash. Use a fresh key for every changed session,\nbody, or business intent.\n\nsessiondelete also requires tenant-policy approval. That approval lasts 15\nminutes and requires a reason, input snapshot, and policy-decision evidence.\n\nHermes does not enforce Idempotency-Key on these session handlers. The AIP\nruntime is the replay boundary. Bypassing AIP or losing its idempotency store\nremoves that protection.\n\nOnly list, get, and messages are published as retry-supported and safe. For a\ntransport failure or remote 5xx on a mutation, reconcile provider state before\nsubmitting a new Action. For chat, inspect messages and downstream systems, but\ndo not assume that absence of a final assistant row proves that no tool ran.\n\nThe generic connector marks non-read transport failures, deadlines, remote\n429, and remote 5xx as uncertain outcomes. The fork title failure described\nabove is a stricter exception: the provider returns 400 after possible writes,\nwhile generic connector metadata classifies that status as a definitive remote\nrejection. Reconcile it manually.\n\nThat non-retryable 400 result can also be settled under the fork's idempotency\nkey. Reusing the key then returns the stored failure; it does not inspect or\nrepair the partially created child.\n\nSee Approvals and Policy (../../../concepts/approvals-and-policy.md) for the\ndelete approval record and Errors and Retry Decisions (../../../reference/errors.md)\nfor the shared AIP failure model.\n\nHandle provider failures\n\n| Signal | Meaning | Application decision |\n\n| 400 invalidsessionid | A selected create or fork ID is unsafe, too long where checked, or whitespace-only | Correct the ID; inspect state first after fork |\n| 400 invalidtitle | Title is invalid or already used | Choose a valid unique title; inspect state first after fork |\n| 400 unsupportedsessionfield | Patch contains a non-public field | Send only title and endreason |\n| 400 missingmessage or content error | Session chat has no usable text/image or uses an unsupported part | Correct the current turn; do not alter prior history |\n| 401 invalidapikey | Endpoint credential is absent or wrong | Repair endpoint authentication; do not retry unchanged |\n| 403 for X-Hermes-Session-Key | Hermes has no API key configured for memory scoping | Configure authenticated access or omit the memory key |\n| 404 sessionnotfound | The exact path row does not exist | Re-list, resolve any compression tip, and verify endpoint routing |\n| 409 sessionexists | Create or fork child ID already exists | Read the existing row before choosing a new identity |\n| 413 bodytoolarge | JSON body exceeds 10,000,000 bytes | Reduce current input; do not split one mutation into blind retries |\n| 503 sessiondbunavailable | Hermes could not open its shared session database | Restore storage, then reconcile before mutation retry |\n| Connector timeout or transport failure | Completion is unknown for non-read work | Inspect session and external effects before any new Action |\n\nThe pinned session-chat handlers increment the in-flight run counter but do not\ncall the provider's shared concurrency-limit check used by ordinary Chat,\nResponses, and structured runs. Do not infer that the published queue-delay or\navailability hints enforce a session-specific concurrency ceiling.\n\nVerify an integration\n\nBefore relying on this family in an application:\n1. Discover all nine capability IDs for the intended endpoint and verify the\n   pinned connector revision.\n2. Confirm the endpoint credential and tenant routing described in\n   Authentication and Endpoints (../getting-started/authentication-and-endpoints.md).\n3. Create a uniquely named session with an AIP idempotency key, then get it and\n   locate it in a filtered list.\n4. Patch its title, read the result, and verify that unknown fields are rejected.\n5. Submit one harmless chat turn, verify the returned effective session ID, and\n   read the active message sequence.\n6. Fork the session with a unique child ID and title. Verify the parent end\n   state, child parent ID, and copied active transcript.\n7. Exercise delete only with tenant-policy approval and a fresh idempotency key.\n   Verify preserved branches and any retained external transcript artifacts.\n8. Record Action IDs, idempotency keys, endpoint identity, status, and redacted\n   evidence without logging transcript content or credentials.\n\nFor general model requests, compare\nChat and Responses (chat-and-responses.md). Return to the\nHermes capability index (README.md) when another operation family owns the\ntask.\n"
  },
  "integrity": {
    "algorithm": "sha256",
    "sourceDigest": "c4656a8997c18b5c675f633971b48880057168722014d6ae940475fbca1c41bb"
  }
}
