Repository navigation
Hermes 0.9.0: import offer, queued writes, capture within server limits (HER-02/07/08) - #53
Merged
Merged
Conversation
Port of 68c97a1 onto origin/main (e5af0a1): past-conversation import offer, history.py replay through the spool, substrate_import tool. Versions and release check bumped to 0.9.0 to match the repository VERSION. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJwSpwAivFKrfusNkeh96b Signed-off-by: Pavel-Tk <110731619+Pavel-Tk@users.noreply.github.com>
…pture within server limits HER-02: the import offer is put to the agent once per connection (recorded in import-offer.json), and a direct request for past conversations goes to substrate_import. The tool, the explicit-write tool and the system prompt all say so: never memory_remember for an import, and say plainly when an import did not run. HER-07: memory_remember and memory_forget report status "queued" when the write is durable in the spool but the synchronous call could not finish (offline, timeout, 5xx, rate limit, unknown outcome). The spool sender is started so it syncs by itself, and the message says not to retry. Only a write that was neither sent nor queued reports an error. HER-08: the server's redaction keeps arrays and objects of at most 128 items (substrate-v5 agent-api/redaction.mjs). A capture_turn with more messages reaches the ledger as payload.messages == "[TRUNCATED]" and is refused as invalid_request; the plugin quarantined it. Now: - live capture sends only messages since the last one enqueued for the session (fingerprint-checked, falls back to the window when the history changed); - every envelope is split into parts of at most 128 messages and 200 KiB with contiguous offsets (contract.capture_turn_parts), and history import uses the same splitter; - the local validator enforces the server bounds (128 items per array or object, depth 8, strings over 8192 bytes refused); - the client refuses requests over 262144 bytes as payload_too_large, not invalid_request; - the spool splits a capture_turn refused as invalid_request or payload_too_large (counter "split"), trims a single oversized message as a last resort (counter "trimmed"), and quarantines only otherwise; - resumed history imports judge each part by its own offsets. Versions: substrate-hermes 0.9.0 (plugin.yaml, client, release check), matching the repository VERSION. Claude and Codex sync scripts: MAX_MESSAGES_PER_ITEM 4096 -> 128, the same server bound (their long turns were refused too). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJwSpwAivFKrfusNkeh96b Signed-off-by: Pavel-Tk <110731619+Pavel-Tk@users.noreply.github.com>
The server's redaction bound (128 items per array) makes the effective limit 128 messages per capture_turn, not 4096. Parts of about 200 KiB, and the /mcp body limit of 262144 bytes. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJwSpwAivFKrfusNkeh96b Signed-off-by: Pavel-Tk <110731619+Pavel-Tk@users.noreply.github.com>
The tool now starts the spool sender, so the same write (same event id) is delivered via memory_import as well. The test asserted the write stayed pending, which raced with the sender on CI (3.11). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJwSpwAivFKrfusNkeh96b Signed-off-by: Pavel-Tk <110731619+Pavel-Tk@users.noreply.github.com>
…he real profile The tool now starts the spool sender, so tests that assumed "no sender" raced with it on CI (3.11): a sender batch could land before an assertion on hits or pending. Those tests now wait for the spool to settle. The import-offer test no longer unsets HERMES_HOME, and the process-wide spool is reset per test, so no test writes into the real ~/.hermes profile. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJwSpwAivFKrfusNkeh96b Signed-off-by: Pavel-Tk <110731619+Pavel-Tk@users.noreply.github.com>
Pavel-Tk
force-pushed
the
fix/hermes-import-spool
branch
from
October 11, 2026 22:11
4d7ac61 to
385785a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes three Hermes findings from the 2026-10-11 customer test, and ports the unmerged Hermes import offer (68c97a1) onto current main.
Changes
asked_atinimport-offer.json), not on every turn until answered. Imports go throughsubstrate_import(preview / start / status / decline) and the spool'smemory_import. Tool and system guidance say a direct request for past conversations goes tosubstrate_import, nevermemory_remember, and that a failed import must not be reported as done.memory_remember/memory_forgetreturnstatus: "queued"when the write is durable in the spool but the synchronous call could not finish (offline, timeout, 5xx, 429, unknown outcome). The sender is started so the write syncs by itself, and the text says not to retry. Only a write that was neither sent nor queued reports{"error": ...}.contract.capture_turn_parts). History import uses the same splitter.payload_too_large(wasinvalid_requestat 512 KiB).invalid_request/payload_too_large(countersplit), trims one oversized message as a last resort (trimmed), and quarantines only otherwise.MAX_MESSAGES_PER_ITEM4096 -> 128 in the Claude and Codexsubstrate_sync.pycopies (byte-identical, as before). Same server bound: their turns over 128 messages were refused too.substrate-hermes0.9.0 inplugin.yaml(both),PLUGIN_VERSION, and the release check, matching the repositoryVERSION. No tag, no release.Root cause of HER-08
substrate-v5/agent-api/redaction.mjs(redactValue,MAX_KEYS_PER_LEVEL = 128) replaces any array or object with more than 128 items by the string"[TRUNCATED]". A Hermes capture replays the whole session, so from message 129 on,payload.messagesbecame a string.validateHermesLedgerEnvelopethen returnedinvalid_request,importErrorCategorymapped that torejected, and the plugin quarantined it as a permanent refusal. I confirmed this by running the server'sredactValueon a 136-message array: it returns"[TRUNCATED]". The contract still said "<= 4096 messages per turn", which the server never honours.Tests
387 passed, 5 skipped(was370 passed, 5 skippedbefore this PR; the skips need a server checkout or node, as before).ruff check .,compileall,check_public_hygiene.py, JSON manifests, andbuild_release.py --checkall pass.substrate_import, notmemory_remember;queuedand enqueue a durable write; a write that cannot be enqueued still fails;payload_too_large.Risks and notes
docs/mcp-contract.mdstill says "<= 4096 messages per turn" in section 5 (a shared doc, left alone here). The effective bound is 128 until the server's redaction is changed (see below).payload.messages, or returnpayload_too_largeinstead of[TRUNCATED]. The plugin side works with the current server.🤖 Generated with Claude Code
https://claude.ai/code/session_01PJwSpwAivFKrfusNkeh96b