Skip to content

fix(goals): prevent profile goal contract drift - #6899

Closed
ticketclosed-wontfix wants to merge 3 commits into
nesquena:masterfrom
ticketclosed-wontfix:fix/profile-goal-manager-contract
Closed

ticketclosed-wontfix wants to merge 3 commits into
nesquena:masterfrom
ticketclosed-wontfix:fix/profile-goal-manager-contract

Conversation

@ticketclosed-wontfix

@ticketclosed-wontfix ticketclosed-wontfix commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Thinking Path

  • Hermes WebUI promises persistent goals that continue after each incomplete turn.
  • Profile-scoped WebUI sessions used a local copy of GoalManager.evaluate_after_turn() rather than the native Hermes implementation.
  • Hermes' judge contract evolved from a two-value result to five values (verdict, reason, parse failure, wait directive, transport failure), while the WebUI copy still unpacked two.
  • The resulting ValueError was caught by the WebUI goal hook and converted to a non-continuing error decision, leaving the goal active but idle.
  • Hermes now exposes a context-local home override, so profile isolation no longer requires duplicating the goal engine.

What Changed

  • Run profile-scoped goal operations through the native Hermes GoalManager under a context-local profile home.
  • Reset the profile context in finally after every delegated operation.
  • Gate native delegation on both context-local home support and call-time default SessionDB path resolution; otherwise use the explicit-DB fallback.
  • Preserve snapshot rollback for both native and legacy profile managers.
  • Add regressions for:
    • the current five-field judge result;
    • native WAIT state and persistence;
    • same-session concurrent profile isolation;
    • the v2026.5.28–v2026.7.20 frozen-DEFAULT_DB_PATH compatibility range;
    • snapshot restoration;
    • context cleanup after success and exception paths.

Why It Matters

An incomplete goal turn no longer silently stops because WebUI's profile adapter drifted from Hermes' native goal contract. Delegating to the native manager also keeps newer wait, parse-failure, transport-failure, contract, and subgoal semantics from drifting independently in WebUI.

Verification

  • Original regression before the fix: test_profile_goal_evaluation_supports_native_judge_contract failed with decision["verdict"] == "error" instead of "continue".
  • Review regression before the capability gate: test_profile_goal_falls_back_when_session_db_path_is_frozen reproduced profile A reading goal-b after profile B wrote the same session ID through a frozen default DB path.
  • ./scripts/test.sh tests/test_goal_command_webui.py tests/test_stage326_pending_goal_continuation_race.py tests/test_issue_1932_goal_hook_unrelated_turns.py tests/test_webui_gateway_chat_backend.py -q
    • 77 passed
  • Current-Agent capability probe: native delegation selected and the previous Hermes home restored after the probe.
  • Actual Hermes Agent v2026.7.20 compatibility canary: _LegacyProfileGoalManager selected and two profiles with the same session ID retained separate goal-a / goal-b state.
  • Diff-scoped Ruff/new-code checks: pass.
  • git diff --check: pass.
  • Full-suite attempt reached 8,441 passed before 22 unrelated test_jump_to_answer_scroll_settle.py failures and a 60-second pytest-timeout internal error; that entire file then passed independently (26 passed). CI remains the authoritative full-matrix check.
  • Independent pre-commit review: pass; no security or logic findings.

Contract Routing

Task type: narrow runtime compatibility bug fix.

Touched areas:

  • api/goals.py profile-scoped goal persistence/evaluation adapter
  • goal/runtime regression tests

Relevant public docs:

  • AGENTS.md
  • CONTRIBUTING.md
  • docs/CONTRACTS.md
  • docs/rfcs/hermes-run-adapter-contract.md

State owner and invariant:

  • Hermes GoalManager remains the authoritative goal-state owner.
  • HERMES_HOME selection is task-local and is restored on every exit.
  • Concurrent profiles cannot read or write each other's goal state.

This restores documented behavior; it does not intentionally change the persistent-goal contract.

Risks / Follow-ups

  • Hermes builds without either required capability—including the intermediate frozen-default-path range—continue through the explicit-DB fallback; it accepts extra judge result fields but intentionally retains its older feature subset.
  • Automatic release of a native WAIT barrier is outside this focused repair and remains part of broader continuation scheduling work.
  • No UI or interaction changes; screenshots are not applicable.

Release-note wording: Profile-scoped persistent goals no longer stop silently when WebUI runs with current Hermes goal-judge semantics.

Model Used

OpenAI Codex provider, gpt-5.6-sol, with Hermes tool orchestration, isolated worktrees, TDD, and an independent reviewer subagent.

@greptile-apps

greptile-apps Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR restores profile-scoped persistent-goal compatibility by delegating to Hermes Agent’s native GoalManager when context-local database resolution is supported, while retaining an explicit-database fallback for older versions.

  • Adds capability probing before selecting native profile delegation.
  • Scopes native construction, operations, and snapshot restoration to the selected profile home with guaranteed context cleanup.
  • Extends the legacy judge adapter to accept the evolved result shape.
  • Documents the ownership and compatibility boundary and adds regression coverage for native semantics, isolation, rollback, and cleanup.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains, and the previously reported documentation gap is addressed by the new persistent-goal profile-boundary section.

Important Files Changed

Filename Overview
api/goals.py Adds capability-gated native profile delegation, guaranteed context cleanup, scoped native rollback, and a compatible legacy fallback without an identified blocking defect.
tests/test_goal_command_webui.py Adds focused regression coverage for the five-field judge contract, native wait behavior, concurrent profile isolation, fallback selection, snapshot restoration, and exception cleanup.
ARCHITECTURE.md Documents the native ownership boundary, capability probe, context lifecycle, rollback behavior, and compatibility fallback introduced by the implementation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Profile-scoped goal operation] --> B{Context-local home API available?}
    B -- No --> L[Legacy explicit-DB GoalManager]
    B -- Yes --> C[Bind profile home]
    C --> D{Default DB resolves to profile/state.db?}
    D -- No --> E[Reset context]
    E --> L
    D -- Yes --> F[Reset probe context]
    F --> N[Construct native GoalManager under scoped profile home]
    N --> O[Run native operation or snapshot restore]
    O --> R[Reset profile context in finally]
    L --> P[(Profile state.db)]
    O --> P
Loading

Reviews (4): Last reviewed commit: "fix(goals): gate native profile persiste..." | Re-trigger Greptile

Comment thread api/goals.py
Comment on lines +79 to +82
from hermes_constants import reset_hermes_home_override, set_hermes_home_override
except Exception: # pragma: no cover - depends on installed hermes-agent
return None
return set_hermes_home_override, reset_hermes_home_override

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Document profile goal delegation

The new context-local native-manager path, legacy compatibility fallback, and scoped rollback behavior change the documented runtime architecture, but no subsystem documentation describes the new ownership and compatibility boundary. This leaves future maintenance guidance based on the obsolete explicit-DB-only model.

Context Used: AGENTS.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@nesquena-hermes nesquena-hermes added the size:L Large PR (>10 files or >250 LOC) label Aug 10, 2026
@ticketclosed-wontfix
ticketclosed-wontfix force-pushed the fix/profile-goal-manager-contract branch from a5c307e to c4ff6d2 Compare August 12, 2026 21:41

@nesquena-hermes nesquena-hermes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ticketclosed-won — the goal-contract-drift concern is real and the fix direction is right, but the gate reproduced a profile-isolation hole in the version gating that needs tightening first:

Must-fix (CORE, reproduced) — cross-profile goal overwrite on an intermediate Agent range

api/goals.py:309. On Hermes Agent v2026.5.28–v2026.7.20, the native _ProfileGoalManager is selected because the context-override API is present — but on those versions SessionDB() still resolves to the frozen import-time DEFAULT_DB_PATH, so all profile goal mutations target the same state.db and one profile overwrites another's goal. Reproduced: two profiles with the same session id both ended up reading goal-b (the explicit-DB fallback correctly kept goal-a/goal-b isolated).

Fix: gate selection of _ProfileGoalManager on both (a) context-home support and (b) call-time SessionDB path resolution — if either is missing, fall back to _LegacyProfileGoalManager. Add a regression with a frozen DEFAULT_DB_PATH proving identical session IDs stay isolated across two profiles (the current 76 tests pass but don't exercise this intermediate-version contract).

Re-push with the capability gate tightened and I'll re-gate.

Use Hermes' context-local home override to run profile-scoped goal operations through the native GoalManager, preserving current judge, wait, and failure semantics while retaining the explicit-DB legacy fallback.
@ticketclosed-wontfix
ticketclosed-wontfix force-pushed the fix/profile-goal-manager-contract branch from c4ff6d2 to bb8b10d Compare August 17, 2026 09:19
@ticketclosed-wontfix

Copy link
Copy Markdown
Contributor Author

Addressed the reproduced intermediate-Agent profile-isolation hole.

  • Native profile delegation now requires both context-home support and call-time default SessionDB path resolution.
  • If either capability is missing or the resolved DB path does not match the selected profile, WebUI uses the explicit-DB legacy manager.
  • Added a regression with a frozen DEFAULT_DB_PATH and identical session IDs across two profiles; before the gate, profile A read goal-b, and after the gate the goals remain goal-a / goal-b.
  • Verified against an actual Hermes Agent v2026.7.20 checkout: _LegacyProfileGoalManager was selected and isolation held.
  • Verified on the current Agent: native delegation remains selected and the ambient Hermes home is restored.
  • Focused goal/runtime suite: 77 passed. Diff-scoped Ruff and git diff --check: passed.

Rebased onto current origin/master and force-pushed commit bb8b10d0. Ready for re-gate.

nesquena-hermes pushed a commit that referenced this pull request Aug 17, 2026
…not installed)

