Skip to content

Jai/conv - #65183

Closed
J-SUPHA wants to merge 3 commits into
mainfrom
jai/conv
Closed

Jai/conv#65183
J-SUPHA wants to merge 3 commits into
mainfrom
jai/conv

Conversation

@J-SUPHA

@J-SUPHA J-SUPHA commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds a per-session conversation=<session_id> tag to Nous Portal inference
requests so Portal-side usage can be attributed to a specific Hermes session.

Every Portal request already carries two product-attribution tags
(product=hermes-agent, client=hermes-client-v<version>) built centrally in
agent/portal_tags.py. The Nous provider profile already receives the agent's
session_id but discarded it. This PR threads that id into the tag list as an
additive, opt-in third tag, emitted only when a session id is present.

The approach keeps the single-source-of-truth pattern in agent/portal_tags.py
(no inlined literals) and is fully backward compatible: nous_portal_tags()
with no argument still returns the canonical two-tag list, so auxiliary/base
call sites are unchanged.

Related Issue

N/A (no tracking issue). Happy to open one if maintainers prefer.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • agent/portal_tags.py
    • Added conversation_tag(session_id: str) -> str returning
      conversation=<session_id> (mirrors the existing hermes_client_tag()
      factoring; documents that this tag is high-cardinality and opt-in).
    • Extended nous_portal_tags(session_id: str | None = None) to append the
      conversation tag only when a truthy session_id is provided. Default
      behavior (no arg) is unchanged → still
      ["product=hermes-agent", "client=hermes-client-v<version>"].
  • plugins/model-providers/nous/__init__.py
    • NousProfile.build_extra_body() now forwards its session_id kwarg into
      the tag list (alongside the existing provider_preferences handling).
  • tests/agent/test_portal_tags.py
    • Added: conversation-tag format, presence when a session id is supplied,
      and omission when it is None/"".
  • tests/providers/test_provider_profiles.py
    • Added TestNousProfile.test_tags_include_conversation_when_session_id.
  • tests/run_agent/test_provider_parity.py
    • Updated the end-to-end _build_api_kwargs Nous parity assertion to expect
      the conversation tag when the agent has a session_id.

Scope / known limitations (intentional)

  • The tag rides the main agent loop only. Auxiliary/background Portal calls
    (compression, title generation, vision, web-extract, session search, kanban,
    curator, etc.) build tags without a session id and remain untagged, so
    per-conversation cost analytics built on this tag will undercount.
  • session_id rotates on context compression and /new, so a single
    user-facing conversation can emit multiple conversation= values. The value
    is the current session segment, not a stable end-to-end conversation id.
  • Only the opaque session_id (<timestamp>_<uuid>) is sent — never the
    gateway session key, which can embed PII (phone numbers / user ids).

How to Test

  1. Point Hermes at the Nous Portal (hermes model → provider nous, or set
    model.provider: nous in ~/.hermes/config.yaml).

  2. Confirm the profile emits the tag when a session id is present:

python -c "
from providers import get_provider_profile
p = get_provider_profile('nous')
print('no session :', p.build_extra_body())
print('w/ session :', p.build_extra_body(session_id='conv-abc-123'))
"

Expected:

no session : {'tags': ['product=hermes-agent', 'client=hermes-client-v<ver>']}
w/ session : {'tags': ['product=hermes-agent', 'client=hermes-client-v<ver>', 'conversation=conv-abc-123']}
  1. Run the targeted tests:
pytest tests/agent/test_portal_tags.py \
       tests/providers/test_provider_profiles.py \
       tests/run_agent/test_provider_parity.py::TestBuildApiKwargsNousPortal -q

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS (Apple Silicon), Python 3.13

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — docstrings in agent/portal_tags.py updated; no user-facing docs needed
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A (no new config keys)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) — N/A (pure Python, no OS-specific code)
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Screenshots / Logs

$ pytest tests/agent/test_portal_tags.py tests/providers/test_provider_profiles.py \
         tests/run_agent/test_provider_parity.py::TestBuildApiKwargsNousPortal -q
.....................................................................    [100%]
69 passed in 1.14s

J-SUPHA added 2 commits July 15, 2026 16:06
The end-to-end _build_api_kwargs parity test asserted the Nous Portal
tags exactly equal the base two-tag list. With the per-session
conversation tag, a real agent (which has a session_id) now emits a
third `conversation=<session_id>` tag. Assert against
nous_portal_tags(session_id=agent.session_id) so the check stays exact.
@J-SUPHA
J-SUPHA requested a review from teknium1 July 15, 2026 20:18
@alt-glitch alt-glitch added type/feature New feature or request comp/portal Nous portal / Hermes Pro / hosted-Hermes path provider/nous Nous Research API (OAuth) telemetry Touches outbound telemetry, usage attribution, or analytics — needs opt-in gating before merge P3 Low — cosmetic, nice to have labels Jul 15, 2026
Update max-iteration summary assertions to include the agent session ID now attached to Nous Portal requests.
teknium1 added a commit that referenced this pull request Jul 16, 2026
… calls

Extends the conversation=<id> Portal tag (salvaged from PR #65183 by
@J-SUPHA) from main-loop-only to every LLM call in a conversation:

- agent/portal_tags.py: ContextVar-based conversation context.
  nous_portal_tags() falls back to the ambient id when no explicit
  session_id is passed, so every aux tag site (auxiliary_client,
  chat_completion_helpers summary path, web_tools) inherits the tag
  with zero per-call-site plumbing. Ambient id wins over explicit
  per-segment ids since it carries the lineage root.
- hermes_state.py: SessionDB.get_conversation_root() — public wrapper
  over the lineage walk; returns the ROOT session id, so one
  user-facing conversation keeps a single conversation= value across
  context-compression rotation, and delegate subagent trees tag as
  their parent conversation.
- run_agent.py: run_conversation() publishes the root id for the turn
  and resets it in finally. _conversation_root_id() resolves via
  _parent_session_id for subagents.
- agent/moa_loop.py: MoA reference fan-out workers now run under
  propagate_context_to_thread so advisor slots attribute to the acting
  conversation (also fixes approval-callback propagation on that path).
- agent/title_generator.py: bare title thread republishes the context
  from its session id (spawned after turn reset).

Tests: ContextVar semantics, cross-context isolation, thread-hop
propagation, lineage-root resolution incl. cycle guard.
@teknium1

Copy link
Copy Markdown
Contributor

Merged via PR #65468 with your commits cherry-picked onto current main — your authorship is preserved in git history. Thanks for the clean, well-tested contribution!

We extended your work in the same PR to cover the known limitations you called out in the description: a ContextVar-based ambient conversation context now propagates the conversation= tag to auxiliary calls (compression, titles, vision, MoA advisor slots), and the tag value is the session-lineage ROOT id, so one conversation keeps a single stable value across context-compression rotation. Delegate subagent trees attribute to their parent conversation as well.

@teknium1 teknium1 closed this Jul 16, 2026
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
… calls

Extends the conversation=<id> Portal tag (salvaged from PR NousResearch#65183 by
@J-SUPHA) from main-loop-only to every LLM call in a conversation:

- agent/portal_tags.py: ContextVar-based conversation context.
  nous_portal_tags() falls back to the ambient id when no explicit
  session_id is passed, so every aux tag site (auxiliary_client,
  chat_completion_helpers summary path, web_tools) inherits the tag
  with zero per-call-site plumbing. Ambient id wins over explicit
  per-segment ids since it carries the lineage root.
- hermes_state.py: SessionDB.get_conversation_root() — public wrapper
  over the lineage walk; returns the ROOT session id, so one
  user-facing conversation keeps a single conversation= value across
  context-compression rotation, and delegate subagent trees tag as
  their parent conversation.
- run_agent.py: run_conversation() publishes the root id for the turn
  and resets it in finally. _conversation_root_id() resolves via
  _parent_session_id for subagents.
- agent/moa_loop.py: MoA reference fan-out workers now run under
  propagate_context_to_thread so advisor slots attribute to the acting
  conversation (also fixes approval-callback propagation on that path).
- agent/title_generator.py: bare title thread republishes the context
  from its session id (spawned after turn reset).

Tests: ContextVar semantics, cross-context isolation, thread-hop
propagation, lineage-root resolution incl. cycle guard.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
… calls

Extends the conversation=<id> Portal tag (salvaged from PR NousResearch#65183 by
@J-SUPHA) from main-loop-only to every LLM call in a conversation:

- agent/portal_tags.py: ContextVar-based conversation context.
  nous_portal_tags() falls back to the ambient id when no explicit
  session_id is passed, so every aux tag site (auxiliary_client,
  chat_completion_helpers summary path, web_tools) inherits the tag
  with zero per-call-site plumbing. Ambient id wins over explicit
  per-segment ids since it carries the lineage root.
- hermes_state.py: SessionDB.get_conversation_root() — public wrapper
  over the lineage walk; returns the ROOT session id, so one
  user-facing conversation keeps a single conversation= value across
  context-compression rotation, and delegate subagent trees tag as
  their parent conversation.
- run_agent.py: run_conversation() publishes the root id for the turn
  and resets it in finally. _conversation_root_id() resolves via
  _parent_session_id for subagents.
- agent/moa_loop.py: MoA reference fan-out workers now run under
  propagate_context_to_thread so advisor slots attribute to the acting
  conversation (also fixes approval-callback propagation on that path).
- agent/title_generator.py: bare title thread republishes the context
  from its session id (spawned after turn reset).

Tests: ContextVar semantics, cross-context isolation, thread-hop
propagation, lineage-root resolution incl. cycle guard.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/portal Nous portal / Hermes Pro / hosted-Hermes path P3 Low — cosmetic, nice to have provider/nous Nous Research API (OAuth) telemetry Touches outbound telemetry, usage attribution, or analytics — needs opt-in gating before merge type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants