test: add unit tests for run_agent.py (AIAgent) - #67
Merged
Conversation
71 tests covering pure functions, state/structure methods, and conversation loop pieces. OpenAI client and tool loading are mocked.
angelburgosrosado
pushed a commit
to angelburgosrosado/hermes-agent
that referenced
this pull request
Apr 27, 2026
…t-tests test: add unit tests for run_agent.py (AIAgent)
3 tasks
benbarclay
added a commit
that referenced
this pull request
Jun 25, 2026
A DM reply carries no guild_id, so the connector's egress guard cannot resolve the owning tenant from metadata.guild_id and declines the send with "discord egress declined: target not routed to an onboarded tenant" — the bug behind "the bot never replies in DMs". Guild replies are unaffected (they carry guild_id), which is why the guild path worked end-to-end while DMs looked broken. The connector now resolves a DM reply's tenant from the recipient's author binding (gateway-gateway #67, resolveByUser keyed on metadata.user_id) — the outbound counterpart to inbound Phase 7a author-first resolution. But it needs the recipient user_id ON the outbound action, and the adapter only re-attached guild_id (_capture_scope/_with_scope), no-op for DMs (the docstring even said so). This extends the adapter's inbound-scope capture: for a DM (no guild_id) remember chat_id -> the authentic author user_id we observed, and re-attach it as metadata.user_id on outbound. Guild capture is unchanged and wins when present; user_id is the DM-only fallback. The id is the one the connector observed inbound (never gateway-asserted), so the trust invariant holds. +4 unit tests (DM reply re-attaches user_id + no guild_id; unknown chat invents nothing; explicit user_id preserved; guild reply never carries user_id). Proved load-bearing (reverting the re-attach fails the DM test). 144 relay tests pass, ruff clean. Pairs with gateway-gateway #67 (the connector-side resolver). Together they close the DM-reply egress gap end-to-end.
pai-scaffolde
referenced
this pull request
in pai-scaffolde/hermes-agent
Jun 28, 2026
A DM reply carries no guild_id, so the connector's egress guard cannot resolve the owning tenant from metadata.guild_id and declines the send with "discord egress declined: target not routed to an onboarded tenant" — the bug behind "the bot never replies in DMs". Guild replies are unaffected (they carry guild_id), which is why the guild path worked end-to-end while DMs looked broken. The connector now resolves a DM reply's tenant from the recipient's author binding (gateway-gateway #67, resolveByUser keyed on metadata.user_id) — the outbound counterpart to inbound Phase 7a author-first resolution. But it needs the recipient user_id ON the outbound action, and the adapter only re-attached guild_id (_capture_scope/_with_scope), no-op for DMs (the docstring even said so). This extends the adapter's inbound-scope capture: for a DM (no guild_id) remember chat_id -> the authentic author user_id we observed, and re-attach it as metadata.user_id on outbound. Guild capture is unchanged and wins when present; user_id is the DM-only fallback. The id is the one the connector observed inbound (never gateway-asserted), so the trust invariant holds. +4 unit tests (DM reply re-attaches user_id + no guild_id; unknown chat invents nothing; explicit user_id preserved; guild reply never carries user_id). Proved load-bearing (reverting the re-attach fails the DM test). 144 relay tests pass, ruff clean. Pairs with gateway-gateway #67 (the connector-side resolver). Together they close the DM-reply egress gap end-to-end.
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
…t-tests test: add unit tests for run_agent.py (AIAgent)
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
A DM reply carries no guild_id, so the connector's egress guard cannot resolve the owning tenant from metadata.guild_id and declines the send with "discord egress declined: target not routed to an onboarded tenant" — the bug behind "the bot never replies in DMs". Guild replies are unaffected (they carry guild_id), which is why the guild path worked end-to-end while DMs looked broken. The connector now resolves a DM reply's tenant from the recipient's author binding (gateway-gateway NousResearch#67, resolveByUser keyed on metadata.user_id) — the outbound counterpart to inbound Phase 7a author-first resolution. But it needs the recipient user_id ON the outbound action, and the adapter only re-attached guild_id (_capture_scope/_with_scope), no-op for DMs (the docstring even said so). This extends the adapter's inbound-scope capture: for a DM (no guild_id) remember chat_id -> the authentic author user_id we observed, and re-attach it as metadata.user_id on outbound. Guild capture is unchanged and wins when present; user_id is the DM-only fallback. The id is the one the connector observed inbound (never gateway-asserted), so the trust invariant holds. +4 unit tests (DM reply re-attaches user_id + no guild_id; unknown chat invents nothing; explicit user_id preserved; guild reply never carries user_id). Proved load-bearing (reverting the re-attach fails the DM test). 144 relay tests pass, ruff clean. Pairs with gateway-gateway NousResearch#67 (the connector-side resolver). Together they close the DM-reply egress gap end-to-end.
habarmc1223-sudo
pushed a commit
to habarmc1223-sudo/hermes-agent-fluxmem
that referenced
this pull request
Jul 8, 2026
A DM reply carries no guild_id, so the connector's egress guard cannot resolve the owning tenant from metadata.guild_id and declines the send with "discord egress declined: target not routed to an onboarded tenant" — the bug behind "the bot never replies in DMs". Guild replies are unaffected (they carry guild_id), which is why the guild path worked end-to-end while DMs looked broken. The connector now resolves a DM reply's tenant from the recipient's author binding (gateway-gateway NousResearch#67, resolveByUser keyed on metadata.user_id) — the outbound counterpart to inbound Phase 7a author-first resolution. But it needs the recipient user_id ON the outbound action, and the adapter only re-attached guild_id (_capture_scope/_with_scope), no-op for DMs (the docstring even said so). This extends the adapter's inbound-scope capture: for a DM (no guild_id) remember chat_id -> the authentic author user_id we observed, and re-attach it as metadata.user_id on outbound. Guild capture is unchanged and wins when present; user_id is the DM-only fallback. The id is the one the connector observed inbound (never gateway-asserted), so the trust invariant holds. +4 unit tests (DM reply re-attaches user_id + no guild_id; unknown chat invents nothing; explicit user_id preserved; guild reply never carries user_id). Proved load-bearing (reverting the re-attach fails the DM test). 144 relay tests pass, ruff clean. Pairs with gateway-gateway NousResearch#67 (the connector-side resolver). Together they close the DM-reply egress gap end-to-end.
santhreal
pushed a commit
to santhreal/hermes-agent
that referenced
this pull request
Jul 13, 2026
A DM reply carries no guild_id, so the connector's egress guard cannot resolve the owning tenant from metadata.guild_id and declines the send with "discord egress declined: target not routed to an onboarded tenant" — the bug behind "the bot never replies in DMs". Guild replies are unaffected (they carry guild_id), which is why the guild path worked end-to-end while DMs looked broken. The connector now resolves a DM reply's tenant from the recipient's author binding (gateway-gateway NousResearch#67, resolveByUser keyed on metadata.user_id) — the outbound counterpart to inbound Phase 7a author-first resolution. But it needs the recipient user_id ON the outbound action, and the adapter only re-attached guild_id (_capture_scope/_with_scope), no-op for DMs (the docstring even said so). This extends the adapter's inbound-scope capture: for a DM (no guild_id) remember chat_id -> the authentic author user_id we observed, and re-attach it as metadata.user_id on outbound. Guild capture is unchanged and wins when present; user_id is the DM-only fallback. The id is the one the connector observed inbound (never gateway-asserted), so the trust invariant holds. +4 unit tests (DM reply re-attaches user_id + no guild_id; unknown chat invents nothing; explicit user_id preserved; guild reply never carries user_id). Proved load-bearing (reverting the re-attach fails the DM test). 144 relay tests pass, ruff clean. Pairs with gateway-gateway NousResearch#67 (the connector-side resolver). Together they close the DM-reply egress gap end-to-end.
israellot
added a commit
to YallaPlay/hermes-agent
that referenced
this pull request
Jul 20, 2026
Adopt upstream's fix (c9c9bb3): the fallback test must also patch hermes_cli.model_catalog.get_curated_openrouter_models, otherwise the assertion depends on whatever the deployed remote manifest currently contains — which drifted and broke CI on main (runs NousResearch#66/NousResearch#67).
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
A DM reply carries no guild_id, so the connector's egress guard cannot resolve the owning tenant from metadata.guild_id and declines the send with "discord egress declined: target not routed to an onboarded tenant" — the bug behind "the bot never replies in DMs". Guild replies are unaffected (they carry guild_id), which is why the guild path worked end-to-end while DMs looked broken. The connector now resolves a DM reply's tenant from the recipient's author binding (gateway-gateway NousResearch#67, resolveByUser keyed on metadata.user_id) — the outbound counterpart to inbound Phase 7a author-first resolution. But it needs the recipient user_id ON the outbound action, and the adapter only re-attached guild_id (_capture_scope/_with_scope), no-op for DMs (the docstring even said so). This extends the adapter's inbound-scope capture: for a DM (no guild_id) remember chat_id -> the authentic author user_id we observed, and re-attach it as metadata.user_id on outbound. Guild capture is unchanged and wins when present; user_id is the DM-only fallback. The id is the one the connector observed inbound (never gateway-asserted), so the trust invariant holds. +4 unit tests (DM reply re-attaches user_id + no guild_id; unknown chat invents nothing; explicit user_id preserved; guild reply never carries user_id). Proved load-bearing (reverting the re-attach fails the DM test). 144 relay tests pass, ruff clean. Pairs with gateway-gateway NousResearch#67 (the connector-side resolver). Together they close the DM-reply egress gap end-to-end.
Meraniya
pushed a commit
to Meraniya/hermes-agent
that referenced
this pull request
Aug 6, 2026
…nting (NousResearch#67) * feat(devops): add config-integrity-watchdog skill with git-backed fingerprinting Replaces mutable .sha256 sidecar with an append-only integrity log committed to the dotfiles git repo. A process without git commit credentials cannot silently forge a fingerprint entry. - seal.py: hash config + append to log + git commit - verify.py: compare current hash against latest seal; detect log tampering - restore.py: revert from git + re-seal; backs up tampered config - PLAN.md: project plan (Linear not available) - Tests: seal->verify, seal->tamper->verify, seal->tamper->restore->verify (29 passing) Closes the 19-day recurring config hijack pattern (Event NousResearch#25+). Slack: https://mfc-nyc.slack.com/archives/C0BD8QBUSJF/p1782742870774319 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PQKCc5mDedYAiCNyXnTezh * fix(tests): add type ignore for pytest import and None guard for regex match Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PQKCc5mDedYAiCNyXnTezh * feat(cli): add hermes config seal/verify/restore commands Integrates config-integrity-watchdog into the Hermes CLI so users can seal, verify, and restore config integrity without remembering script paths. Calls core logic shared with the standalone scripts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PQKCc5mDedYAiCNyXnTezh * fix(cli): fallback import path, move PLAN.md to .plans/, add restore caveat - _import_core() now tries ~/.hermes/skills first, then repo-relative skills/ as fallback for pre-sync usage; prints clear error + exits 1 if skill not found in either location - Move PLAN.md to .plans/config-integrity-watchdog.md - Append git-HEAD caveat to restore.py module docstring Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PQKCc5mDedYAiCNyXnTezh --------- Co-authored-by: Claude <noreply@anthropic.com>
Meraniya
pushed a commit
to Meraniya/hermes-agent
that referenced
this pull request
Aug 6, 2026
…, NousResearch#69) and mark arc complete (NousResearch#76) Session close-out audit found the Outcome section only mentioned PR NousResearch#67. PR NousResearch#69 explicitly self-describes as "follow-up to NousResearch#67" (unsigned-commit git fallback) and PR NousResearch#68 (AGENTS.md docs) is also a direct follow-up; neither was recorded. Also clarifies that NousResearch#71/NousResearch#72 (cron ticker heartbeat/stall fix) are an unrelated arc shipped the same day, not part of this project. Claude-Session: https://claude.ai/code/session_01PQKCc5mDedYAiCNyXnTezh Co-authored-by: Claude <noreply@anthropic.com>
Meraniya
pushed a commit
to Meraniya/hermes-agent
that referenced
this pull request
Aug 6, 2026
…writes (NousResearch#82) save_config()/restore_config() already keep the local .sha256 sidecar in sync (PR NousResearch#57), but the *external* git-backed baseline used by `hermes config verify` (PR NousResearch#67) was only ever updated by an explicit `hermes config seal`. Any authorized write through save_config() — model scanner, /model command, platform setup flows via write_platform_config_field() — desynced that baseline, so the Config Integrity Watchdog cron job flagged the legitimate change as tampering: "Hermes Config Integrity Failure! The configuration hash does not match the sealed baseline." This is the same scanner/watchdog TOCTOU conflict documented in docs/plans/2026-07-02-scanner-watchdog-conflict-resolution.md, just recurring one layer over in the newer git-backed mechanism. _write_config_to_disk() now also calls the config-integrity-watchdog skill's seal() (quietly) whenever $HERMES_DOTFILES_DIR is configured, keeping the git-backed log current on every authorized write. Extracted _find_core_module() (returns None instead of exiting) so this can opportunistically no-op on machines without the watchdog set up. restore_quick_snapshot() in backup.py gets the same treatment for config.yaml restores from a quick snapshot. Co-authored-by: Test <test@test.com>
leewenjie
pushed a commit
to leewenjie/hermes-agent
that referenced
this pull request
Aug 7, 2026
A DM reply carries no guild_id, so the connector's egress guard cannot resolve the owning tenant from metadata.guild_id and declines the send with "discord egress declined: target not routed to an onboarded tenant" — the bug behind "the bot never replies in DMs". Guild replies are unaffected (they carry guild_id), which is why the guild path worked end-to-end while DMs looked broken. The connector now resolves a DM reply's tenant from the recipient's author binding (gateway-gateway NousResearch#67, resolveByUser keyed on metadata.user_id) — the outbound counterpart to inbound Phase 7a author-first resolution. But it needs the recipient user_id ON the outbound action, and the adapter only re-attached guild_id (_capture_scope/_with_scope), no-op for DMs (the docstring even said so). This extends the adapter's inbound-scope capture: for a DM (no guild_id) remember chat_id -> the authentic author user_id we observed, and re-attach it as metadata.user_id on outbound. Guild capture is unchanged and wins when present; user_id is the DM-only fallback. The id is the one the connector observed inbound (never gateway-asserted), so the trust invariant holds. +4 unit tests (DM reply re-attaches user_id + no guild_id; unknown chat invents nothing; explicit user_id preserved; guild reply never carries user_id). Proved load-bearing (reverting the re-attach fails the DM test). 144 relay tests pass, ruff clean. Pairs with gateway-gateway NousResearch#67 (the connector-side resolver). Together they close the DM-reply egress gap end-to-end.
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
run_agent.py(AIAgentclass) covering 16 methods across 3 groups_has_content_after_think_block,_strip_think_blocks,_extract_reasoning,_clean_session_content,_get_messages_up_to_last_assistant,_mask_api_key_for_logs__init__, interrupt mechanism,_hydrate_todo_store,_build_system_prompt,_invalidate_system_prompt,_build_api_kwargs,_build_assistant_message,_format_tools_for_system_message_execute_tool_calls,_handle_max_iterations,run_conversation(stop, tool calls, interrupt, invalid tool retry, empty content fallback, context compression)Test plan
uv run pytest tests/test_run_agent.py -v— 71 passed in ~3suv run pytest -v— full suite 243 passed, no regressions