You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
AI code review — automated review for reference; please use your judgment.
Two unrelated changes in one PR: (a) forwarded/cross-profile tasks run off the HTTP worker and message/send returns WORKING immediately (plugins/platforms/a2a/adapter.py — new _forward_in_background at :637, the async _rpc_message_send path at :704-726, reworked forwarded branch at :566-586); (b) a messageId idempotency lookup so a retried message returns the existing task instead of dispatching twice (adapter.py:535-546, protocol.py:185-193 and get_by_message_id at :382). The TaskStore/set_state guard (rec["state"] not in TERMINAL_STATES, protocol.py:333) and _finalize_task's finally: self._pop_pending make the async plumbing consistent, and the state machine transitions themselves look right. Two issues, both about the change being only half-applied.
message/send is now async for every agent, but this repo's own outbound client cannot consume that — adapter.py:704-726 returns STATE_WORKING with an empty reply as soon as the task is scheduled, for the local path as well as the forwarded one (main blocked in _await_reply and returned the finished Task). The Hermes A2A client in the same plugin reads the answer straight out of the message/send response and never polls tasks/get: tools.py:121-132 (_reply_text_from_result walks artifacts then status.message), used by tools.py:180-190 (a2a_call) and tools.py:242-249 (_call_peer_sync). Against a peer running this build, a2a_call now answers [peer · context X · working] + (no text reply), and a2a_orchestrate's first/best modes treat that empty body as a success (tools.py:282-287 only reject replies starting with Error: and otherwise rank by length). The PR updated the server-side tests to poll (tests/plugins/test_a2a_phase23.py:532, tests/plugins/test_a2a_plugin.py:1406) but no client-side test covers the pairing. Either teach _send_task to poll tasks/get on a non-terminal state (bounded by the peer timeout, then report the task id), or scope the async return to the forwarded branch and keep the blocking reply for agent.get("local", True). As it stands the change breaks Hermes-to-Hermes delegation for every agent, not just the forwarded ones it was written for. (blocker)
A2A_PEER_TOKENS separator flipped from : to = with no migration and stale docs — plugins/platforms/a2a/security.py:35 now splits on =, so an alice:tok1 value — the format every other surface still produces and documents — no longer yields any pair (":" never satisfies if "=" in pair). _parse_peer_tokens then returns {}, authenticate() finds no peer match and returns None (401 for every peer), and with no token of any kind the server refuses to widen off loopback (security.py:81). Still advertising the old format after this PR: plugins/platforms/a2a/plugin.yaml:43-45 ("'alice:tok1,bob:tok2'", prompt "name:token, comma-separated"), the interactive setup wizard at plugins/platforms/a2a/init.py:65-69 (writes A2A_PEER_TOKENS in name:token form), README.md:60 and :76, DESIGN.md:99, and get_peer_tokens' own docstring at security.py:236. Accept both separators for a deprecation window (e.g. split on = when present, else on :), or update the wizard, plugin.yaml prompt, both docs and the docstring in the same PR — otherwise every existing deployment silently drops peer identity on upgrade. (blocker)
Minor: the idempotency check is a check-then-act across two calls — get_by_message_id at adapter.py:540 and tasks.create at :548 both take self.tasks._lock separately, so two concurrent retries carrying the same messageId can both miss and create two tasks; a create-time uniqueness check inside the store lock (or setdefault on a message_id index) would close it. Minor: the idempotent return path skips security.audit("inbound", ...) and _register_inline_push (adapter.py:546-559), so a retried message leaves no audit line and cannot re-register a push config.
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
area/configConfig system, migrations, profilescomp/pluginsPlugin system and bundled pluginsP3Low — cosmetic, nice to havesweeper:risk-compatibilitySweeper risk: may break existing users, config, migrations, defaults, or upgradessweeper:risk-security-boundarySweeper risk: may affect sandboxing, auth, credentials, or sensitive datatype/bugSomething isn't working
3 participants
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.
What does this PR do?
Related Issue
Fixes #
Type of Change
Changes Made
How to Test
Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AFor New Skills
hermes --toolsets skills -q "Use the X skill to do Y"Screenshots / Logs