Type: Decision Status: Accepted Systems: Sources, Plugins, Cache Author: Phil / Claude Date: 2026-07-20 Related: LLP 0012, LLP 0015, LLP 0026, LLP 0030, LLP 0035, LLP 0118, LLP 0119, LLP 0122
Hermes activity materializes into the existing
ai_gateway_messagesdataset through theai_gateway.projected_exchangematerializer, withclient_name = 'hermes'. No new dataset is minted.
Pulled hermes sessions (LLP 0119) have to land somewhere. Three candidates:
- A dedicated
hermes_messagestable, per the LLP 0012 "one source, one table" default. - The otel
logsdataset (hermes rows flattened to severity/body log records). - The canonical
ai_gateway_messagesdataset, via theai_gateway.projected_exchangebackfill materializer that@hypaware/ai-gatewayregisters for exactly this purpose (ai-gateway/src/dataset.js,AI_GATEWAY_PROJECTED_EXCHANGE_KIND).
ai_gateway_messages, via the materializer. The hermes adapter is a
backfill provider yielding AiGatewayProjectedExchange items; the ai-gateway
plugin's materializer expands them into canonical rows, the same path
@hypaware/claude and @hypaware/codex use.
- Hermes data is AI-conversation data: sessions, turns, tool calls,
token usage. Every downstream consumer,
hyp queryconventions, the usage reports, context-graph projection (LLP 0023), export/sink policy seams,hyp purge, keys offai_gateway_messages. Rows landing there make hermes visible to all of it with zero consumer changes (spec R1). A parallel table would fork every one of those consumers. - "One source, one table" is not violated. The hermes adapter owns no
table at all; like claude and codex, it registers no dataset. The single
owner of
ai_gateway_messagesremains@hypaware/ai-gateway, and the materializer contract exists precisely so client adapters can feed the owner's table without owning it. - Rejecting otel
logs: flattening conversations to severity/body log records destroys the message/part semantics the dataset consumers rely on, andlogsis invisible to the AI-usage reports and the graph.
client_name/conversation_source:'hermes'. This is the join key that distinguishes hermes rows, symmetrical to the claude/codex adapters.provider: derived from the session's billing metadata (billing_provider/billing_base_urlin hermes'ssessionstable), normalized to the existing provider vocabulary where possible (openai,openrouter,nous, ...). Hermes talks to arbitrary OpenAI-compatible upstreams, soproviderreflects the upstream, never'hermes'itself: hermes is a client, not a provider.- Identity (spec R2): hermes message ids are integers scoped to the
store, not UUIDs.
message_id/part_idare minted deterministically from hermes's stable keys (session id + message id + part index), so re-imports dedupe via the existing pre-writepart_idguard.session_idcarries the hermes session id (namespaced to avoid collision with other clients' id spaces; exact format in LLP 0122). - Usage: token counts map through
LLP 0035 normalization into
attributes.usage; hermes-specific extras (estimated/actual cost, api_call_count, end_reason, hermessourcechannel) ride inattributes. - Threading:
parent_session_idmaps toparent_thread_id, giving subagent/delegation links the same shape Claude sidechains have.
- The hermes plugin declares
requires.plugins: ["@hypaware/ai-gateway"]in its manifest; the materializer is a hard dependency (LLP 0006). - Schema evolution stays additive and owned by ai-gateway
(LLP 0029); hermes
needs no new columns, only
attributespayloads. session_idpartitioning (LLP 0030) applies to hermes rows unchanged.- Code that lands this carries
@ref LLP 0120 [implements]on the projector that buildsAiGatewayProjectedExchangeitems.