Skip to content

refactor(T1): persona default-flip for devagentic-local (refs #143)#146

Merged
PowerCreek merged 1 commit into
mainfrom
t1-persona-default-flip-143
May 28, 2026
Merged

refactor(T1): persona default-flip for devagentic-local (refs #143)#146
PowerCreek merged 1 commit into
mainfrom
t1-persona-default-flip-143

Conversation

@PowerCreek

Copy link
Copy Markdown

T1 of #143 thin-client refactor. HERMES_DEFER_PERSONA becomes tri-state:

  • Truthy (1/true/yes/on) → defer (unchanged)
  • Falsy (0/false/no/off) → opt-out, keep baked persona (NEW)
  • Unset/empty → provider-aware: devagentic-local defers, others don't (default-flip)
  • Unknown → falls back to provider default, doctor warns

Non-devagentic-local sessions keep legacy persona unchanged. Operator can opt out of the default-flip with HERMES_DEFER_PERSONA=0. 8 new T1 tests + updated doctor probe tests for the tri-state + 125 total green. Composition + preserved-items list per #143.

T1 of the #143 thin-client refactor scope. Changes ``HERMES_DEFER_PERSONA``
from a binary on/off toggle to a tri-state with provider-aware
default:

| Env value | Behavior |
|---|---|
| Truthy (1/true/yes/on) | Defer (same as pre-T1) |
| Falsy (0/false/no/off) | Opt-out — keep baked hermes persona (NEW) |
| Unset/empty | Provider-aware: devagentic-local defers; others don't (T1 default-flip) |
| Unknown value | Falls back to provider default (doctor warns on typo) |

## Rationale

Per #143: when devagentic owns the model role + R5 workflow-preamble,
hermes-side "Hermes Agent by Nous Research" identity is a competing
voice. Devagentic-emitted system prompts should be authoritative
for devagentic-local sessions. Non-devagentic-local sessions
(OpenRouter, Anthropic, etc.) keep the legacy persona — they
don't have an upstream preamble source to defer to.

## Implementation

- ``agent/system_prompt.py::_resolve_persona_deferred(agent=None)``
  now accepts optional agent, reads provider from
  ``getattr(agent, "provider", "")``.
- New constant ``_DEFER_PERSONA_FALSY = {"0", "false", "no", "off"}``
  enables explicit opt-out (previously fell through to default-false).
- Call site in ``build_system_prompt_parts`` threads ``agent`` to
  the resolver.
- Backward compat: ``_resolve_persona_deferred()`` (no args) still
  works — returns False on unset (no provider context).

## Doctor probe

Updated ``_check_persona_deferred_env`` to tri-state:
- Truthy → ``check_ok`` (defer note)
- Falsy → ``check_info`` (explicit opt-out, legacy behavior)
- Unknown → ``check_warn`` (typo; full truthy+falsy legend in detail)

## Tests

- 8 new T1 tests in
  ``tests/agent/test_system_prompt_persona_deferred.py``:
  default-flip on devagentic-local; other providers unchanged;
  explicit-falsy overrides default-flip (4 parametrized);
  explicit-truthy unchanged on other providers; unknown env falls
  back to provider default; legacy no-agent-arg unchanged; e2e
  SOUL.md drop on devagentic-local; e2e SOUL.md kept on
  openrouter.
- Updated doctor probe tests:
  ``test_falsy_emits_info_with_opt_out_note`` (4 parametrized
  falsy values) and ``test_unknown_value_emits_warn_with_full_legend``
  (3 parametrized unknown values) replace the prior
  ``test_non_truthy_emits_warn_with_valid_sample``.
- 125 total green across affected suites (persona + intent + doctor
  probes + T3 + diag-env-gate).

## Composition

Per #143 sequencing (T3 → T1 → T2-gated → T2-default-flip):
- T3 / PR #145 (clarify default-out) merged ✓
- **This PR: T1 (persona default-flip)**
- Next: T2 (env-gated empty-content recovery removal)
- Then: T2 default-flip
- Later: T4-T6
@PowerCreek PowerCreek merged commit 6d33297 into main May 28, 2026
@PowerCreek PowerCreek deleted the t1-persona-default-flip-143 branch May 28, 2026 00:57
PowerCreek added a commit that referenced this pull request May 28, 2026
…MPTY_RECOVERY (refs #143) (#147)

T2 of the #143 thin-client refactor scope. Devagentic-side cascade
(NousResearch#324) + runaway detector (NousResearch#345-348) + exec-terminus (NousResearch#349-354)
now cover the empty-content recovery layer with full intent /
role / dispatch-trace context. Hermes-side recovery layered on top
caused 3×4 dispatch stacking (#118) + mode confusion + invisible
swallow points (#133 debug funnel).

This patch default-flips: the four hermes-side recovery paths are
short-circuited unless ``HERMES_LEGACY_EMPTY_RECOVERY`` is set to
a truthy value. Legacy users opt-in to keep the pre-T2 behavior.

## Gated paths (all skipped when env unset)

| Path | Source | Loc |
|---|---|---|
| ``_finish_wants_tools`` synthetic recovery | PR #108 / #99 | line ~3618 |
| ``_post_tool_empty_retried`` nudge | pre-existing NousResearch#9400-class | line ~3749 |
| ``_structural_empty`` synthetic recovery | PR #69 / #67 | line ~3880 |
| 3-retry empty-content loop | pre-#67 | line ~3927 |

When env unset (default), empty responses fall through to either
the fallback-chain provider switch (if configured) or the clean
``(empty)`` terminal with ``_empty_terminal_sentinel=True``.

## Preserved through T2 (verified by test_t2_legacy_empty_recovery_gate.py)

- **PR #119 cascade_exhausted short-circuit** — hermes correctly
  deferring to devagentic's sentinel; NOT recovery. Source-level
  test asserts the ``if _cascade_err:`` block is NOT prefixed by
  ``_legacy_recovery_on``.
- **PR #122/#125 raw tool_calls fallback** — pre-recovery wire
  parsing in transports/chat_completions.py. Source-level test
  asserts the helper name doesn't appear in conversation_loop
  (lives elsewhere; untouched).
- **PR #131/#136/#138/#141 diagnostics** — env-gated via
  ``HERMES_DIAG_RAW_CAPTURE`` (#140), independent of this env.

## Operator deploy

Default behavior changes: empty responses surface cleanly (no
synthetic re-prompt). To preserve pre-T2 behavior:

```bash
export HERMES_LEGACY_EMPTY_RECOVERY=1
```

The legacy escape hatch is intended as a temporary safety net
while operators validate the thin-client architecture. Once the
devagentic-side cascade is universally deployed + observed to
cover all empty-content cases, the legacy gate can be removed in
a follow-up (the env var stays as a no-op for backward compat).

## Tests

- 23 new tests in
  ``tests/agent/test_t2_legacy_empty_recovery_gate.py``:
  resolver default-false / empty-false / 6 truthy / 6 falsy-or-
  unknown; source-level gate assertions for each of the four
  recovery branches; preserved-path assertions for cascade_exhausted
  + raw tool_calls fallback location; default-off + opt-in
  resolver round-trip.
- 163 total green across affected suites (T2 + existing empty-
  terminal mirror tests + finish_reason_tools_recovery +
  cascade_exhausted + internal_marker_stripping +
  tool_call_type_default + tool_use_enforcement + T3 + T1 persona
  + doctor persona probe).

## Composition

Per #143 sequencing:
- T3 / PR #145 (clarify default-out) — merged ✓
- T1 / PR #146 (persona default-flip) — merged ✓
- **T2 / this PR (empty-content recovery removal, env-gated)**
- Later: T4-T6 (tool list / iteration cap / summary fallback)

The legacy escape hatch design choice (vs. full removal) lets
operators roll back per-deployment if a previously-recovered edge
case surfaces in the field. Diagnostic from #140
(HERMES_DIAG_RAW_CAPTURE) remains the observability tool to spot
any uncovered case.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant