This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(kora): KR-PLUGIN-IDENTITY-OPTION-C-AND-DAEMON-PHASE2 — full identity-as-plugin + 8-listener migration#199
Merged
rafe-walker merged 1 commit intoMay 24, 2026
Conversation
…tity-as-plugin + 8-listener migration
Two substantial deliverables in one bucket. After this: 7/7 plugin extractions complete (Lock R3-2 closed); 9/9 periodic-task listeners on the gateway path (Phase 2 of 6).
Deliverable A — KR-PLUGIN-IDENTITY (Option C). Full identity-as-plugin architecture so the eventual `pip install kora-runtime` distribution works AND multi-tenant Hermes deployments (Kora + Marvin + Dave all on one install) are possible.
New surface:
- agent/identity_spec.py — IdentitySpec frozen dataclass (soul_md_content + system_prompt_content + identity_metadata) + IdentityProvider type alias
- kora_cli/plugins.py — VALID_HOOKS += "pre_agent_identity_set"; new PluginContext.register_identity_provider convenience method that wraps the {"identity": IdentitySpec} envelope so plugin authors return raw IdentitySpec
- kora_cli/reasoning/anthropic_engine.py — AnthropicReasoningEngine.__init__ now invokes the hook BEFORE the file-read fallback; first-non-None-wins; backward-compat fallback to kora_system_prompt.md path preserved for bare-Hermes-no-Kora-plugin users
- kora_cli/reasoning/kora_hermes_plugin/identity/ — sub-plugin following the #185 template (constants + loader + plugin + register). Kora claims its canonical identity from kora_system_prompt.md + SOUL.md. Operator escape hatch: KORA_DISABLE_IDENTITY_PROVIDER=true falls back to file-read
Upstream-PR drafting deferred per Joshua's amended feedback-local-first-upstream-after — the new hook lives in the Kora fork only; battle-testing happens before any upstream-PR work.
Companion kora-docs PR has HOW_TO_BUILD_YOUR_OWN_AGENT.md proving the architecture supports a 3rd-party "Marvin" agent registering its own identity without forking Hermes.
Deliverable B — KR-DAEMON-LISTENERS-VIA-GATEWAY Phase 2. 8 remaining periodic-task listeners migrated to dual-registry shape (Path B thin-shim, same pattern as snapshot listener in #196): heartbeat_probes, email_inbound_imap, probe_wake, mcp_consumption, cost_telemetry, alert_notifier, promote_phrasebook, promote_snapshot_expand.
Each Listener-class-shape listener (6 of 8):
- Process-wide _listener_singleton so both registries point at the same instance's bound methods (prevents double-log-on-startup if both consumers fire)
- startup() now accepts optional coordinator kwarg (Hermes BackgroundDaemonEntry.startup is Callable[[Any], Any]; Kora's coordinator passes no arg)
- BackgroundDaemonEntry registered with PeriodicTaskSpec carrying the same callback + interval the heartbeat scheduler runs
- Defensive duplicate-registration guard for importlib.reload + xdist workers
The 2 pure-periodic-task listeners (promote_phrasebook, promote_snapshot_expand) get no-op startup/shutdown wrappers + a BackgroundDaemonEntry that carries only the periodic task (no Kora-side LISTENER_REGISTRY entry to bridge — those never had one).
Multi-task listeners (cost_telemetry has 3 periodic tasks; alert_notifier has 2) carry only the PRIMARY task in the BackgroundDaemonEntry per §4.1 audit recommendation c. The other tasks stay on Kora's heartbeat scheduler until Phase 4 (scheduler dissolution) revisits the multi-task shape.
Bucket spec said "purelymail_client (IMAP poller)" but the actual IMAP poller is email_inbound_imap_listener.py — purelymail_client_listener.py is the outbound SMTP singleton-holder (not periodic-task). Migrated email_inbound_imap; flagged in PM hand-off.
3 newer promotion listeners (promote_probe_fix_envelopes, promote_router_tuning, promote_tool_trimming) were added after my prior audit. They follow the same shape as promote_phrasebook + promote_snapshot_expand; flagged for a tiny Phase 2.5 follow-up bucket.
Tests: 78 new tests across identity (26) + Phase 2 listeners (38) + IdentitySpec dataclass (5) + test_kora_hermes_plugin (1 renamed + 1 new). 563/563 focused regression set green (all listener tests + all kora_hermes_plugin tests + cost_ladder + cost_telemetry). Two existing MockCtx tests updated to handle the new register_identity_provider method.
Inventory state post-merge:
- 7/7 plugin extractions (closes Lock R3-2): cost_ladder, audit, caching, short_circuit, state_holders, haiku_router, identity
- 9/9 periodic-task listeners on the gateway path (Phase 2 complete)
- Remaining listener migration phases: Phase 3 (singleton-holders: slack_client, purelymail_client, reasoning_engine), Phase 4 (scheduler dissolution), Phase 5 (HTTP service-mounts stay Kora-local), Phase 6 (DaemonCoordinator shim)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 24, 2026
Merged
rafe-walker
added a commit
that referenced
this pull request
May 24, 2026
…Option C (#203) Build Marvin as a runnable bundled plugin to validate the KR-PLUGIN-IDENTITY Option C architecture from #199 actually works for a non-Kora identity. Proves the pip-installable Kora bundle vision before any external IsoKron user tries it. plugins/marvin/ — new bundled plugin (Hermes plugin SDK discovery via plugins/<name>/ convention) - plugin.yaml — manifest declaring the pre_agent_identity_set hook - __init__.py — register(ctx) → ctx.register_identity_provider(marvin_identity_provider). Reads MARVIN.md + marvin_system_prompt.md eagerly at import time. - MARVIN.md — Paranoid Android persona (SOUL.md analog for Marvin's identity) - marvin_system_prompt.md — reasoning-engine prompt; explicitly says "you are not Kora" 11 tests in tests/plugins/test_marvin_multi_tenant_proof.py validate end-to-end: Scenario 1 — Kora-only: bare Hermes without Marvin → engine uses Kora's identity (pre-#199 behavior preserved; no regression) Scenario 2 — Marvin-only: Hermes + Marvin plugin (NO Kora) → engine uses Marvin's identity. THIS is the architecture validation — proves Option C supports a non-Kora identity via the same hook surface, with NO code in Kora's fork changing. Scenario 3a — Both plugins, Marvin first in plugins.enabled → Marvin wins (first-non-None-wins by FIFO) Scenario 3b — Both plugins, Kora first → Kora wins (same code path, opposite outcome from registration order; operator controls via config.yaml ordering) Plus: manifest pin, identity-files-exist pin, register-wires-correctly pin, would-engine-init-consume-marvin's-spec pin, no-provider fallback pin Live demo captured in companion kora-docs PR (MARVIN_DEMO_TRANSCRIPT.md) — all 4 scenarios reproducible. Tests: 11/11 Marvin tests + 544/544 focused regression set green. 60 broader-sweep failures verified pre-existing on baseline (4657 baseline → 4721 with my +64 new tests; same 60 failures present pre-PR; test-isolation issue unrelated to this work). Gaps surfaced (documented in MARVIN_DEMO_TRANSCRIPT.md §6): - SOUL.md content carried but not yet engine-consumed (future refactor; not pressing) - No end-to-end test constructing a real engine (requires Anthropic creds; structural pin from #199 covers source-level wiring) - Hot-reload not supported (matches existing identity-loading contract; not a regression) - No actual pip-install publish path validated yet (mechanically the same surface; cheap to validate when operator dispatches the bundle bucket) Co-authored-by: CC#3 Kora Runtime <kora-pm@stormhavenenterprises.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
Two substantial deliverables. After this lands: 7/7 plugin extractions complete (closes Lock R3-2); 9/9 periodic-task listeners on the gateway path (Phase 2 of 6).
Companion kora-docs PR with HOW_TO doc: https://github.com/rafe-walker/kora-docs/pull/new/docs/kora-KR-PLUGIN-IDENTITY-OPTION-C-AND-DAEMON-PHASE2-MEGABUCKET
Deliverable A — KR-PLUGIN-IDENTITY (Option C)
Full identity-as-plugin architecture. Multi-tenant deployments + `pip install kora-runtime` distribution become possible.
IdentitySpec contract
```python
@DataClass(frozen=True)
class IdentitySpec:
soul_md_content: str # persona prompt (historically ~/.kora/SOUL.md)
system_prompt_content: str # reasoning-engine prompt (kora_system_prompt.md)
identity_metadata: dict # free-form: agent_name, version, plugin_name, etc.
```
Hook firing
```python
kora_cli/reasoning/anthropic_engine.py — AnthropicReasoningEngine.init
results = invoke_hook("pre_agent_identity_set", engine=self)
for r in results:
if isinstance(r, dict) and isinstance(r.get("identity"), IdentitySpec):
# first-non-None wins; engine uses identity.system_prompt_content
break
else fall back to existing file-read at kora_system_prompt.md
```
Plugin author surface
```python
def my_identity_provider(*, engine, **kw) -> IdentitySpec | None:
return IdentitySpec(soul_md_content="...", system_prompt_content="...")
In plugin's register(ctx):
ctx.register_identity_provider(my_identity_provider)
```
The convenience method wraps the `{"identity": ...}` envelope automatically; plugin authors return raw `IdentitySpec`. First-non-None-wins semantic. Fail-safe on exceptions.
Kora's identity sub-plugin
`kora_cli/reasoning/kora_hermes_plugin/identity/` — follows the #185 template (constants + loader + plugin + register). Claims Kora's canonical identity from `kora_system_prompt.md` + `SOUL.md`. Operator escape hatch: `KORA_DISABLE_IDENTITY_PROVIDER=true` falls back to file-read.
Backward compat
Upstream-PR drafting deferred
Per Joshua's amended `feedback-local-first-upstream-after`: "we deal with sharing the CODE once its actually fucking tested." The new hook + IdentitySpec + ctx method live in the Kora fork only. No upstream-PR draft created in this bucket. Battle-testing happens first.
Deliverable B — KR-DAEMON-LISTENERS-VIA-GATEWAY Phase 2
8 remaining periodic-task listeners migrated to dual-registry shape (Path B thin-shim; same pattern as snapshot listener in #196).
Per-listener migration table
Per-Listener-class shape changes
Note on bucket-spec divergence (already flagged in commit body)
Test plan
HOW_TO_BUILD_YOUR_OWN_AGENT.md preview
(In companion kora-docs PR. TOC reproduced here for convenience.)
🤖 Generated with Claude Code