This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(kora): KR-HERMES-LOCAL-EXTENSIONS — in-fork hooks + listener registration#172
Merged
rafe-walker merged 1 commit intoMay 24, 2026
Conversation
…istration
Per Council R3 close-out + operator pre-decision B + feedback-
local-first-upstream-after. Adds the Hermes-core extensions
KR-REASONING-ROUTE-THROUGH-GATEWAY will require. All changes in
our local fork on feature/phase2-upgrades. Upstream-PR
packaging waits until the route-through bucket battle-tests
these surfaces.
# Discovery findings (audit before code)
5 areas audited; 4 gaps confirmed + extension added; 1 deferred.
1. pre_llm_call mutability — gap (observer-only today). Added new
hook pre_api_request_mutable.
2. post_llm_call re-issue — DEFERRED to follow-on bucket
KR-HERMES-LOCAL-EXT-REISSUE.
Substantial control-flow change
inside conversation_loop; per §4
STOP-ASK guidance for invasive
extensions.
3. context.route field — gap (Hermes hooks carry `platform`
not `route`). Threaded as `route`
kwarg into 7 hook sites.
4. Listener registration — gap (register_platform is chat-
platform shape, not background-
daemon shape). Added
register_background_daemon +
BackgroundDaemonRegistry.
5. Tool-list manipulation hook — gap. Added pre_tool_list_finalized
in build_api_kwargs.
# Extension contracts
(a) Hook ``pre_api_request_mutable`` (VALID_HOOKS:135)
Fires at api_kwargs construction site in conversation_loop
(after _build_api_kwargs returns + before observer
pre_api_request). Plugins return {"override": {...}} to
replace api_kwargs keys before SDK call. Backward compat:
pre_api_request observer still fires + sees the final
post-override api_kwargs.
(b) Hook ``pre_tool_list_finalized`` (VALID_HOOKS:144)
Fires in chat_completion_helpers.build_api_kwargs at the
tools_for_api = agent.tools site. Plugins return
{"override": [tool, ...]} to filter the tool list per-call
WITHOUT mutating agent.tools (which is process-wide).
First non-None override wins. Fail-safe: any hook exception
→ caught + logged → unfiltered tools.
(c) ``route`` kwarg threaded into 7 hook sites in
conversation_loop.py (pre_llm_call, pre_api_request,
pre_api_request_mutable, post_api_request, post_llm_call,
on_session_start, on_session_end, transform_llm_output) +
pre_tool_list_finalized in chat_completion_helpers. Sourced
from getattr(agent, "route", "") or "". Backward compat:
legacy CLI invocations that never set agent.route see "".
(d) PluginContext.register_background_daemon(name, startup,
shutdown, *, periodic_task=None, shutdown_timeout=5.0) +
BackgroundDaemonRegistry singleton at agent/background_
daemon_registry.py. Registration-only in this bucket;
lifecycle execution is the consumer's responsibility (Kora's
DaemonCoordinator already implements consumer shape;
gateway-side consumer wiring is the route-through bucket).
# Backward compatibility
- All 17 pre-existing VALID_HOOKS entries preserved
- All 7 pre-existing hook invocations gain a `route=` kwarg
(additive; existing **kwargs plugins ignore it transparently)
- register_hook contract unchanged for existing hook names
- agent.tools is never mutated by pre_tool_list_finalized
- 145/145 pre-existing hook tests (test_shell_hooks +
test_plugin_llm + test_transform_llm_output_hook +
test_transform_tool_result_hook + test_model_tools) pass
# Tests
tests/agent/test_hermes_local_extensions.py — 19 new tests:
- VALID_HOOKS contains new entries (2 tests)
- BackgroundDaemonRegistry: register / list / order / dup /
by_name / periodic_task / shutdown_timeout / singleton (8)
- PluginContext.register_background_daemon forwarder (3)
- register_hook accepts new hook names without warning (2)
- pre_tool_list_finalized integration: filter + no-override
fall-through + exception fail-safe (3)
- PeriodicTaskSpec round-trip (1)
# Documentation
kora_docs/14_research/hermes_local_extensions_2026-05-23.md
covers:
- Discovery findings (per-surface gap analysis)
- Per-extension contract (file:line, return semantics, fail
handling)
- Backward-compat assertions
- Upstream-PR readiness checklist per extension (gates for
when each can package into a Hermes-upstream PR)
- Verification commands
# Regression
164/164 in-scope tests pass (19 new + 145 existing hook tests).
Full repo xdist: 9599 passed, 45 failed. 43 of 45 are pre-
existing baseline flakes; the +2 (test_panel_inventory_count_
matches_expected pin drift 34→36 + test_banner_hides_when_no_
active_alerts AlertsBanner.tsx regex drift) are pre-existing
FE-instrumentation snapshot tests unrelated to agent/ changes.
Zero failures in tests/agent/* or tests/test_*hook* —
backward compat for all existing Hermes plugin code preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merged
13 tasks
rafe-walker
added a commit
that referenced
this pull request
May 24, 2026
…ck R3-2 Phase C completion (#189) Substantial paired bundle. Deliverable 1 — New local Hermes hook post_llm_call_can_reissue at agent/conversation_loop.py. First-non-None override semantics matching #172/#181 family. Anti-loop safety: at most one reissue per iteration. Backward compat: post_api_request observer sees FINAL (post-reissue) response only. Deliverable 2 — kora_hermes_plugin/haiku_router/ following #185 template. Plugin consumes should_escalate_post_call (from cost_ladder/selector.py since #185 but no caller until now). Implements parallel-Claude pattern from R3: low-confidence Haiku response → reissue to Opus with Haiku response in messages context. record_inference fires twice per iteration (Haiku + Opus reissue) with escalated_to_opus tagged correctly. Sample trace: Haiku reply uncertain (i"m not sure...) → escalates → Opus response includes Haiku context → Opus confirms terser. Two consecutive record_inference calls, only second tagged escalated_to_opus=True. What this completes: 6 of 7 plugin extractions (KR-PLUGIN-IDENTITY still deferred per Lock R3-2); all KR-HAIKU-ROUTER #165 escalation paths now functional end-to-end; Lock R3-2 Phase C closed. Two non-blocking follow-ups flagged in PR body: escalation_reason as structured telemetry field (one-line CostStateHolder.record_inference bump); per-call api_call_count accounting (transparent-upgrade vs per-call — currently transparent). 125 directly-affected tests green; 56 broader-suite failures verified pre-existing on base (fastapi/blake3/HERMES_HOME environmental, not regressions).
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Summary
Path B kickoff bucket per Council R3 close-out + operator pre-decision B +
feedback-local-first-upstream-after. Adds the Hermes-core extensions that KR-REASONING-ROUTE-THROUGH-GATEWAY will require. All changes in our local fork onfeature/phase2-upgrades; zero upstream-PR work in this bucket — upstream packaging waits until the extensions are battle-tested.Discovery findings — what was already supported vs gap
5 areas audited. 4 gaps confirmed + extended; 1 deferred.
pre_llm_callmutability{"context": str}orstrfor context-string injection. Cannot mutate model/kwargs.pre_api_request_mutablepost_llm_callre-issueKR-HERMES-LOCAL-EXT-REISSUE(substantial control-flow change in conversation_loop; §4 STOP-ASK guidance)context.routefieldplatform(slack/discord/cli) not the per-route telemetry taxonomyroutekwarg threaded into 7 hook sitesregister_platformexists but shaped for chat-platform adapters; no background-daemon shapeBackgroundDaemonRegistry+PluginContext.register_background_daemonagent.toolsused verbatim inbuild_api_kwargs:235pre_tool_list_finalizedPer-extension contract
pre_api_request_mutablehook (NEW)agent/conversation_loop.py:~990(after_build_api_kwargsreturns + before observerpre_api_request){"override": {<kwarg>: <value>}}→ keys replaceapi_kwargs[key]; multiple plugins merge left-to-right (last write wins)pre_api_requeststill fires AFTER override applies — sees the final api_kwargspre_tool_list_finalizedhook (NEW)agent/chat_completion_helpers.build_api_kwargs:~237{"override": [<tool>, ...]}→ replaces tools for that SINGLE API callagent.toolsNEVER mutatedagent.toolsusedroutefield (NEW kwarg, threaded into 7 hook sites)getattr(agent, "route", "") or ""— caller (Kora's route-through code) setsagent.route = "slack_dm"before invoking""→ maps to telemetryROUTE_UNKNOWNon_session_start,pre_llm_call,pre_api_request,pre_api_request_mutable,post_api_request,transform_llm_output,post_llm_call,on_session_end,pre_tool_list_finalized**kwargsplugin callbacks ignore the new kwarg transparentlyBackgroundDaemonRegistry+PluginContext.register_background_daemon(NEW)agent/background_daemon_registry.py(188 LOC)PluginContext.register_background_daemon(name, startup, shutdown, *, periodic_task=None, shutdown_timeout=5.0)DaemonCoordinatoralready implements; gateway-side consumer wiring lands in KR-REASONING-ROUTE-THROUGH-GATEWAY)ValueError(matchesplatform_registry.registersemantic)Upstream-PR readiness notes
pre_api_request_mutablepre_tool_list_finalizedroutefieldKNOWN_ROUTESis a candidate; recommend extracting Hermes-friendly taxonomy first.register_background_daemonBackgroundDaemonRunner(consumer of registry) is a natural companion PRTest plan
tests/agent/test_hermes_local_extensions.pypasstest_shell_hooks,test_plugin_llm,test_transform_llm_output_hook,test_transform_tool_result_hook,test_model_tools)register_hook/register_platformalteredtest_panel_inventory_count_matches_expectedpin drift 34→36 +test_banner_hides_when_no_active_alertsAlertsBanner.tsx regex drift) are pre-existing FE-instrumentation snapshot tests unrelated to agent/ changes (recent FE buckets added pages / tweaked components without updating the pins).tests/agent/*ortests/test_*hook*— backward compat for all existing Hermes plugin code preserved.Research doc
kora_docs/14_research/hermes_local_extensions_2026-05-23.md(~280 lines) — discovery findings + per-extension contract + backward-compat assertions + upstream-PR readiness checklist + verification commands. Will reuse this when packaging the upstream PRs once route-through stabilizes.🤖 Generated with Claude Code