Files
hope-voice-api/docs/hermes-installed-contracts.md

119 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Exact installed Hermes contract research
Read-only public-source and image-label research, 2026-10-02. No runtime secrets read, no production mutations, no application code changes. Existing `.research-hermes` deliberately not trusted.
## Verified pins
Repository `hermes-webui.Dockerfile:1-10` pins WebUI **62e4d7b9e2560f9d49c0c29c2dc1414fbd4e2d12** and base OCI index `nousresearch/hermes-agent@sha256:d4da4a40cd7a28aba983775d9fd31d94cbf153eeb0cb9e844d6d0f612b7c24db`. Public Docker Hub index resolves amd64 manifest `sha256:d566d4a435767265c2a0172030b778fbdaa1a9ab19442c75ec5aad1df32dec54`; public config label `org.opencontainers.image.revision` is **749220ef0007f8d87bd1531f1c24b0fe93816385**. Local Docker daemon unavailable, so no live container inspect performed. Repo qualification records installed pins and authenticated gateway runs; browser approval/cancellation has not been independently qualified.
Exact public checkouts:
- `/tmp/hope-contract-research-agent-749220ef`
- `/tmp/hope-contract-research-webui-62e4d7b`
Pinned references:
- [Gateway API](https://github.com/NousResearch/hermes-agent/blob/749220ef0007f8d87bd1531f1c24b0fe93816385/gateway/platforms/api_server.py)
- [Runs](https://github.com/NousResearch/hermes-agent/blob/749220ef0007f8d87bd1531f1c24b0fe93816385/gateway/platforms/api_server_runs.py)
- [WebUI routes](https://github.com/nesquena/hermes-webui/blob/62e4d7b9e2560f9d49c0c29c2dc1414fbd4e2d12/api/routes.py)
## Core implementation recommendation
Use gateway native persisted session APIs, not WebUI-local sidecars, for unified text/voice chat. Hope backend resolves signed-in account private gateway and injects its Bearer key. Never expose credentials or accept client-selected account/gateway URL. Gateway and WebUI both have `/api/sessions` but entirely different representations/authentication.
Provisioner lines335-346 establishes account `/workspace`, API loopback, multiplex false, max_concurrent_runs1, progress enabled, terminal/file/web/browser/memory/skills/delegation toolsets. Voice room must not become conversation identity.
## Gateway session API
All require `Authorization: Bearer <private-key>`. [Routes lines1603-1650](https://github.com/NousResearch/hermes-agent/blob/749220ef0007f8d87bd1531f1c24b0fe93816385/gateway/platforms/api_server.py#L1603-L1650). [Handlers2904-3159](https://github.com/NousResearch/hermes-agent/blob/749220ef0007f8d87bd1531f1c24b0fe93816385/gateway/platforms/api_server.py#L2904-L3159).
- GET `/api/sessions?limit=50&offset=0&source=...&include_children=false&title=...&include_hidden=false` -> `{object:"list",data:Session[],limit,offset,has_more}`. Limit max200. Title is exact-match; hidden inclusion honored only with title. Pins can be backfilled past limit.
- POST `/api/sessions` JSON `{id?:string,session_id?:string,title?:string,source?:string,system_prompt?:string,model?:string,provider?:string,model_options?:object,require_model_lock?:boolean}` ->201 `{object:"hermes.session",session:Session}`. ID defaults `api_<unix>_<uuid8>`, max256/no unsafe path/CR/LF/NUL. Existing409 `session_exists`.
- GET `/api/sessions/{id}` -> `{object:"hermes.session",session:Session}`; missing404.
- PATCH `/api/sessions/{id}` only `{title?,end_reason?,pinned?:boolean,archived?:boolean,hidden?:boolean,unread?:boolean}` -> same wrapper. Unknown fields400. Pin clears hidden.
- DELETE `/api/sessions/{id}` -> `{object:"hermes.session.deleted",id,deleted:boolean}`.
- GET `/api/sessions/{id}/messages?limit=...&offset=...&order=latest|oldest` -> `{object:"list",session_id:<resolved-live-tip>,data:Message[],pagination:{limit,offset,order,returned}}`. Limit max500. Without limit defaults latest500; explicit limit without order defaults oldest. Nonnegative integer validation.
- POST `/api/sessions/{id}/fork` `{id?,session_id?,title?}` ->201 session wrapper; copies messages to branch and ends parent with branched. NOT resume.
- Also POST `/api/sessions/{id}/chat`, `/chat/stream`, `/model` exist, but unified run coordinator should use runs consistently.
Session client projection fields (only when present): `id,source,user_id,model,title,started_at,ended_at,end_reason,message_count,tool_call_count,input_tokens,output_tokens,cache_read_tokens,cache_write_tokens,reasoning_tokens,estimated_cost_usd,actual_cost_usd,api_call_count,parent_session_id,last_active,preview,_lineage_root_id,pinned,archived,hidden`; plus always `has_system_prompt:boolean,has_model_config:boolean`. No full prompt/config. Message projection fields when present: `id,session_id,role,content,tool_call_id,tool_calls,tool_name,timestamp,token_count,finish_reason,reasoning,reasoning_content,display_kind`. [Projection2840-2866](https://github.com/NousResearch/hermes-agent/blob/749220ef0007f8d87bd1531f1c24b0fe93816385/gateway/platforms/api_server.py#L2840-L2866).
### Resume identity
Runs identity precedence **body.session_id > previous_response_id stored session > declared X-Hermes-Session-Key > fresh run_id**. Runs does NOT select history from `X-Hermes-Session-Id` alone. Explicit/chained session never rebound to header.
History precedence explicit nonempty conversation_history > response snapshot > all-but-last input messages > selected-session SessionDB. For native resume send only current input + session_id, no browser-replayed history. Old session IDs resolve compression/continuation tip, excluding branch/delegate/reset/tool children. History response returns resolved ID; preserve logical root separately if stable UI identity needed. GET session metadata itself does not resolve. [Resolver1213-1252](https://github.com/NousResearch/hermes-agent/blob/749220ef0007f8d87bd1531f1c24b0fe93816385/hermes_state_messages.py#L1213-L1252), runs638-661.
X-Hermes-Session-Key is optional conversation/memory affinity, not account auth, <=256/no CR/LF/NUL, requires configured API auth. Use stable conversation scope, not ephemeral voice room. WebUI uses `webui:<session-id>` and explicitly supplies history, unlike recommended native resume.
## Runs: exact request/response
POST `/v1/runs` JSON `{input:string|[{role:"user",content:string|multimodal-array}],session_id?:string,model?:string,provider?:string,model_options?:object,instructions?:string,conversation_history?:[{role,content}],previous_response_id?:string}`. Nonempty current input required. Last input array element content is current message, earlier elements become history.
Send Bearer key, Content-Type JSON, **Idempotency-Key** stable unique per admission (1–255 visible ASCII), optional X-Hermes-Session-Key. Returns202 `{run_id:"run_<uuidhex>",status:"started",replayed:false}`. Exact retry body/session-key/idempotency key returns original202 `{run_id,status:<current>,replayed:true}` plus `Idempotency-Replayed:true`; different fingerprint409 `idempotency_key_conflict`. Persist exact request before admission. **Durable status persistence is tied to idempotent records; always use idempotency.** [Admission567-703](https://github.com/NousResearch/hermes-agent/blob/749220ef0007f8d87bd1531f1c24b0fe93816385/gateway/platforms/api_server_runs.py#L567-L703), response413-430.
GET `/v1/runs/{id}` -> `{object:"hermes.run",run_id,status,created_at,updated_at,session_id?,model?,output?,usage?,runtime?,error?,approval?,last_event?,shutdown_requested_at?,...}`. Status queued/running/waiting_for_approval/stopping/completed/failed/cancelled/interrupted. Terminal output is **STRING**, not message array. Usage includes input_tokens/output_tokens/total_tokens/cache_read_tokens/cache_write_tokens. Poll output is answer fallback; native messages is transcript fallback.
GET `/v1/runs/{id}/events` SSE payload envelope `{event,run_id,timestamp,...}`. Names:
- message.delta `{delta}`
- message.interim `{text,already_streamed}` (avoid double render)
- tool.started `{tool,preview}`
- tool.completed `{tool,duration,error,...}` (bounded redacted preview can occur)
- reasoning.available `{text}` (never speak as answer)
- approval.request copied approval data, redacted command, choices + envelope; status approval holds same event
- approval.responded `{choice,request_id?,resolved}`
- run.completed `{output:string,usage,runtime,...terminal-fields}`
- run.failed/run.cancelled/run.interrupted terminal fields/error
**Events consume one asyncio.Queue, not broadcast or durable replay. No Last-Event-ID cursor contract. One upstream consumer per run; Hope coordinator broadcasts/replays to clients. Disconnect drops transport; use poll/history recovery, not duplicated execution. Gateway restart interrupts run; idempotency prevents duplicate admission, does not resurrect execution.** [Events1006-1047](https://github.com/NousResearch/hermes-agent/blob/749220ef0007f8d87bd1531f1c24b0fe93816385/gateway/platforms/api_server_runs.py#L1006-L1047), completion862-940.
POST `/v1/runs/{id}/stop` no body required -> `{run_id,status:"stopping"}`; already terminal -> existing status. Poll terminal. Missing404; notactive409. Stopping is not completion. Source1147-1168.
POST `/v1/runs/{id}/approval` `{choice:"once"|"session"|"always"|"deny",request_id?:string,all?:boolean,resolve_all?:boolean}` -> `{object:"hermes.run.approval_response",run_id,choice,request_id?,resolved:int}`. Prefer exact request_id and once/deny. Request ID nonempty<=256. Aliases approve/approved/allow normalize once. No active/pending409; invalid400. Approval event is exactly **approval.request**, not approval.required; fields copied from provider approval payload, not fixed required reason/description schema. Room grants constrain once/deny/exact request_id/no all. Source1062-1110; API112-125.
POST `/steer` `{input:string}` (message/text aliases) -> `{object:"hermes.run.steer",run_id,accepted:true}`; running only409 otherwise.
### Voice instructions vs temporary privacy
Runs instructions maps to ephemeral_system_prompt, **appended** to cached native system prompt at API-call time, not persisted and not replacement. Use per-voice-turn concise speech instructions while preserving native tool/system/memory. [Composition1279-1286](https://github.com/NousResearch/hermes-agent/blob/749220ef0007f8d87bd1531f1c24b0fe93816385/agent/turn_context.py#L1279-L1286), runs682.
**No normal runs no-persistence/no-memory/per-request toolset option.** _create_agent always passes SessionDB, configured toolsets and memory manager. A fresh session/run ID is still persisted. Deleting transcript later cannot undo memory/files/tool side effects. Instructions "ignore memory" are advisory, NOT privacy enforcement. Do not claim private temporary chat support on this pin without separately isolated/disabled persistence and side-effect controls. [Agent construction2211-2283](https://github.com/NousResearch/hermes-agent/blob/749220ef0007f8d87bd1531f1c24b0fe93816385/gateway/platforms/api_server.py#L2211-L2283).
## Gateway jobs, capabilities, tools
GET `/v1/capabilities` inspect runtime availability, including endpoints/feature flags. Static capabilities explicitly admin_config_rw:false, memory_write_api:false. GET `/v1/toolsets` lists toolset enabled/configured/key/tool state; GET `/v1/skills` installed skill manifest. Not a connector CRUD/admin API. No invented connector API; configured provider/tool availability is source/runtime dependent.
Gateway cron is `/api/jobs`, not WebUI `/api/crons`. Authenticated; missing cron module501. ID12 lowercase hex; name max200, prompt max5000/scanned.
- GET `/api/jobs?include_disabled=true` -> `{jobs:Job[]}`, defaults excludes disabled.
- POST `/api/jobs` `{name(required),schedule(required,string),prompt?,deliver?:"local",skills?:[],repeat?:positive-int,paused?,paused_reason?}` ->200 `{job}`; scheduler registration error424.
- GET `/api/jobs/{id}` -> `{job}`; PATCH whitelist name,schedule,prompt,deliver,skills,skill,repeat,enabled -> `{job}`; DELETE -> `{ok:true}`.
- POST `/pause`, `/resume` -> `{job}`.
- POST `/run` optional `{prompt}` transient context -> `{job}`; schedules trigger, not synchronous answer.
Job raw fields: id,name,prompt,skills,skill,model,provider,base_url,script,no_agent,monitor_script,monitor_url,monitor_state,context_from,schedule object,schedule_display,repeat:{times:int|null,completed:int},enabled,state,paused_at,paused_reason,created_at,next_run_at,last_run_at,last_status,last_error,last_delivery_error,last_delivery_unverified,failure_streak,deliver,origin,enabled_toolsets,workdir. Project safe client fields rather than exposing arbitrary future raw config. [Jobs3619-3797](https://github.com/NousResearch/hermes-agent/blob/749220ef0007f8d87bd1531f1c24b0fe93816385/gateway/platforms/api_server.py#L3619-L3797), [record1809-1852](https://github.com/NousResearch/hermes-agent/blob/749220ef0007f8d87bd1531f1c24b0fe93816385/cron/jobs.py#L1809-L1852).
## Files/memory: exact boundary
Gateway has **NO direct workspace file browser or memory REST**. Native tools can operate inside runs, but do not fabricate `/api/files` or `/api/memory`. Browser-control `/v1/artifacts/upload|download/{id}` is optional gated one-shot scope-bound receipt/download, NOT general upload; provisioner doesn't enable browser extension control. Upload raw MIME bytes/X-Artifact-Filename ->201 provenance receipt; download second read404. API2710-2782.
WebUI auxiliary API uses distinct password/cookie auth and WebUI-local session/workspace state. Gateway-only IDs do not automatically establish WebUI-local sessions. Intentional fixed-root account adapter needed; no arbitrary host-path proxy.
### WebUI memory
GET `/api/memory` -> `{memory,user,soul,project_context,memory_path,user_path,soul_path,project_context_path,project_context_name,project_context_workspace,memory_mtime,user_mtime,soul_mtime,project_context_mtime,project_context_shadowed,external_notes_enabled}`. Redacted text. Actual home files `memories/MEMORY.md`, `memories/USER.md`, `SOUL.md`. For provisioned accounts HERMES_HOME=/opt/data. Respects memory_enabled/user_profile_enabled.
POST `/api/memory/write` `{section:"memory"|"user"|"soul",content:string}` -> `{ok:true,section,path}`. Full replacement/no conflict version; disabled403; refuses symlink. A safe Hope adapter must resolve account home internally and use fixed section allowlist, not client path. Source routes23359-23418 and30271-30334.
### WebUI files/upload
- GET `/api/file?session_id=<WebUI-ID>&path=<relative>` -> ordinary text `{path,content,size,lines}`; separate Office conversion schema. Existing WebUI session workspace confinement. routes22038-22055; workspace.py1866-1887.
- GET `/api/file/raw?session_id&path` -> bytes/MIME/disposition. routes21998-22036.
- POST `/api/file/save` `{session_id,path,content}` -> `{ok:true,path,size}`, existing target only; symlink refusal, Office separate office-save. routes27513-27541.
- POST `/api/file/delete` `{session_id,path,recursive?}` -> `{ok:true,path}`; directories require recursive. routes27481-27510.
- POST `/api/upload` multipart `session_id,file` -> `{filename,path:absolute-private-container-path,size,mime,is_image}`. Writes session attachment dir, not workspace; sanitized/no overwrite/symlink. [Upload294-340](https://github.com/nesquena/hermes-webui/blob/62e4d7b9e2560f9d49c0c29c2dc1414fbd4e2d12/api/upload.py#L294-L340).
- `/api/workspace/upload` and `/api/upload/extract` separate workspace/archive flows. Do not treat attachment absolute paths as browser URLs.
WebUI cron GET `/api/crons`, history/run/recent/status/output; POST create/update/delete/run/pause/resume. Create `{prompt,schedule,name?,deliver?,skills?,model?,provider?,profile?,toast_notifications?,badge_notifications?}` -> `{ok:true,job}`. UI enriches local Hermes cron data/profile state; gateway jobs preferred for basic unified support. routes15302-15361,17419-17454,26815-26855.
## Evidence limit
Exact source contracts, not new live execution qualification. Retained config/provider/runtime capability can narrow availability. No test sessions created. Cancellation/approval honest pending/stopping/error states required. Temporary privacy unsupported by ordinary pinned runs API. Public source checkouts and this new doc only.