Ellie continuity bridge (Phase 3, Plan B): conversation_id + session-end notify - #2
Merged
Merged
Conversation
Adds _conversation_id() helper that prefers _gateway_session_key (stable cross-session key for gateway-routed conversations) and falls back to session_id, then "hermes-unknown". Includes two new tests covering both paths.
…gger) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zerocool0133700-lgtm
pushed a commit
that referenced
this pull request
Jul 30, 2026
…ch#67140) The background write guard decided ownership from `isinstance(usage_rec, dict)`, so a local skill with NO usage record passed. That successful write called bump_patch(), which created a `created_by: null` record — and the identical write was refused from then on. "Allowed exactly once, then never" is a race with our own bookkeeping, not a policy. Reproduced on main: patch #1 succeeds, patch #2 with the same arguments is refused. Option B from the issue. Option A (split `session_review` from `scheduled_curator` and let the session fork patch user-owned skills it consulted) would widen autonomous write permission onto skills the user owns with no user present to consent — wrong direction for a no-user-present actor. - skill_manager_tool: missing and explicit-null records now resolve IDENTICALLY, both fail closed. The refusal names the reason and points at `hermes curator adopt <name>`. - background_review: both review prompts told the reviewer to patch any skill consulted in the session and claimed pinned skills could be improved, while enforcement refused both. Prompts now list pinned, external, and user-owned skills as protected, and tell the reviewer to RECOMMEND adoption instead of attempting a write that will be refused. - skill_usage: document that `created_by` is a curator-management policy flag, not a provenance claim, and add `is_curator_managed()` so call sites read as the question they ask. Field name retained — it is on disk in every `.usage.json` and renaming would strand those records. - curator CLI: `hermes curator list-unmanaged` itemizes unmanaged skills with the reason each is unmanaged (completes the NousResearch#67139 spec). Foreground writes are untouched: a user-directed edit to a user-owned skill still works, including on pinned skills. Sibling tests: 9 failures in test_skill_manager_tool.py were fixtures that created record-less skills to exercise OTHER guards (consolidation-delete, read-before-write) and relied on ownership falling through. Fixed at the fixture, since the real curator only ever operates on managed sediment. One test asserted the old "manually authored" wording; rewritten to assert the behavior contract instead of the string. Validation: 274 targeted tests + all 7 background-review files (60 tests) pass. E2E on a temp HERMES_HOME (30 checks) covers the flip, foreground writes, adoption unblocking, pin semantics, prompt/enforcement parity, and the new verb. Each new test sabotage-verified: revert the fix, confirm it goes red. Fixes NousResearch#67140
zerocool0133700-lgtm
pushed a commit
that referenced
this pull request
Jul 30, 2026
…hat tile (NousResearch#71969) * fix: Branch button is a dead no-op inside a branched chat tile session-tile.tsx wired onBranchInNewChat to () => undefined for tiled/branched sessions (nested branching isn't supported there), but the button in AssistantMessage's action bar rendered unconditionally regardless of whether a real handler was supplied. The button looked clickable but silently did nothing, with no visual feedback. - AssistantMessage now only renders the Branch button when onBranchInNewChat is actually provided, matching the existing pattern used for onDismissError/onRestoreToMessage. - session-tile.tsx no longer passes a no-op handler; the prop is simply omitted so the button doesn't render in tiles. - onBranchInNewChat is now optional on ChatViewProps, and the latestChatActions passthrough wrapper uses the existing latestOptional helper instead of an unconditional call. * test: assert Branch button visibility matches handler presence Adds coverage for the bug #2 fix: renders Thread with and without an onBranchInNewChat handler and asserts the Branch in new chat button is shown only when a real handler is supplied, hidden otherwise - covering both the normal open-chat case and the session-tile (branched chat) case that used to leave a dead, clickable button.
zerocool0133700-lgtm
pushed a commit
that referenced
this pull request
Aug 10, 2026
… a broken chat A completely unconfigured install previously booted into a working-looking chat (banner showed model 'unknown'), accepted a message, spun ~30s, then failed with 'Set OPENROUTER_API_KEY' — a provider the user never chose — and never offered setup. - HermesCLI.run() now probes provider readiness at startup (TTY only) and offers the shared provider picker (hermes model flow, which fronts Quick Setup / Nous Portal OAuth) when nothing is configured. Decline is respected; picker state re-syncs into the live CLI so the next turn works without a restart. - New silent probe _runtime_credentials_ready(): no printing, no state mutation; handles keyless local endpoints and callable bearer providers. - The empty-api-key error is provider-aware: names the actual resolved provider and points at 'hermes model' / 'hermes setup' instead of hardcoding OPENROUTER_API_KEY. - Banner: unconfigured installs render 'no model configured — run /model' in red instead of the silent 'unknown' model slug. Consumer-onboarding audit finding #2 (sev 5), Aug 2026.
zerocool0133700-lgtm
pushed a commit
that referenced
this pull request
Aug 10, 2026
A wedged adapter transport (network hang, dead websocket) previously blocked _check_session_stalls forever: sibling candidates in the same pass were never evaluated and the watcher stopped ticking. Wrap the send in asyncio.wait_for (15s); on timeout log a WARNING and do NOT latch, so the next tick retries. Regression uses a never-resolving fake adapter and proves the pass completes, a healthy sibling candidate is still notified in the same pass, and the watcher ticks again (sabotage-verified against the unbounded send).
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.
Summary
The producer side of Ellie's cross-session continuity loop. Builds on the Phase 1 Ellie sidecar bridge (PR #1, merged) so Hermes drives Ellie's memory layer end-to-end. Plan:
docs/plans/2026-06-14-ellie-continuity-bridge-plan-b.md.conversation_idon every/api/turn—agent._gateway_session_key or agent.session_id, so Ellie reads the right per-conversation digest.notify_ellie_session_end()— at session end, POSTs the final transcript + a monotonicmarkerto Ellie's/api/session-end, so Ellie rolls the digest forward and distills durable facts to the Forest. Best-effort, fire-and-forget (Ellie returns 202).shutdown_memory_provider(the single session-end funnel for CLI, gateway,/reset), backend-gated, fire-once, fully fail-soft.Design notes
marker= epoch-millis at session start (notlen(history)): must be monotonic across sessions sharing a channel (so a later session's digest wins Ellie's compare-and-set) yet stable across retries of the same session-end (so Ellie's dedupe blocks double-fire).self.backendattribute exists — backend is config-driven. The hook reads the same config the forwarder uses, so if turns route to Ellie, session-end fires too.Test Plan
uv run pytest tests/agent/test_ellie_bridge.py→ 14 passed (Phase 1 + new conversation_id / session_end / fire-once / backend-gate cases)docs/plans/2026-06-14-ellie-continuity-smoke-runbook.md): against an isolated2/2/smokeForest scope, a fact stated in session 1 ("deploy on Fridays, never Mondays") was distilled to the Forest at session-end, then surfaced via recall in a later, separate session's turn — Ellie answered "Always deploy on: Friday, Never deploy on: Monday." The smoke also found and fixed a too-tightrecall_timeout_msdefault on the Ellie side (250→2000ms).Counterpart
Ellie gateway PR NousResearch#64 (
feat/ellie-hermes-memory) implements the consumer side:/api/turnconversation_id,/api/session-end, distill-to-Forest, recall preamble.🤖 Generated with Claude Code