#6899's native-contract tests imported hermes_cli unconditionally, failing
CI with ModuleNotFoundError. Match the repo's established importorskip pattern
so they skip cleanly when the agent isn't installed and run when it is.
Co-authored-by: ticketclosed-wontfix
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Re-gate at head bb8b10d: SAFE TO SHIP — Codex verified the capability gate now requires both context-home resolution AND call-time DB-path selection, the affected agent tags (v2026.5.28, v2026.7.20) correctly select the legacy manager so profiles keep separate goals, and newer builds use native delegation correctly (32 goal tests pass).

One CI-only fix I folded into the release: the new test_goal_command_webui.py native-contract tests imported hermes_cli unconditionally, which fails CI (ModuleNotFoundError) since the agent isn't installed there. I guarded those with pytest.importorskip("hermes_cli.goals", reason="hermes-agent not installed") — matching the repo's established pattern (e.g. test_xsession_wakeup_misroute.py) — so they skip cleanly in CI and run when the agent is present. Shipping in the next experimental release; thanks!

nesquena-hermes added a commit that referenced this pull request Aug 17, 2026
…-webui entry point (#6742) (#7108)

* fix(goals): delegate profile evaluation to native manager

Use Hermes' context-local home override to run profile-scoped goal operations through the native GoalManager, preserving current judge, wait, and failure semantics while retaining the explicit-DB legacy fallback.

* docs(goals): describe profile ownership boundary

* fix(goals): gate native profile persistence capability

* feat(cli): add packaged hermes-webui CLI entry point (#6739)

* fix(cli): route hermes-webui entry through bootstrap:main for wheel install (#6742)

* docs(changelog): note #6899 profile goal isolation + #6742 hermes-webui entry point

* test(goals): guard hermes_cli import with importorskip for CI (agent not installed)

#6899's native-contract tests imported hermes_cli unconditionally, failing
CI with ModuleNotFoundError. Match the repo's established importorskip pattern
so they skip cleanly when the agent isn't installed and run when it is.
Co-authored-by: ticketclosed-wontfix

---------

Co-authored-by: Nick <202622897+ticketclosed-wontfix@users.noreply.github.com>
Co-authored-by: webtecnica <webtecnica@gmail.com>
Co-authored-by: n <a@n>
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Shipped in experimental release exp-v0.52.237. Native profile-goal persistence is now gated on the agent supporting both context-home resolution and call-time DB-path selection (else it falls back to the legacy per-profile manager), so profiles stay isolated on the affected builds. Thanks @ticketclosed-won! (I folded in a small CI-only test guard — see the comment above.)

alai04 pushed a commit to alai04/hermes-webui that referenced this pull request Aug 31, 2026
…+ hermes-webui entry point (nesquena#6742) (nesquena#7108)

* fix(goals): delegate profile evaluation to native manager

Use Hermes' context-local home override to run profile-scoped goal operations through the native GoalManager, preserving current judge, wait, and failure semantics while retaining the explicit-DB legacy fallback.

* docs(goals): describe profile ownership boundary

* fix(goals): gate native profile persistence capability

* feat(cli): add packaged hermes-webui CLI entry point (nesquena#6739)

* fix(cli): route hermes-webui entry through bootstrap:main for wheel install (nesquena#6742)

* docs(changelog): note nesquena#6899 profile goal isolation + nesquena#6742 hermes-webui entry point

* test(goals): guard hermes_cli import with importorskip for CI (agent not installed)

nesquena#6899's native-contract tests imported hermes_cli unconditionally, failing
CI with ModuleNotFoundError. Match the repo's established importorskip pattern
so they skip cleanly when the agent isn't installed and run when it is.
Co-authored-by: ticketclosed-wontfix

---------

Co-authored-by: Nick <202622897+ticketclosed-wontfix@users.noreply.github.com>
Co-authored-by: webtecnica <webtecnica@gmail.com>
Co-authored-by: n <a@n>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L Large PR (>10 files or >250 LOC)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants