{
  "schemaVersion": "1.0",
  "title": "Manage a persisted Hermes session lifecycle",
  "description": "Use this guide to create one addressable Hermes transcript, add a verified turn, branch it, and then close or delete it deliberately. The procedure keeps transcript identity, AIP execution identity, and optional memory scope separate throug",
  "canonical": "https://getaip.org/docs/connectors/hermes-agent/guides/session-lifecycle",
  "route": "/docs/connectors/hermes-agent/guides/session-lifecycle",
  "source": "docs/connectors/hermes-agent/guides/session-lifecycle.md",
  "protocol": "Agent Interoperability Protocol",
  "protocolVersion": "1.0",
  "section": "Connectors",
  "documentType": "Connector",
  "language": "en",
  "downloads": {
    "md": "/docs/download/connectors/hermes-agent/guides/session-lifecycle.md",
    "txt": "/docs/download/connectors/hermes-agent/guides/session-lifecycle.txt",
    "json": "/docs/download/connectors/hermes-agent/guides/session-lifecycle.json",
    "pdf": "/docs/download/connectors/hermes-agent/guides/session-lifecycle.pdf"
  },
  "content": {
    "format": "text/markdown",
    "markdown": "---\ntitle: Manage a persisted Hermes session lifecycle\ndescription: >-\n  Create, verify, continue, branch, close, and delete one persisted Hermes\n  transcript without losing its identity or replaying uncertain work\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>: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---\n\n# Manage a persisted Hermes session lifecycle\n\nUse this guide to create one addressable Hermes transcript, add a verified\nturn, branch it, and then close or delete it deliberately. The procedure keeps\ntranscript identity, AIP execution identity, and optional memory scope\nseparate throughout the lifecycle.\n\nSession chat can call tools and change external systems. A timeout, failed\nresponse, or missing assistant message does not prove that no effect occurred.\nReconcile the transcript and downstream systems before submitting new work.\n\nThis guide uses synchronous session chat. Use the streaming guide when the\napplication needs incremental deltas or cancellation.\n\n## Keep the identities separate\n\nPersist these values in application state before each consequential call.\n\n| Identity | Owner | Purpose |\n|---|---|---|\n| Endpoint-qualified capability ID | AIP deployment | Selects the admitted Hermes endpoint and operation |\n| Hermes `session_id` | Endpoint-local SessionDB | Addresses one persisted transcript row |\n| AIP Action ID | AIP runtime | Identifies one capability invocation and its lifecycle |\n| AIP idempotency key | AIP runtime | Fences create, patch, fork, or delete within the capability scope |\n| Optional `session_key` | Hermes long-term memory | Selects a memory scope; it does not address the transcript |\n\nA Hermes transcript is not an AIP Session. An AIP Session carries\nprotocol-level interaction context, while the path `session_id` selects a row\nand messages in the selected Hermes endpoint's `state.db`.\n\nDo not infer tenant ownership from a `session_id`. The endpoint Bearer\ncredential authenticates the Hermes API request, but the pinned provider does\nnot compare the AIP principal or tenant with a row owner.\n\n## Before you start\n\nConfirm all of these conditions:\n\n- the intended Hermes endpoint is admitted for the current tenant;\n- the requester can discover and invoke the required session capabilities;\n- the endpoint and its `state.db` are isolated at the intended tenant boundary;\n- the application can retain Action IDs, keys, session IDs, and redacted\n  results durably;\n- the operator understands that model turns can create external effects;\n- a requester-different, tenant-authorized reviewer is available before\n  `session_delete`.\n\nThe examples use `primary` as the endpoint ID. Replace it with the exact\nnormalized ID returned by capability discovery.\n\n## 1. Discover the session capabilities\n\nQuery the endpoint-qualified family before allocating a transcript:\n\n```sh\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIP_REQUESTER_TOKEN_FILE\" \\\n  capability list \"$AIP_URL\" \\\n  --text 'cap:hermes_agent:primary:session' \\\n  --limit 20\n```\n\nVerify that the returned IDs use one endpoint and include the operations needed\nfor the planned lifecycle. This guide uses:\n\n```sh\nexport HERMES_SESSIONS_LIST='cap:hermes_agent:primary:sessions_list'\nexport HERMES_SESSION_CREATE='cap:hermes_agent:primary:session_create'\nexport HERMES_SESSION_GET='cap:hermes_agent:primary:session_get'\nexport HERMES_SESSION_PATCH='cap:hermes_agent:primary:session_patch'\nexport HERMES_SESSION_MESSAGES='cap:hermes_agent:primary:session_messages'\nexport HERMES_SESSION_FORK='cap:hermes_agent:primary:session_fork'\nexport HERMES_SESSION_CHAT='cap:hermes_agent:primary:session_chat'\nexport HERMES_SESSION_DELETE='cap:hermes_agent:primary:session_delete'\n```\n\nDo not combine capabilities from endpoints that have different local session\ndatabases.\n\n## 2. Allocate the transcript and Action identities\n\nChoose an application-owned transcript ID and allocate the create Action ID and\nrequired idempotency key before submission:\n\n```sh\nexport HERMES_SESSION_ID='support-case-example-2030'\nexport CREATE_ACTION_ID='act_0123456789abcdef0123456789abcdef'\nexport CREATE_KEY='session-create-support-case-example-2030'\n```\n\nA create ID must be non-empty after trimming, no longer than 256 characters,\nand free of control characters, `..`, slash, backslash, or a Windows drive\nprefix. An explicit, stable ID is easier to reconcile than a provider-generated\none.\n\nUse a new idempotency key whenever the endpoint, session ID, title, model,\nsystem prompt, or business intent changes. Within its retention window, an\nexact key replay returns the original AIP result; it does not compare a changed\ninput with the retained request.\n\n## 3. Create the transcript\n\nCreate `session-create.json`:\n\n```json\n{\n  \"body\": {\n    \"id\": \"support-case-example-2030\",\n    \"title\": \"Support case example 2030\",\n    \"model\": \"hermes-agent\",\n    \"system_prompt\": \"Keep the transcript factual and concise.\"\n  }\n}\n```\n\nSubmit the mutation with the identities allocated above:\n\n```sh\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIP_REQUESTER_TOKEN_FILE\" \\\n  action call \"$AIP_URL\" \\\n  \"$HERMES_SESSION_CREATE\" \\\n  --action-id \"$CREATE_ACTION_ID\" \\\n  --idempotency-key \"$CREATE_KEY\" \\\n  --mode sync \\\n  --input @session-create.json\n```\n\nRetain the returned `output.session.id`. A successful create reports a safe\nprojection; it does not return the stored system prompt or model configuration.\nThe pinned session-chat handler does not restore that stored prompt as the\nturn's instruction. Supply and govern any ephemeral `system_message` on the\nspecific chat Action.\n\nIf the call ends with a transport error, timeout, remote `5xx`, or another\nuncertain result, do not create a replacement ID. Query the original Action,\nthen get and list the intended transcript before deciding whether any new\nmutation is safe.\n\n## 4. Verify the exact row and list projection\n\nRead the exact row:\n\n```sh\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIP_REQUESTER_TOKEN_FILE\" \\\n  action call \"$AIP_URL\" \\\n  \"$HERMES_SESSION_GET\" \\\n  --mode sync \\\n  --input '{\"session_id\":\"support-case-example-2030\"}'\n```\n\nVerify the returned ID, `source: \"api_server\"`, title, model, open state, and\nthe expected `has_system_prompt` marker. Then locate it in the endpoint list:\n\n```sh\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIP_REQUESTER_TOKEN_FILE\" \\\n  action call \"$AIP_URL\" \\\n  \"$HERMES_SESSIONS_LIST\" \\\n  --mode sync \\\n  --input '{\"source\":\"api_server\",\"limit\":50,\"offset\":0}'\n```\n\nThe list uses offset pagination over changing last-activity order. De-duplicate\nby ID and do not treat one page as a snapshot or ownership inventory.\n\n## 5. Submit one bounded session turn\n\nAllocate a new AIP Action ID for the turn:\n\n```sh\nexport CHAT_ACTION_ID='act_abcdef0123456789abcdef0123456789'\n```\n\nCreate `session-chat.json`:\n\n```json\n{\n  \"session_id\": \"support-case-example-2030\",\n  \"message\": \"Summarize the verified facts already present in this transcript.\",\n  \"system_message\": \"Return at most three factual bullets. Do not call tools.\"\n}\n```\n\nInvoke synchronous session chat:\n\n```sh\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIP_REQUESTER_TOKEN_FILE\" \\\n  action call \"$AIP_URL\" \\\n  \"$HERMES_SESSION_CHAT\" \\\n  --action-id \"$CHAT_ACTION_ID\" \\\n  --mode sync \\\n  --input @session-chat.json\n```\n\nThe contract does not require an idempotency key for chat and declares replay\nunsafe. Do not resend this turn automatically after an uncertain result. Read\nthe Action status, transcript, and any systems the model could have changed.\n\nThe provider may fail to load prior history and continue with empty history.\nIt can also return a successful model result after a transcript persistence\nfailure. Treat the chat response as model output, not proof of history loading\nor durable append.\n\n## 6. Verify the effective transcript and messages\n\nFirst, retain `output.session_id` from the completed chat result. Context\ncompression can move the turn to a continuation transcript and return a\ndifferent effective ID.\n\nRead messages using the last verified ID:\n\n```sh\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIP_REQUESTER_TOKEN_FILE\" \\\n  action call \"$AIP_URL\" \\\n  \"$HERMES_SESSION_MESSAGES\" \\\n  --mode sync \\\n  --input '{\"session_id\":\"support-case-example-2030\"}'\n```\n\nAlways inspect the top-level `output.session_id` in this result. The messages\nroute resolves a compression continuation before it reads active messages.\nUpdate the application's current transcript ID when that value differs.\n\nVerify, in order:\n\n1. the user turn is present under the effective transcript;\n2. the assistant row and finish metadata are present when expected;\n3. the returned sequence belongs to the intended endpoint and case;\n4. any claimed tool or external effect is confirmed by its authoritative\n   system rather than by assistant text.\n\nThe route returns the active messages of the resolved row. It does not merge\nevery compression ancestor into one response.\n\n## 7. Patch client-visible metadata\n\nAllocate a fresh Action ID and required key, then create `session-patch.json`:\n\n```json\n{\n  \"session_id\": \"support-case-example-2030\",\n  \"body\": {\n    \"title\": \"Support case example 2030 — reviewed\"\n  }\n}\n```\n\n```sh\nexport PATCH_ACTION_ID='act_11111111111111111111111111111111'\nexport PATCH_KEY='session-patch-support-case-example-2030-reviewed'\n\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIP_REQUESTER_TOKEN_FILE\" \\\n  action call \"$AIP_URL\" \\\n  \"$HERMES_SESSION_PATCH\" \\\n  --action-id \"$PATCH_ACTION_ID\" \\\n  --idempotency-key \"$PATCH_KEY\" \\\n  --mode sync \\\n  --input @session-patch.json\n```\n\nPatch supports only `title` and `end_reason`. Unknown fields fail instead of\nchanging hidden provider state. Read the exact row after the mutation.\n\n## 8. Fork a verified transcript\n\nFork only from the current, verified transcript ID. Allocate a new child ID,\nAction ID, and idempotency key:\n\n```sh\nexport HERMES_CHILD_ID='support-case-example-2030-alternative'\nexport FORK_ACTION_ID='act_22222222222222222222222222222222'\nexport FORK_KEY='session-fork-support-case-example-2030-alternative'\n```\n\nCreate `session-fork.json`:\n\n```json\n{\n  \"session_id\": \"support-case-example-2030\",\n  \"body\": {\n    \"id\": \"support-case-example-2030-alternative\",\n    \"title\": \"Support case example 2030 — alternative\"\n  }\n}\n```\n\n```sh\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIP_REQUESTER_TOKEN_FILE\" \\\n  action call \"$AIP_URL\" \\\n  \"$HERMES_SESSION_FORK\" \\\n  --action-id \"$FORK_ACTION_ID\" \\\n  --idempotency-key \"$FORK_KEY\" \\\n  --mode sync \\\n  --input @session-fork.json\n```\n\nThe provider ends an open source as `branched`, creates the child, copies the\nsource's active messages, and only then validates the child title. Fork is not\natomic. A provider `400 invalid_title` can therefore follow durable changes.\n\nAfter every fork result, including an apparent rejection:\n\n1. get the source and intended child IDs separately;\n2. inspect the source `end_reason` and child `parent_session_id`;\n3. read the child's active messages;\n4. retain both records before deciding whether a new mutation is needed.\n\nDo not blindly retry a failed fork. A replayed key can return the stored error\nwithout discovering or repairing a child that already exists.\n\n## 9. Continue from the verified child\n\nUse the child only after its row, lineage, and copied messages are confirmed.\nSubmit a new chat Action whose `session_id` is the child ID, then repeat the\nmessage verification from step 6.\n\nThe parent and child now have separate transcript identities. An optional\n`session_key` can still point both calls at one long-term-memory scope, so do\nnot use it when the branches must also be isolated at that layer.\n\n## 10. Choose close or delete\n\nUse metadata closure when the row and transcript must remain available:\n\n```json\n{\n  \"session_id\": \"support-case-example-2030-alternative\",\n  \"body\": {\n    \"end_reason\": \"client_closed\"\n  }\n}\n```\n\nSubmit it through `session_patch` with a new Action ID and key. The first\nnon-empty close reason wins. Closure is metadata: the pinned chat handler does\nnot reject an ended row, so the application must enforce its own no-further-\nchat rule.\n\nUse `session_delete` only when the selected row and its active messages should\nbe removed from SessionDB. Delete is high risk and first returns an AIP\n`pending_approval`. Review the exact endpoint, session ID, input snapshot,\npolicy hash, and retained lineage before an authorized reviewer decides it.\n\nCreate `session-delete.json` with the last verified target:\n\n```json\n{\n  \"session_id\": \"support-case-example-2030-alternative\"\n}\n```\n\nAllocate a new Action ID and required key, then submit the delete once:\n\n```sh\nexport DELETE_ACTION_ID='act_33333333333333333333333333333333'\nexport DELETE_KEY='session-delete-support-case-example-2030-alternative'\n\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIP_REQUESTER_TOKEN_FILE\" \\\n  action call \"$AIP_URL\" \\\n  \"$HERMES_SESSION_DELETE\" \\\n  --action-id \"$DELETE_ACTION_ID\" \\\n  --idempotency-key \"$DELETE_KEY\" \\\n  --mode sync \\\n  --input @session-delete.json\n```\n\nDecide the returned approval record instead of submitting delete again. An\napproved decision resumes the same queued Action. Query that Action ID and then\nread the target and known children to verify the final state.\n\nDeletion does not compensate model, tool, message, code, or network effects.\nIt also does not delete long-term memory or separate transcript artifacts. The\nprovider preserves branch and compression children by clearing their parent\nlink, while delegate descendants are cascade-deleted.\n\n## Recover by lifecycle phase\n\n| Observation | Safe next action |\n|---|---|\n| Create result lost | Query the original Action, then get and list the intended ID |\n| Chat result lost | Query the Action, read messages, and reconcile downstream effects |\n| Returned session ID changed | Adopt the verified effective ID before the next turn or fork |\n| `404 session_not_found` | Verify endpoint routing, then list and resolve a continuation |\n| Fork returned `400` or delivery failed | Inspect both source and intended child; do not replay blindly |\n| Patch result uncertain | Get the exact row and compare title or close metadata |\n| Delete waits for approval | Review and decide the existing approval; do not resubmit delete |\n| Delete result uncertain | Query the Action and get the row before any replacement mutation |\n| `503 session_db_unavailable` | Restore endpoint storage, then reconcile every mutation identity |\n\nProvider SQLite is local to the configured Hermes home. A row missing from one\nendpoint is not proof that it never existed on another endpoint or before local\nstorage loss.\n\n## Verify the client workflow\n\nBefore accepting the integration, use controlled data to verify that it:\n\n1. stores endpoint ID, transcript ID, Action ID, and idempotency key separately;\n2. creates a safe explicit transcript ID with a fresh required key;\n3. confirms the exact row and list projection after create;\n4. never treats `session_key` as transcript identity or authorization;\n5. does not replay a chat turn after an uncertain outcome;\n6. reads active messages after every consequential turn;\n7. adopts the returned effective ID after compression rotation;\n8. allows only title and close-reason metadata through patch;\n9. reconciles both source and child after every fork result;\n10. prevents application chat after metadata closure when policy requires it;\n11. sends delete through the existing AIP approval record;\n12. verifies preserved children and external effects after delete;\n13. redacts transcript, reasoning, tool, and credential data from evidence;\n14. records source revision and endpoint identity with the result.\n\nThis checklist is code-aligned integration evidence. It is not live endpoint,\nstorage-durability, model, tool, or production qualification.\n\n## Related documentation\n\n- [Persistent session capabilities](../capabilities/sessions.md)\n- [Stream chat and responses](chat-and-response-streaming.md)\n- [Authentication and endpoints](../getting-started/authentication-and-endpoints.md)\n- [Actions and sessions](../../../concepts/actions-and-sessions.md)\n- [Approvals and policy](../../../concepts/approvals-and-policy.md)\n- [Errors and recovery](../../../reference/errors.md)\n",
    "text": "Manage a persisted Hermes session lifecycle\n\nUse this guide to create one addressable Hermes transcript, add a verified\nturn, branch it, and then close or delete it deliberately. The procedure keeps\ntranscript identity, AIP execution identity, and optional memory scope\nseparate throughout the lifecycle.\n\nSession chat can call tools and change external systems. A timeout, failed\nresponse, or missing assistant message does not prove that no effect occurred.\nReconcile the transcript and downstream systems before submitting new work.\n\nThis guide uses synchronous session chat. Use the streaming guide when the\napplication needs incremental deltas or cancellation.\n\nKeep the identities separate\n\nPersist these values in application state before each consequential call.\n\n| Identity | Owner | Purpose |\n\n| Endpoint-qualified capability ID | AIP deployment | Selects the admitted Hermes endpoint and operation |\n| Hermes sessionid | Endpoint-local SessionDB | Addresses one persisted transcript row |\n| AIP Action ID | AIP runtime | Identifies one capability invocation and its lifecycle |\n| AIP idempotency key | AIP runtime | Fences create, patch, fork, or delete within the capability scope |\n| Optional sessionkey | Hermes long-term memory | Selects a memory scope; it does not address the transcript |\n\nA Hermes transcript is not an AIP Session. An AIP Session carries\nprotocol-level interaction context, while the path sessionid selects a row\nand messages in the selected Hermes endpoint's state.db.\n\nDo not infer tenant ownership from a sessionid. The endpoint Bearer\ncredential authenticates the Hermes API request, but the pinned provider does\nnot compare the AIP principal or tenant with a row owner.\n\nBefore you start\n\nConfirm all of these conditions:\n• the intended Hermes endpoint is admitted for the current tenant;\n• the requester can discover and invoke the required session capabilities;\n• the endpoint and its state.db are isolated at the intended tenant boundary;\n• the application can retain Action IDs, keys, session IDs, and redacted\n  results durably;\n• the operator understands that model turns can create external effects;\n• a requester-different, tenant-authorized reviewer is available before\n  sessiondelete.\n\nThe examples use primary as the endpoint ID. Replace it with the exact\nnormalized ID returned by capability discovery.\n1. Discover the session capabilities\n\nQuery the endpoint-qualified family before allocating a transcript:\n\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIPREQUESTERTOKENFILE\" \\\n  capability list \"$AIPURL\" \\\n  --text 'cap:hermesagent:primary:session' \\\n  --limit 20\n\nVerify that the returned IDs use one endpoint and include the operations needed\nfor the planned lifecycle. This guide uses:\n\nexport HERMESSESSIONSLIST='cap:hermesagent:primary:sessionslist'\nexport HERMESSESSIONCREATE='cap:hermesagent:primary:sessioncreate'\nexport HERMESSESSIONGET='cap:hermesagent:primary:sessionget'\nexport HERMESSESSIONPATCH='cap:hermesagent:primary:sessionpatch'\nexport HERMESSESSIONMESSAGES='cap:hermesagent:primary:sessionmessages'\nexport HERMESSESSIONFORK='cap:hermesagent:primary:sessionfork'\nexport HERMESSESSIONCHAT='cap:hermesagent:primary:sessionchat'\nexport HERMESSESSIONDELETE='cap:hermesagent:primary:sessiondelete'\n\nDo not combine capabilities from endpoints that have different local session\ndatabases.\n2. Allocate the transcript and Action identities\n\nChoose an application-owned transcript ID and allocate the create Action ID and\nrequired idempotency key before submission:\n\nexport HERMESSESSIONID='support-case-example-2030'\nexport CREATEACTIONID='act0123456789abcdef0123456789abcdef'\nexport CREATEKEY='session-create-support-case-example-2030'\n\nA create ID must be non-empty after trimming, no longer than 256 characters,\nand free of control characters, .., slash, backslash, or a Windows drive\nprefix. An explicit, stable ID is easier to reconcile than a provider-generated\none.\n\nUse a new idempotency key whenever the endpoint, session ID, title, model,\nsystem prompt, or business intent changes. Within its retention window, an\nexact key replay returns the original AIP result; it does not compare a changed\ninput with the retained request.\n3. Create the transcript\n\nCreate session-create.json:\n\n{\n  \"body\": {\n    \"id\": \"support-case-example-2030\",\n    \"title\": \"Support case example 2030\",\n    \"model\": \"hermes-agent\",\n    \"systemprompt\": \"Keep the transcript factual and concise.\"\n  }\n}\n\nSubmit the mutation with the identities allocated above:\n\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIPREQUESTERTOKENFILE\" \\\n  action call \"$AIPURL\" \\\n  \"$HERMESSESSIONCREATE\" \\\n  --action-id \"$CREATEACTIONID\" \\\n  --idempotency-key \"$CREATEKEY\" \\\n  --mode sync \\\n  --input @session-create.json\n\nRetain the returned output.session.id. A successful create reports a safe\nprojection; it does not return the stored system prompt or model configuration.\nThe pinned session-chat handler does not restore that stored prompt as the\nturn's instruction. Supply and govern any ephemeral systemmessage on the\nspecific chat Action.\n\nIf the call ends with a transport error, timeout, remote 5xx, or another\nuncertain result, do not create a replacement ID. Query the original Action,\nthen get and list the intended transcript before deciding whether any new\nmutation is safe.\n4. Verify the exact row and list projection\n\nRead the exact row:\n\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIPREQUESTERTOKENFILE\" \\\n  action call \"$AIPURL\" \\\n  \"$HERMESSESSIONGET\" \\\n  --mode sync \\\n  --input '{\"sessionid\":\"support-case-example-2030\"}'\n\nVerify the returned ID, source: \"apiserver\", title, model, open state, and\nthe expected hassystemprompt marker. Then locate it in the endpoint list:\n\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIPREQUESTERTOKENFILE\" \\\n  action call \"$AIPURL\" \\\n  \"$HERMESSESSIONSLIST\" \\\n  --mode sync \\\n  --input '{\"source\":\"apiserver\",\"limit\":50,\"offset\":0}'\n\nThe list uses offset pagination over changing last-activity order. De-duplicate\nby ID and do not treat one page as a snapshot or ownership inventory.\n5. Submit one bounded session turn\n\nAllocate a new AIP Action ID for the turn:\n\nexport CHATACTIONID='actabcdef0123456789abcdef0123456789'\n\nCreate session-chat.json:\n\n{\n  \"sessionid\": \"support-case-example-2030\",\n  \"message\": \"Summarize the verified facts already present in this transcript.\",\n  \"systemmessage\": \"Return at most three factual bullets. Do not call tools.\"\n}\n\nInvoke synchronous session chat:\n\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIPREQUESTERTOKENFILE\" \\\n  action call \"$AIPURL\" \\\n  \"$HERMESSESSIONCHAT\" \\\n  --action-id \"$CHATACTIONID\" \\\n  --mode sync \\\n  --input @session-chat.json\n\nThe contract does not require an idempotency key for chat and declares replay\nunsafe. Do not resend this turn automatically after an uncertain result. Read\nthe Action status, transcript, and any systems the model could have changed.\n\nThe provider may fail to load prior history and continue with empty history.\nIt can also return a successful model result after a transcript persistence\nfailure. Treat the chat response as model output, not proof of history loading\nor durable append.\n6. Verify the effective transcript and messages\n\nFirst, retain output.sessionid from the completed chat result. Context\ncompression can move the turn to a continuation transcript and return a\ndifferent effective ID.\n\nRead messages using the last verified ID:\n\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIPREQUESTERTOKENFILE\" \\\n  action call \"$AIPURL\" \\\n  \"$HERMESSESSIONMESSAGES\" \\\n  --mode sync \\\n  --input '{\"sessionid\":\"support-case-example-2030\"}'\n\nAlways inspect the top-level output.sessionid in this result. The messages\nroute resolves a compression continuation before it reads active messages.\nUpdate the application's current transcript ID when that value differs.\n\nVerify, in order:\n1. the user turn is present under the effective transcript;\n2. the assistant row and finish metadata are present when expected;\n3. the returned sequence belongs to the intended endpoint and case;\n4. any claimed tool or external effect is confirmed by its authoritative\n   system rather than by assistant text.\n\nThe route returns the active messages of the resolved row. It does not merge\nevery compression ancestor into one response.\n7. Patch client-visible metadata\n\nAllocate a fresh Action ID and required key, then create session-patch.json:\n\n{\n  \"sessionid\": \"support-case-example-2030\",\n  \"body\": {\n    \"title\": \"Support case example 2030 — reviewed\"\n  }\n}\n\nexport PATCHACTIONID='act11111111111111111111111111111111'\nexport PATCHKEY='session-patch-support-case-example-2030-reviewed'\n\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIPREQUESTERTOKENFILE\" \\\n  action call \"$AIPURL\" \\\n  \"$HERMESSESSIONPATCH\" \\\n  --action-id \"$PATCHACTIONID\" \\\n  --idempotency-key \"$PATCHKEY\" \\\n  --mode sync \\\n  --input @session-patch.json\n\nPatch supports only title and endreason. Unknown fields fail instead of\nchanging hidden provider state. Read the exact row after the mutation.\n8. Fork a verified transcript\n\nFork only from the current, verified transcript ID. Allocate a new child ID,\nAction ID, and idempotency key:\n\nexport HERMESCHILDID='support-case-example-2030-alternative'\nexport FORKACTIONID='act22222222222222222222222222222222'\nexport FORKKEY='session-fork-support-case-example-2030-alternative'\n\nCreate session-fork.json:\n\n{\n  \"sessionid\": \"support-case-example-2030\",\n  \"body\": {\n    \"id\": \"support-case-example-2030-alternative\",\n    \"title\": \"Support case example 2030 — alternative\"\n  }\n}\n\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIPREQUESTERTOKENFILE\" \\\n  action call \"$AIPURL\" \\\n  \"$HERMESSESSIONFORK\" \\\n  --action-id \"$FORKACTIONID\" \\\n  --idempotency-key \"$FORKKEY\" \\\n  --mode sync \\\n  --input @session-fork.json\n\nThe provider ends an open source as branched, creates the child, copies the\nsource's active messages, and only then validates the child title. Fork is not\natomic. A provider 400 invalidtitle can therefore follow durable changes.\n\nAfter every fork result, including an apparent rejection:\n1. get the source and intended child IDs separately;\n2. inspect the source endreason and child parentsessionid;\n3. read the child's active messages;\n4. retain both records before deciding whether a new mutation is needed.\n\nDo not blindly retry a failed fork. A replayed key can return the stored error\nwithout discovering or repairing a child that already exists.\n9. Continue from the verified child\n\nUse the child only after its row, lineage, and copied messages are confirmed.\nSubmit a new chat Action whose sessionid is the child ID, then repeat the\nmessage verification from step 6.\n\nThe parent and child now have separate transcript identities. An optional\nsessionkey can still point both calls at one long-term-memory scope, so do\nnot use it when the branches must also be isolated at that layer.\n10. Choose close or delete\n\nUse metadata closure when the row and transcript must remain available:\n\n{\n  \"sessionid\": \"support-case-example-2030-alternative\",\n  \"body\": {\n    \"endreason\": \"clientclosed\"\n  }\n}\n\nSubmit it through sessionpatch with a new Action ID and key. The first\nnon-empty close reason wins. Closure is metadata: the pinned chat handler does\nnot reject an ended row, so the application must enforce its own no-further-\nchat rule.\n\nUse sessiondelete only when the selected row and its active messages should\nbe removed from SessionDB. Delete is high risk and first returns an AIP\npendingapproval. Review the exact endpoint, session ID, input snapshot,\npolicy hash, and retained lineage before an authorized reviewer decides it.\n\nCreate session-delete.json with the last verified target:\n\n{\n  \"sessionid\": \"support-case-example-2030-alternative\"\n}\n\nAllocate a new Action ID and required key, then submit the delete once:\n\nexport DELETEACTIONID='act33333333333333333333333333333333'\nexport DELETEKEY='session-delete-support-case-example-2030-alternative'\n\ncargo run --locked -p aipctl -- \\\n  --native-bearer-token-file \"$AIPREQUESTERTOKENFILE\" \\\n  action call \"$AIPURL\" \\\n  \"$HERMESSESSIONDELETE\" \\\n  --action-id \"$DELETEACTIONID\" \\\n  --idempotency-key \"$DELETEKEY\" \\\n  --mode sync \\\n  --input @session-delete.json\n\nDecide the returned approval record instead of submitting delete again. An\napproved decision resumes the same queued Action. Query that Action ID and then\nread the target and known children to verify the final state.\n\nDeletion does not compensate model, tool, message, code, or network effects.\nIt also does not delete long-term memory or separate transcript artifacts. The\nprovider preserves branch and compression children by clearing their parent\nlink, while delegate descendants are cascade-deleted.\n\nRecover by lifecycle phase\n\n| Observation | Safe next action |\n\n| Create result lost | Query the original Action, then get and list the intended ID |\n| Chat result lost | Query the Action, read messages, and reconcile downstream effects |\n| Returned session ID changed | Adopt the verified effective ID before the next turn or fork |\n| 404 sessionnotfound | Verify endpoint routing, then list and resolve a continuation |\n| Fork returned 400 or delivery failed | Inspect both source and intended child; do not replay blindly |\n| Patch result uncertain | Get the exact row and compare title or close metadata |\n| Delete waits for approval | Review and decide the existing approval; do not resubmit delete |\n| Delete result uncertain | Query the Action and get the row before any replacement mutation |\n| 503 sessiondbunavailable | Restore endpoint storage, then reconcile every mutation identity |\n\nProvider SQLite is local to the configured Hermes home. A row missing from one\nendpoint is not proof that it never existed on another endpoint or before local\nstorage loss.\n\nVerify the client workflow\n\nBefore accepting the integration, use controlled data to verify that it:\n1. stores endpoint ID, transcript ID, Action ID, and idempotency key separately;\n2. creates a safe explicit transcript ID with a fresh required key;\n3. confirms the exact row and list projection after create;\n4. never treats sessionkey as transcript identity or authorization;\n5. does not replay a chat turn after an uncertain outcome;\n6. reads active messages after every consequential turn;\n7. adopts the returned effective ID after compression rotation;\n8. allows only title and close-reason metadata through patch;\n9. reconciles both source and child after every fork result;\n10. prevents application chat after metadata closure when policy requires it;\n11. sends delete through the existing AIP approval record;\n12. verifies preserved children and external effects after delete;\n13. redacts transcript, reasoning, tool, and credential data from evidence;\n14. records source revision and endpoint identity with the result.\n\nThis checklist is code-aligned integration evidence. It is not live endpoint,\nstorage-durability, model, tool, or production qualification.\n\nRelated documentation\n• Persistent session capabilities (../capabilities/sessions.md)\n• Stream chat and responses (chat-and-response-streaming.md)\n• Authentication and endpoints (../getting-started/authentication-and-endpoints.md)\n• Actions and sessions (../../../concepts/actions-and-sessions.md)\n• Approvals and policy (../../../concepts/approvals-and-policy.md)\n• Errors and recovery (../../../reference/errors.md)\n"
  },
  "integrity": {
    "algorithm": "sha256",
    "sourceDigest": "df1184eb73911d6865fc8a47efbf9d8a293f76e79263b8ec172b51856e59fb23"
  }
}
