Skip to content

feat: add @handle-routed agent team (interactive agents + API bots) - #91

Merged
groupthinking merged 10 commits into
mainfrom
claude/myxstack-agent-merge-myaock
Jul 2, 2026
Merged

feat: add @handle-routed agent team (interactive agents + API bots)#91
groupthinking merged 10 commits into
mainfrom
claude/myxstack-agent-merge-myaock

Conversation

@groupthinking

Copy link
Copy Markdown
Owner

Summary

Mentions are now routed to a team of @handle-addressable members. Tag a member anywhere in a mention and the listener dispatches to it:

@MyXstack @Tradedesk $TSLA buy 100     → approval-gated trade proposal (paper broker)
@MyXstack @Research why is $NVDA down? → Grok research brief filed on the timeline
@MyXstack @Shopping shoes under $150   → product picks, purchase approval-gated
@MyXstack @TickerBot $BTC              → deterministic cashtag lookup (API bot)

Classification: interactive agents vs API bots

Every member carries a kind in its profile and in the A2A registry:

  • agent (interactive agent) — conversational, LLM-backed via Grok + MCP tools, can run sub-steps, delegate to other members over the A2A bus, and propose approval-gated actions.
  • bot (API bot) — deterministic function executor: input → function → output. No LLM, no autonomy. @TickerBot is the reference implementation.

How it works

  • agents/router.py matches registered @Handles in mention text (case-insensitive, word-bounded); untagged mentions fall back to a GeneralAgent that preserves the original generic Grok behavior.
  • listener.py routes each mention to a member, posts its reply, and pushes its timeline card (cards carry agent_id in metadata).
  • mcp_dispatcher.py now looks up the card's owning member on Approve/Reject and calls its execute_action() before falling back to the legacy generic Grok path.
  • a2a_store.py / timeline_server.py store the new kind classification, and all team members self-register in the A2A registry at listener startup.

Safety defaults

  • Trades are paper-only: PaperBroker records simulated fills in a local JSON ledger; no live orders. A real broker is a pluggable adapter behind the same execute() interface.
  • Purchases are intents: no payment executor ships in the repo.
  • Side effects are approval-gated: nothing executes until a human acts on the timeline card.

Testing

  • New pytest suite (18 tests): @handle routing, trade-command parsing, approval-card creation, paper-broker fills/positions, approve/reject execution paths.
  • make test target + requirements-dev.txt + pytest.ini added.
  • All modified services compile and import cleanly against requirements.txt.

Docs

  • docs/AGENTS.md: roster, agent-vs-bot classification, request flow diagram, how to add a member, X API Exhibit note.
  • README: new Agent Team section; env.example: configurable member handles and paper-ledger path.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FTLFzAjoSosiPfHGaXSZkP


Generated by Claude Code

Mentions are now routed to a team of addressable members by @handle:
- @tradedesk (agent): parses $TICKER buy/sell, logs approval-gated
  trade proposals, executes via paper broker on approval (no live orders)
- @research (agent): Grok + MCP research briefs filed to the timeline
- @shopping (agent): product picks with approval-gated purchase intents
- @tickerbot (bot): deterministic cashtag lookup, reference API bot

Members are classified kind=agent (interactive, LLM-backed, can delegate
over A2A) vs kind=bot (deterministic function executor); the A2A registry
now stores the classification. The dispatcher routes timeline approvals
back to the owning member before falling back to generic Grok execution.
Untagged mentions keep the original generic behavior via a fallback agent.

Includes pytest suite (routing, trade parsing, paper broker), make test
target, env.example entries, and docs/AGENTS.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTLFzAjoSosiPfHGaXSZkP
Copilot AI review requested due to automatic review settings July 1, 2026 23:08
@linear-code
linear-code Bot marked this pull request as ready for review July 1, 2026 23:08
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🔍 PR Validation

⚠️ PR title should follow conventional commits format
⚠️ Large PR detected (1030 lines changed)

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Jul 1, 2026
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 20762a0d-c96f-4661-a16c-3f1db75a59f2

📥 Commits

Reviewing files that changed from the base of the PR and between 411647f and 833f189.

📒 Files selected for processing (6)
  • agents/base.py
  • agents/team/general.py
  • agents/team/tradedesk.py
  • listener.py
  • mcp_dispatcher.py
  • tests/test_tradedesk.py
📝 Walkthrough

Walkthrough

Adds agent routing, built-in members, approval-gated trade execution, structured timeline actions, agent-kind support, and docs/tests/tooling for local runs.

Changes

Agent Team Framework

Layer / File(s) Summary
Shared contracts and routing
agents/base.py, agents/router.py, agents/registry.py, agents/__init__.py, agents/team/__init__.py, a2a_store.py, timeline_server.py
Defines the shared mention/reply/profile types, base member contract, Grok/A2A helpers, handle matching, team assembly, cached routing, member lookup, team registration, and kind persistence/schema support.
Paper broker and fills
agents/broker.py, tests/test_broker.py
Adds the JSON-ledger paper broker, atomic persistence, idempotent fills, position aggregation, and broker tests.
TradeDesk proposal and execution
agents/team/tradedesk.py, tests/test_tradedesk.py
Parses trade mentions, emits approval cards, and executes approve/reject actions against the broker with duplicate handling.
Other built-in members
agents/team/general.py, agents/team/research.py, agents/team/shopping.py, agents/team/tickerbot.py
Adds the fallback general agent, research agent, shopping agent, and deterministic cashtag bot.
Listener, dispatcher, and schema wiring
listener.py, mcp_dispatcher.py
Routes mentions through team members, posts timeline cards, executes structured timeline actions, and stops replayed actions from running twice.
Docs and dev tooling
docs/AGENTS.md, README.md, env.example, Makefile, pytest.ini, requirements-dev.txt
Documents the agent team, adds env defaults, and wires local test execution.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Listener
  participant route_mention
  participant TeamMember
  participant TimelineServer
  participant Dispatcher
  participant find_member
  participant PaperBroker

  Listener->>route_mention: MentionContext(text)
  route_mention->>TeamMember: select member
  TeamMember-->>Listener: AgentReply(text, card)
  Listener->>TimelineServer: push_timeline_card(card, posted_by)

  Dispatcher->>TimelineServer: get_timeline_item(item_id)
  Dispatcher->>find_member: agent_id
  find_member-->>Dispatcher: TeamMember
  Dispatcher->>TeamMember: execute_action(item, action)
  TeamMember->>PaperBroker: execute(...)
  TeamMember-->>Dispatcher: result
Loading

Poem

Handles in, and cards go out,
with retries, locks, and the usual doubt.
Duplicate fills still want a seat;
ordered events keep dodging defeat.
If the queue lies, everything slips.

🚥 Pre-merge checks | ✅ 6 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Enforce Copilot Verification ⚠️ Warning PR #91 has a Copilot review summary, but only COMMENTED/CHANGES_REQUESTED states were found; no explicit APPROVED Copilot review exists. Get an explicit APPROVED review from copilot-pull-request-reviewer[bot]; human approvals do not satisfy this check.
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed Title matches the main change: an @handle-routed agent team with interactive agents and API bots.
Description check ✅ Passed Description matches the changeset and covers routing, member kinds, safety defaults, and tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Require Ai Unit Tests ✅ Passed PR #91 has the copilot-rabbit label, and new unit tests were added in tests/test_broker.py, tests/test_router.py, and tests/test_tradedesk.py.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/myxstack-agent-merge-myaock
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/myxstack-agent-merge-myaock

Comment @coderabbitai help to get the list of available commands.

@groupthinking groupthinking changed the title Add @handle-routed agent team (interactive agents + API bots) feat: add @handle-routed agent team (interactive agents + API bots) Jul 1, 2026
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🔍 PR Validation

⚠️ Large PR detected (1030 lines changed)

Copilot AI left a comment

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.

Pull request overview

This PR introduces an @handle-routed “agent team” for the Python listener so mentions can be dispatched to specialized interactive agents (LLM-backed) or deterministic API bots, with approvals routed back to the owning member via timeline card metadata.

Changes:

  • Add a registry/router layer (agents/) and several built-in team members (TradeDesk, Research, Shopping, TickerBot, plus fallback GeneralAgent).
  • Update listener + dispatcher to route mentions and approval actions to the correct team member.
  • Add a new pytest suite + make test flow, plus docs/README/env updates describing the agent team.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
timeline_server.py Adds kind to A2A agent create payloads (classification: agent vs bot).
a2a_store.py Persists the new kind classification in the A2A registry store.
listener.py Routes inbound mentions to a team member and posts member-owned timeline cards.
mcp_dispatcher.py Routes Approve/Reject actions to the owning member’s execute_action() before falling back to Grok.
agents/init.py Defines agents package for team routing.
agents/base.py Introduces shared dataclasses (MentionContext, AgentReply, AgentProfile) and grok_chat().
agents/broker.py Adds PaperBroker to execute/record simulated fills in a local JSON ledger.
agents/router.py Implements word-bounded, case-insensitive @handle matching.
agents/registry.py Builds the team roster, provides routing helpers, and registers members into A2A on startup.
agents/team/init.py Declares built-in team members package.
agents/team/general.py Implements fallback “GeneralAgent” that preserves the legacy Grok behavior.
agents/team/tradedesk.py Implements approval-gated trade proposal parsing + execution via PaperBroker.
agents/team/research.py Implements research agent that replies briefly and files a full timeline brief.
agents/team/shopping.py Implements shopping agent that posts picks + approval-gated purchase intents.
agents/team/tickerbot.py Implements deterministic cashtag lookup bot (no LLM).
tests/test_router.py Adds routing tests (case-insensitive, word boundaries, fallback, bot kind).
tests/test_tradedesk.py Adds trade parsing + approval-card + approve/reject execution tests.
tests/test_broker.py Adds paper broker fill recording + position aggregation tests.
requirements-dev.txt Adds dev deps for tests (pytest).
pytest.ini Configures pytest discovery and pythonpath.
Makefile Adds make test target to install dev deps and run pytest.
README.md Documents the agent team and agent vs bot classification.
env.example Adds env vars for member handles and paper-ledger path.
docs/AGENTS.md Adds full agent-team documentation: roster, flow, and how to add members.

Comment thread agents/team/tradedesk.py Outdated
Comment on lines +105 to +108
summary = (
f"{str(metadata.get('side', '')).upper()} "
f"{metadata.get('quantity', 0):g} ${metadata.get('ticker', '?')}"
)
Comment thread mcp_dispatcher.py Outdated
Comment on lines +35 to +40
def get_timeline_item(item_id: str) -> Optional[Dict]:
timeline_url = os.getenv("TIMELINE_API_URL", "http://127.0.0.1:8080")
response = requests.get(f"{timeline_url}/v1/timeline/items/{item_id}", timeout=10)
if response.status_code != 200:
return None
return response.json()
Comment thread timeline_server.py Outdated
description: Optional[str] = ""
status: Optional[str] = "offline"
endpoint: Optional[str] = ""
kind: Optional[str] = "agent" # "agent" (interactive) or "bot" (deterministic)
Address Copilot review on #91:
- get_timeline_item swallows request/JSON errors so the dispatcher loop
  survives timeline-server hiccups and falls back to generic execution
- Tradedesk coerces ticker/side/quantity from card metadata before
  formatting or executing, rejecting non-numeric quantities safely
- timeline_server constrains A2A 'kind' to Literal['agent','bot']

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTLFzAjoSosiPfHGaXSZkP
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🔍 PR Validation

⚠️ Large PR detected (1038 lines changed)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
tests/test_router.py (1)

1-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No coverage for the multi-handle-in-one-mention case.

Every test here tags exactly one handle. Given the priority-ordering bug flagged in agents/router.py (find_target, lines 9-23), a test like route_mention(MentionContext(text="@research @tradedesk ...")) would have caught it immediately. Add it once that fix lands.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_router.py` around lines 1 - 45, Add coverage in test_route_to...
around route_mention and/or find_target for the multi-handle mention case, since
current tests only exercise one handle at a time. Create a test that passes a
MentionContext like "`@Research` `@Tradedesk` ..." and assert the router picks the
correct target according to the intended priority order in
agents.router.find_target. Use the existing symbols route_mention, find_target,
and build_team to keep the new test aligned with the current routing behavior.
tests/test_broker.py (1)

1-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tests only cover the happy path.

No test exercises a corrupted/truncated ledger file (silently returns [], losing history) or calling execute() twice for the same trade. Both are the exact failure modes flagged in agents/broker.py. Add one before this bites in prod.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_broker.py` around lines 1 - 19, The broker tests only cover normal
execution and miss the failure paths in PaperBroker. Add a test around
PaperBroker’s ledger-loading/restore behavior for a corrupted or truncated
trades file to verify it does not silently drop history, and add a test that
calls PaperBroker.execute() twice with the same trade to ensure duplicate
executions are handled as expected. Use the PaperBroker and execute identifiers
so the new coverage targets the exact regression points.
a2a_store.py (1)

84-102: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Duplicate IDs leave the A2A registry stale. register_agent() returns early on an existing id, so the POST path never updates the seeded x-agent record. Since DEFAULT_AGENTS is written directly into the store without kind, GET /v1/a2a/agents keeps returning that record without the classification field. Update mutable fields on match instead of no-oping, or stop re-registering the same id.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@a2a_store.py` around lines 84 - 102, register_agent() currently no-ops when
an existing agent id is found, which leaves seeded entries like x-agent stale
and missing updated fields such as kind. Update the existing record in the
data["agents"] list when the id matches instead of returning early, so mutable
fields from the incoming payload are merged into the stored agent before
_write_store(data) is called. Use register_agent and the
A2A_STORE_LOCK/_read_store/_write_store flow to locate the fix.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agents/base.py`:
- Around line 93-112: Wrap the requests.post call in send_a2a_message with
RequestException handling so timeline outages don’t bubble up to callers like
handle_mention; follow the same graceful-failure pattern used in register_team,
and make sure send_a2a_message logs or otherwise swallows the failure instead of
raising. If you choose to address auth instead, add the required authorization
header consistently in send_a2a_message and align it with the server-side
expectations for /v1/a2a/messages.
- Around line 71-90: grok_chat currently relies on the xai_sdk Client default
deadline, which can let a single xAI/MCP request stall the mention loop; update
grok_chat to enforce an explicit hard timeout around the chat request/stream
using the existing Client/chat.stream flow, and make it fail fast by returning
the empty fallback already used when XAI_API_KEY is missing. Keep the change
localized to grok_chat and its Client/chat.create/chat.stream path so the
listener’s higher-level handle_mention error handling can continue to work
unchanged.

In `@agents/broker.py`:
- Line 16: The ledger write path in broker.py is not crash-safe and the current
_LEDGER_LOCK only protects threads, not multiple processes. Update the
read/write flow around _read and the ledger persistence logic to use an atomic
write pattern so a mid-write crash cannot truncate the file, and stop swallowing
parse failures in _read so corruption is surfaced instead of silently returning
an empty list. If this ledger can be shared across workers, replace the
in-process lock with a cross-process guard or otherwise enforce single-writer
access, and thread through an idempotency key from the execute_action flow so
duplicate approvals do not double-fill.

In `@agents/registry.py`:
- Around line 33-35: The fallback in route_mention currently depends on
team[-1], which silently breaks if build_team() changes member ordering. Update
route_mention to select the general fallback by an explicit marker instead of
position, using the TeamMember returned by get_team() and the same target lookup
logic. Prefer identifying the fallback via a property such as an empty
profile.handle, so untagged mentions always route to the intended GeneralAgent
regardless of list order.
- Around line 47-64: register_team() currently swallows transient
timeline-server startup failures, so a member can miss A2A registration forever
on the first POST. Update register_team() to retry requests.post to
/v1/a2a/agents with a small backoff and only give up after several attempts,
using the existing timeline_url/profile payload logic. Keep the exception
handling around requests.RequestException, but add retry logging tied to
register_team() so startup races are handled instead of silently moving on.
- Around line 26-30: `get_team()` is not thread-safe: the lazy singleton check
on `_TEAM` can race and call `build_team()` twice. Add synchronization around
the first initialization in `agents.registry.get_team` (for example, a
module-level lock guarding the None check and assignment) so only one thread can
build and publish `_TEAM`, while preserving the existing cached return path.

In `@agents/router.py`:
- Around line 9-23: The routing in find_target currently returns the first
matching handle based on team roster order, which breaks multi-mention messages.
Update find_target so it scans all matched handles in the text and selects the
member whose `@handle` appears earliest in the input, while still ignoring empty
handles and preserving case-insensitive, word-bounded matching. Use the existing
find_target and TeamMember/profile.handle symbols to locate the logic and keep
the fallback behavior unchanged.

In `@agents/team/shopping.py`:
- Line 62: The reply truncation logic in shopping.py is duplicated in
research.py, so extract it into a shared helper in agents/base.py such as
truncate_for_reply(text, limit=270, suffix=...). Update the shopping reply
assignment to call that helper instead of inlining the one-liner, and reuse the
same helper from the matching code in research.py so the threshold and wording
stay consistent in one place.
- Around line 39-49: The prompt in handle_mention interpolates untrusted
mention.text directly into the Grok request, creating a prompt-injection path.
Update handle_mention in ShoppingAgent to wrap mention.text in a clearly
delimited data block and explicitly instruct grok_chat to treat it as untrusted
content, not instructions. Use the existing mention.text and grok_chat call
structure as the place to apply the fix, and mirror the same protection pattern
in the other team agents mentioned in the review.

In `@agents/team/tradedesk.py`:
- Around line 101-118: The approve path in execute_action currently calls
self.broker.execute() every time, which can double-fill the same trade if the
dispatcher replays the message before last_seen is persisted. Add an idempotency
guard by deriving a stable key from the item metadata and passing it through to
broker.execute, then have execute_action skip or no-op when the same approval
has already been processed. Use the existing execute_action method and the
broker.execute call site as the entry points for the change, and make the
returned approval message reflect the deduped behavior.
- Around line 101-118: The trade action path in execute_action currently
approves or rejects any matching trade item without verifying who requested it.
Update the PATCH /v1/timeline/items/{id} flow and TradeDeskAgent.execute_action
so the caller’s identity is passed in and compared against the card’s author_id
or an authorized approver list before any approve/reject dispatch; if it doesn’t
match, reject the action early and avoid calling broker.execute.

In `@listener.py`:
- Around line 107-143: main() (or the mention-processing block in listener.py)
is doing routing, reply handling, card pushing, and logging in one large
branch-heavy section. Extract this flow into a focused process_mention(client,
mention) helper, moving the MentionContext creation, route_mention call,
member.handle_mention error handling, client.create_tweet reply logic, and
push_timeline_card logic there so the top-level loop stays small and the
branch/statement count drops.
- Around line 107-146: The mention-processing flow in listener.py marks a
mention as seen even when push_timeline_card fails, which can permanently drop
approval-gated cards. Update the mention handling around member.handle_mention,
client.create_tweet, and push_timeline_card so a card-push failure does not
advance the watermark via save_last_seen/start_time. Either skip save_last_seen
for that mention until the card is successfully pushed, or persist the failed
card in a retry queue and only mark the mention seen after the card lands.

In `@Makefile`:
- Around line 30-31: The test target is not bootstrappable because it assumes
.venv/bin/activate already exists, so make test fails on a clean checkout before
pytest starts. Update the Makefile’s test rule to depend on setup or
create/activate the virtual environment inline before running pip install and
pytest, using the existing setup target and test target names so the workflow
works from scratch.

In `@mcp_dispatcher.py`:
- Around line 35-42: Wrap the network call in get_timeline_item with exception
handling so requests.get failures do not crash the dispatcher loop. Catch
request-related errors inside get_timeline_item and return None on failure, and
make sure the caller that polls timeline items continues gracefully, similar to
the try/except used around owner.execute_action. Keep the fix localized around
get_timeline_item and the polling path that uses it.
- Around line 138-156: Add an idempotency guard in the structured action path
before calling owner.execute_action in mcp_dispatcher.py so retries do not
replay the same timeline_action. Check and mark the timeline item as already
processed (or otherwise make the action idempotent) before the side effect
occurs, and ensure PaperBroker.execute is only reached once for a given action
even if the dispatcher crashes and reruns.

In `@README.md`:
- Around line 22-27: Two fenced Markdown blocks in README are missing a language
tag and will keep triggering markdownlint. Update the bare fences in the
affected examples to use a plain text tag such as text so the blocks remain
unchanged visually while satisfying the linter.

In `@tests/test_tradedesk.py`:
- Around line 54-70: Add a regression test for double-approval idempotency in
test_approve_executes_paper_trade by calling TradeDeskAgent.execute_action twice
with the same item and asserting the PaperBroker position stays at 10.0 instead
of doubling. Use the existing TradeDeskAgent and PaperBroker setup to verify the
duplicate-fill case is prevented once the execute_action/broker.execute fix is
in place.

In `@timeline_server.py`:
- Line 44: The TimelineServer model’s kind field is too permissive and allows
arbitrary strings into the registry. Tighten validation in the TimelineServer
schema by constraining kind to the existing allowed values used by downstream
routing, and keep the default aligned with the current agent behavior. Use the
kind field declaration in TimelineServer as the anchor and update any related
validation/type definition so only "agent" or "bot" can pass.

---

Outside diff comments:
In `@a2a_store.py`:
- Around line 84-102: register_agent() currently no-ops when an existing agent
id is found, which leaves seeded entries like x-agent stale and missing updated
fields such as kind. Update the existing record in the data["agents"] list when
the id matches instead of returning early, so mutable fields from the incoming
payload are merged into the stored agent before _write_store(data) is called.
Use register_agent and the A2A_STORE_LOCK/_read_store/_write_store flow to
locate the fix.

In `@tests/test_broker.py`:
- Around line 1-19: The broker tests only cover normal execution and miss the
failure paths in PaperBroker. Add a test around PaperBroker’s
ledger-loading/restore behavior for a corrupted or truncated trades file to
verify it does not silently drop history, and add a test that calls
PaperBroker.execute() twice with the same trade to ensure duplicate executions
are handled as expected. Use the PaperBroker and execute identifiers so the new
coverage targets the exact regression points.

In `@tests/test_router.py`:
- Around line 1-45: Add coverage in test_route_to... around route_mention and/or
find_target for the multi-handle mention case, since current tests only exercise
one handle at a time. Create a test that passes a MentionContext like "`@Research`
`@Tradedesk` ..." and assert the router picks the correct target according to the
intended priority order in agents.router.find_target. Use the existing symbols
route_mention, find_target, and build_team to keep the new test aligned with the
current routing behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 18dc353d-e5d3-4c34-8944-9a1aa2319033

📥 Commits

Reviewing files that changed from the base of the PR and between 538bc6c and 5ebd07d.

📒 Files selected for processing (24)
  • Makefile
  • README.md
  • a2a_store.py
  • agents/__init__.py
  • agents/base.py
  • agents/broker.py
  • agents/registry.py
  • agents/router.py
  • agents/team/__init__.py
  • agents/team/general.py
  • agents/team/research.py
  • agents/team/shopping.py
  • agents/team/tickerbot.py
  • agents/team/tradedesk.py
  • docs/AGENTS.md
  • env.example
  • listener.py
  • mcp_dispatcher.py
  • pytest.ini
  • requirements-dev.txt
  • tests/test_broker.py
  • tests/test_router.py
  • tests/test_tradedesk.py
  • timeline_server.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • groupthinking/uvai-skills (manual)
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: copilot-pull-request-reviewer
⚠️ CI failures not shown inline (2)

GitHub Actions: 🤖 Autonomous Agent CI/CD / 2_🏷️ Auto-Label PRs.txt: feat: add @handle-routed agent team (interactive agents + API bots)

Conclusion: failure

View job details

##[group]Run actions/labeler@v5
 with:
   repo-***REDACTED***
   configuration-path: .github/labeler.yml
   sync-labels: false
   dot: true
 ##[endgroup]
 The configuration file (path: .github/labeler.yml) was not found locally, fetching via the api
 ##[warning]The config file was not found at .github/labeler.yml. Make sure it exists and that this action has the correct access rights.
 ##[error]HttpError: Not Found

GitHub Actions: 🤖 Autonomous Agent CI/CD / 🏷️ Auto-Label PRs: feat: add @handle-routed agent team (interactive agents + API bots)

Conclusion: failure

View job details

##[group]Run actions/labeler@v5
 with:
   repo-***REDACTED***
   configuration-path: .github/labeler.yml
   sync-labels: false
   dot: true
 ##[endgroup]
 The configuration file (path: .github/labeler.yml) was not found locally, fetching via the api
 ##[warning]The config file was not found at .github/labeler.yml. Make sure it exists and that this action has the correct access rights.
 ##[error]HttpError: Not Found
🧰 Additional context used
🪛 ast-grep (0.44.0)
agents/router.py

[warning] 20-20: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.search(r"@" + re.escape(handle) + r"\b", lowered)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

agents/registry.py

[warning] 61-61: Request-controlled URL passed to requests; validate against an allowlist to prevent SSRF.
Context: requests.post(f"{timeline_url}/v1/a2a/agents", json=payload, timeout=10)
Note: [CWE-918] Server-Side Request Forgery (SSRF).

(ssrf-requests)

mcp_dispatcher.py

[warning] 36-36: Request-controlled URL passed to requests; validate against an allowlist to prevent SSRF.
Context: requests.get(f"{timeline_url}/v1/timeline/items/{item_id}", timeout=10)
Note: [CWE-918] Server-Side Request Forgery (SSRF).

(ssrf-requests)

agents/base.py

[warning] 101-111: Request-controlled URL passed to requests; validate against an allowlist to prevent SSRF.
Context: requests.post(
f"{timeline_url}/v1/a2a/messages",
json={
"from": from_agent,
"to": to,
"type": message_type,
"content": content,
"metadata": metadata or {},
},
timeout=10,
)
Note: [CWE-918] Server-Side Request Forgery (SSRF).

(ssrf-requests)

agents/broker.py

[info] 46-46: use jsonify instead of json.dumps for JSON output
Context: json.dumps(fills, indent=2)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

listener.py

[warning] 60-60: Request-controlled URL passed to requests; validate against an allowlist to prevent SSRF.
Context: requests.post(f"{timeline_url}/v1/timeline/items", json=payload, timeout=10)
Note: [CWE-918] Server-Side Request Forgery (SSRF).

(ssrf-requests)

🪛 markdownlint-cli2 (0.22.1)
docs/AGENTS.md

[warning] 7-7: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 39-39: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

README.md

[warning] 22-22: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 Ruff (0.15.20)
agents/router.py

[warning] 4-4: typing.List is deprecated, use list instead

(UP035)

agents/team/research.py

[warning] 22-22: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)

agents/registry.py

[warning] 4-4: typing.List is deprecated, use list instead

(UP035)


[warning] 27-27: Using the global statement to update _TEAM is discouraged

(PLW0603)

mcp_dispatcher.py

[warning] 147-147: Do not catch blind exception: Exception

(BLE001)

agents/base.py

[warning] 14-14: typing.Dict is deprecated, use dict instead

(UP035)


[warning] 14-14: typing.List is deprecated, use list instead

(UP035)


[warning] 59-59: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)


[warning] 65-65: Unused method argument: item

(ARG002)


[warning] 65-65: Unused method argument: action

(ARG002)

agents/team/shopping.py

[warning] 12-12: typing.Dict is deprecated, use dict instead

(UP035)


[warning] 27-27: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)

agents/team/general.py

[warning] 19-19: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)

agents/team/tradedesk.py

[warning] 13-13: typing.Dict is deprecated, use dict instead

(UP035)


[warning] 51-51: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)

agents/team/tickerbot.py

[warning] 19-19: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)

agents/broker.py

[warning] 14-14: typing.Dict is deprecated, use dict instead

(UP035)


[warning] 14-14: typing.List is deprecated, use list instead

(UP035)


[warning] 20-20: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)

listener.py

[warning] 64-64: Too many branches (13 > 12)

(PLR0912)


[warning] 64-64: Too many statements (52 > 50)

(PLR0915)


[warning] 69-69: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 120-120: Do not catch blind exception: Exception

(BLE001)


[warning] 133-133: Do not catch blind exception: Exception

(BLE001)


[warning] 139-139: Do not catch blind exception: Exception

(BLE001)

🔍 Remote MCP GitHub Copilot

PR context

  • The target PR is #91 in groupthinking/MyXstack, titled “feat: add @handle-routed agent team (interactive agents + API bots)”. It is open on branch claude/myxstack-agent-merge-myaock against main.
  • CI is mostly green: validate passed, 🧠 Copilot Auto-Review passed, label and 🔍 Auto-Detect & Build passed; copilot-pull-request-reviewer is still in progress, while auto-merge and 🛡️ Security Scan were skipped.
  • GitHub’s validation comment flags two review-process notes: the PR title should follow conventional commits, and the PR is large (1030 lines changed).
  • The main code changes are an agents/ team system: agents/router.py adds @handle matching; listener.py now routes each mention to a TeamMember, posts reply.text, and optionally pushes reply.card; mcp_dispatcher.py now resolves timeline approvals back to the owning member via execute_action() before falling back to legacy Grok execution; agents/broker.py adds a local JSON PaperBroker.
  • Built-in members are @Tradedesk (agent), @Research (agent), @Shopping (agent), @TickerBot (bot), plus GeneralAgent as fallback. The docs/env updates describe paper-only trades, approval-gated shopping intents, and configurable handles. Tests were added for routing, broker fills, and trade approval/rejection paths.
🔇 Additional comments (14)
docs/AGENTS.md (1)

1-88: LGTM!

env.example (1)

55-67: LGTM!

pytest.ini (1)

1-3: LGTM!

requirements-dev.txt (1)

1-2: LGTM!

agents/__init__.py (1)

1-1: LGTM!

agents/base.py (1)

46-53: LGTM on the dataclasses themselves.

agents/team/__init__.py (1)

1-1: LGTM!

agents/team/research.py (2)

34-39: 🔒 Security & Privacy

Same unsanitized-prompt problem as shopping.py.

mention.text goes straight into the research prompt. Already flagged with fix suggestion in agents/team/shopping.py.


43-57: LGTM!

agents/team/general.py (2)

31-37: 🔒 Security & Privacy

Same unsanitized-prompt problem as shopping.py.

Fallback agent gets the widest exposure since it handles every untagged mention — same fix applies.


40-52: LGTM!

agents/team/shopping.py (1)

65-76: LGTM!

agents/team/tickerbot.py (1)

18-38: LGTM!

Deterministic, no LLM, no external I/O — the one member in this batch that can't be prompt-injected or double-fire a side effect.

listener.py (1)

64-71: LGTM!

Comment thread agents/base.py
Comment thread agents/base.py Outdated
Comment thread agents/broker.py
Comment thread agents/registry.py
Comment thread agents/registry.py Outdated
Comment thread mcp_dispatcher.py
Comment thread mcp_dispatcher.py
Comment thread README.md Outdated
Comment on lines +22 to +27
```
@MyXstack @Tradedesk $TSLA buy 100 → approval-gated trade proposal (paper broker)
@MyXstack @Research why is $NVDA down? → Grok research brief on the timeline
@MyXstack @Shopping shoes under $150 → product picks, purchase approval-gated
@MyXstack @TickerBot $BTC → deterministic cashtag lookup (API bot)
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Tag the two bare fences.

Markdownlint will keep flagging Lines 22 and 39 until those blocks get a language tag, e.g. text.

Also applies to: 39-53

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 22-22: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 22 - 27, Two fenced Markdown blocks in README are
missing a language tag and will keep triggering markdownlint. Update the bare
fences in the affected examples to use a plain text tag such as text so the
blocks remain unchanged visually while satisfying the linter.

Source: Linters/SAST tools

Comment thread tests/test_tradedesk.py
Comment thread timeline_server.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agents/team/tradedesk.py`:
- Around line 105-112: The coercion in tradedesk processing still allows invalid
trade values through: validate the normalized side in the trade-card flow before
calling broker.execute(), and reject anything outside buy/sell instead of
persisting it. Also add a positive-quantity check after the float conversion in
the same trade-handling path so zero or negative quantities return an error
before execution. Use the existing metadata parsing in the trade card logic and
the broker.execute() call site to place these guards.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: dd8fccb2-5436-435c-af1d-fe640f90b8b0

📥 Commits

Reviewing files that changed from the base of the PR and between 5ebd07d and dcfc6a7.

📒 Files selected for processing (3)
  • agents/team/tradedesk.py
  • mcp_dispatcher.py
  • timeline_server.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • groupthinking/uvai-skills (manual)
📜 Review details
⚠️ CI failures not shown inline (2)

GitHub Actions: 🤖 Autonomous Agent CI/CD / 1_🏷️ Auto-Label PRs.txt: feat: add @handle-routed agent team (interactive agents + API bots)

Conclusion: failure

View job details

##[group]Run actions/labeler@v5
 with:
   repo-***REDACTED***
   configuration-path: .github/labeler.yml
   sync-labels: false
   dot: true
 ##[endgroup]
 The configuration file (path: .github/labeler.yml) was not found locally, fetching via the api
 ##[warning]The config file was not found at .github/labeler.yml. Make sure it exists and that this action has the correct access rights.
 ##[error]HttpError: Not Found

GitHub Actions: 🤖 Autonomous Agent CI/CD / 🏷️ Auto-Label PRs: feat: add @handle-routed agent team (interactive agents + API bots)

Conclusion: failure

View job details

##[group]Run actions/labeler@v5
 with:
   repo-***REDACTED***
   configuration-path: .github/labeler.yml
   sync-labels: false
   dot: true
 ##[endgroup]
 The configuration file (path: .github/labeler.yml) was not found locally, fetching via the api
 ##[warning]The config file was not found at .github/labeler.yml. Make sure it exists and that this action has the correct access rights.
 ##[error]HttpError: Not Found
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:22:51.655Z
Learning: Classify each team member by `kind` in its profile and the A2A registry as either an `agent` (interactive, LLM-backed, can reason, use MCP tools, delegate over A2A, and propose approval-gated actions) or a `bot` (deterministic function executor with no LLM or autonomy).
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:22:51.655Z
Learning: `Tradedesk` must parse `$TICKER buy/sell [qty]`, log a trade proposal to the approval timeline, and only execute fills through `PaperBroker` after approval; no live broker should be used by default.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:22:51.655Z
Learning: `Shopping` must research product picks with Grok and treat purchases as approval-gated intents; no payment executor is wired in by default.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:22:51.655Z
Learning: `Research` must answer using Grok plus MCP tools for live X context, post a short reply, and file the full brief on the timeline.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:22:51.655Z
Learning: `TickerBot` must act as a deterministic cashtag lookup bot that returns live-search links for each `$TICKER`.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:22:51.655Z
Learning: Members are configurable via `TRADEDESK_HANDLE`, `RESEARCH_HANDLE`, `SHOPPING_HANDLE`, and `TICKERBOT_HANDLE` in `.env`.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:22:51.655Z
Learning: Interactive agents may message each other directly over the A2A bus via `POST /v1/a2a/messages` using `agents.base.send_a2a_message()`.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:22:51.655Z
Learning: When adding a team member, create a `TeamMember` subclass in `agents/team/mymember.py`, define a unique `AgentProfile` with `id`, `handle`, and `kind`, implement `handle_mention()` returning `AgentReply(text, card=None)`, and implement `execute_action()` when the member has Approve/Reject cards.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:22:51.655Z
Learning: Register the new team member in `build_team()` in `agents/registry.py` before the fallback `GeneralAgent`.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:22:51.655Z
Learning: Trades must be paper-only: `PaperBroker` writes simulated fills to a local ledger and must not touch a real exchange.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:22:51.655Z
Learning: Purchases must remain intents only; approving a shopping card records the intent and no payment adapter is shipped by default.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:22:51.655Z
Learning: All side-effecting actions must be approval-gated on the timeline before the dispatcher executes them.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:22:51.655Z
Learning: Until each member gets its own real X handle, all members share the listener's X account and the router selects the member from the tagged handle in the text.
🪛 ast-grep (0.44.0)
mcp_dispatcher.py

[warning] 37-37: Request-controlled URL passed to requests; validate against an allowlist to prevent SSRF.
Context: requests.get(f"{timeline_url}/v1/timeline/items/{item_id}", timeout=10)
Note: [CWE-918] Server-Side Request Forgery (SSRF).

(ssrf-requests)

🪛 Ruff (0.15.20)
timeline_server.py

[warning] 2-2: typing.Dict is deprecated, use dict instead

(UP035)


[warning] 2-2: typing.List is deprecated, use list instead

(UP035)

🔍 Remote MCP GitHub Copilot

Relevant review context for PR #91

  • PR is open on claude/myxstack-agent-merge-myaock into main, with 24 files changed, 957 additions / 81 deletions (GitHub later validation also flagged the PR as large).
  • CI status is mostly green, but GitHub validation noted conventional-commit title concerns and the PR size; CodeRabbit’s run also says copilot-pull-request-reviewer was COMMENTED/CHANGES_REQUESTED, not approved.
  • The latest review highlights these still-relevant issues:
    • Prompt injection risk: mention.text is passed directly into Grok prompts in agents/team/shopping.py, agents/team/research.py, and agents/team/general.py.
    • Routing edge case: agents/router.py currently returns the first matching handle by roster order, and CodeRabbit wants multi-handle mentions to route by earliest handle in text.
    • Idempotency / duplicate-fills risk: TradeDeskAgent.execute_action() can call PaperBroker.execute() again if an approval is replayed; reviewer wants a stable idempotency guard.
    • A2A registry staleness: register_agent() may no-op on existing IDs, leaving seeded records like x-agent without updated fields such as kind.
    • Unresolved lint/docs items: markdown fences in README.md/docs/AGENTS.md, plus several ruff warnings about type hints / exception handling / branch complexity.
  • Key implementation facts confirmed by the diff:
    • listener.py now routes mentions through MentionContext -> route_mention() -> handle_mention(), then posts reply.text and optional reply.card.
    • mcp_dispatcher.py now fetches the timeline item, resolves the owning member via find_member(agent_id), and calls execute_action() before falling back to Grok.
    • agents/broker.py stores simulated fills in a local JSON ledger; PaperBroker.execute() normalizes ticker/side and positions() aggregates net quantity.
🔇 Additional comments (4)
mcp_dispatcher.py (2)

142-160: Still no idempotency guard on the approve/reject execution path.

Fetch failures are handled now, fine. But owner.execute_action(item, action) at line 150 can still be replayed on crash/retry before whatever marks the item "done" — same trade gets filled twice, same purchase intent double-logged. This was called out before and it's still not fixed. Ledger's a JSON file right now, blast radius is small, but the pattern's the same one that'll bite in prod with a real broker.


35-44: LGTM!

agents/team/tradedesk.py (1)

101-119: Duplicate approvals still double-fill, and anyone can trigger approve/reject.

Coercion on ticker/side/quantity is fine as far as it goes, but the two real problems from last round are untouched: broker.execute() on line 115 fires unconditionally on approve with no idempotency key, and there's still no check that the approver matches author_id from the card metadata. Retry/replay on that PATCH still means duplicate fills; unauthenticated approve still means anyone can execute someone else's trade proposal.

timeline_server.py (1)

2-2: LGTM!

Also applies to: 44-45

Comment thread agents/team/tradedesk.py
…injection-guarded prompts

Address CodeRabbit review on #91:
- PaperBroker: idempotency key (card id) so replayed/double approvals
  never double-fill; atomic ledger writes via os.replace; cross-process
  flock; corrupt ledgers preserved as .corrupt-* backups instead of
  silently discarded
- Tradedesk: passes the card id as fill key, reports duplicate approvals;
  validates side in {buy,sell} and quantity > 0 before executing
- Dispatcher: replay guard via processed_action metadata flag so a crash
  between execute and watermark save can't re-run a side effect
- Listener: mention flow extracted to process_mention(); the approval
  card is pushed before the X reply, and a failed card push stops the
  watermark so the mention retries next poll instead of losing the card
- Prompts: mention text wrapped in delimited untrusted-content blocks
  (shopping/research/general); Grok client gets a hard XAI_TIMEOUT_SECONDS
  deadline (default 120s)
- Registry: thread-safe team singleton; fallback selected by empty-handle
  marker instead of roster position; A2A registration retries with backoff
- Router: earliest @tag in the text wins for multi-handle mentions
- send_a2a_message returns bool instead of raising on bus outages
- a2a_store: re-registration updates mutable fields (stale seeded records
  now pick up kind)
- Makefile: make test bootstraps the venv; README/docs fences tagged
- Tests: double-approval, invalid side/quantity, duplicate fill key,
  corrupt-ledger preservation, multi-handle routing (23 total)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTLFzAjoSosiPfHGaXSZkP
Copilot AI review requested due to automatic review settings July 1, 2026 23:31
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🔍 PR Validation

⚠️ Large PR detected (1263 lines changed)

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.

Comment thread timeline_server.py Outdated
Comment on lines 44 to 46
# "agent" (interactive, LLM-backed) or "bot" (deterministic executor)
kind: Optional[Literal["agent", "bot"]] = "agent"
tags: List[str] = Field(default_factory=list)
Comment thread a2a_store.py
Comment on lines +91 to 94
# Classification: "agent" (interactive, LLM-backed, autonomous) vs
# "bot" (deterministic function executor).
"kind": payload.get("kind", "agent"),
"tags": payload.get("tags", []),
Comment thread listener.py Outdated
"actions": card.get("actions", []),
"metadata": card.get("metadata", {}),
}
requests.post(f"{timeline_url}/v1/timeline/items", json=payload, timeout=10)
Comment thread agents/base.py Outdated
Comment on lines +90 to +92
if len(text) <= limit:
return text
return text[:240].rsplit(" ", 1)[0] + suffix
… null, truncation honors limit

- push_timeline_card raises on 4xx/5xx so a failed card push holds the
  mention watermark instead of being treated as success
- timeline_server: kind is non-Optional; explicit null is rejected
- a2a_store: kind normalized to agent/bot with safe default at the store
- truncate_for_reply uses its limit parameter instead of hardcoding 240

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTLFzAjoSosiPfHGaXSZkP
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🔍 PR Validation

⚠️ Large PR detected (1272 lines changed)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
mcp_dispatcher.py (2)

157-171: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Fail closed for owned cards instead of falling back to generic Grok.

If a card has agent_id but find_member() fails or execute_action() returns None, Line 171 sends the action to the legacy MCP workflow. That bypasses the member’s safety policy, including paper-only trades and shopping-intent-only behavior. Only use the legacy fallback for cards with no agent_id.

Based on learnings: trades must remain paper-only, shopping approvals are intents only, and side-effecting actions must be approval-gated through the owning member before execution.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp_dispatcher.py` around lines 157 - 171, The fallback in the action
execution path is too permissive for owned cards, since mcp_dispatcher’s
owned-item branch can still drop into the legacy Grok workflow when
find_member() fails or execute_action() returns None. Update the logic around
owner.execute_action so that if item_meta has an agent_id, the code fails closed
and does not call call_grok at all; reserve the legacy fallback only for items
without an agent_id. Keep the handling inside the mcp_dispatcher action flow and
the owner/find_member/execute_action branch so the member’s safety policy
remains the only execution path for owned cards.

Source: Learnings


138-163: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Don’t execute timeline approvals without an approver check. timeline_server.py emits timeline_action with only timeline_item_id and action, and mcp_dispatcher.py goes straight to owner.execute_action() with no actor/ACL validation. Any client that can post to the A2A bus can approve someone else’s card. Require a signed approver identity or check the card author/ACL before dispatch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp_dispatcher.py` around lines 138 - 163, The structured timeline action
handling in mcp_dispatcher.py currently dispatches directly from item_id and
action to owner.execute_action without verifying who is allowed to approve it.
Update the timeline_action path in the dispatcher to require and validate an
approver identity or ACL before calling find_member() and execute_action(), and
reject the message if the actor is missing or unauthorized. Use the existing
metadata/item handling around get_timeline_item(), item_meta, and
owner.execute_action() to locate the fix and enforce the check before any side
effect runs.

Source: MCP tools

agents/broker.py (1)

95-103: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

positions() skips the cross-process lock — corrupt-ledger recovery can race and crash.

execute() takes _LEDGER_LOCK and _ledger_file_lock(...). positions() only takes _LEDGER_LOCK. Fine for a single process — useless the moment two processes (dispatcher + whatever else calls positions()) hit a corrupt ledger at the same time. _read()'s corruption path does self.ledger_path.rename(backup) unguarded by the file lock here; second caller's rename target is already gone → unhandled FileNotFoundError, process dies. Cute way to turn "preserve corrupt ledger" into "crash on corrupt ledger" for whoever loses the race. The docstring on _ledger_file_lock literally says this lock exists so "concurrent dispatchers can't corrupt the ledger" — apply it consistently or don't bother.

🔧 Fix
     def positions(self) -> Dict[str, float]:
         totals: Dict[str, float] = {}
-        with _LEDGER_LOCK:
+        with _LEDGER_LOCK, _ledger_file_lock(self.ledger_path):
             fills = self._read()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agents/broker.py` around lines 95 - 103, The positions() method only uses
_LEDGER_LOCK and misses the cross-process protection already used by execute(),
so corrupt-ledger recovery can race during _read(). Update positions() to also
wrap the ledger access in _ledger_file_lock(...) for the same ledger path,
alongside the existing _LEDGER_LOCK, so the rename-and-backup recovery path is
serialized consistently. Use the existing positions(), _read(), _LEDGER_LOCK,
and _ledger_file_lock symbols to locate the change.
♻️ Duplicate comments (2)
mcp_dispatcher.py (1)

147-176: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

This is still not a pre-side-effect replay guard.

processed_action is written after owner.execute_action(), so a crash between Line 161 and Line 174 can replay the same approval. Use a durable idempotency key/state transition before execution, or require every executor path to be idempotent by timeline_item_id:action.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp_dispatcher.py` around lines 147 - 176, The replay guard in the action
handling flow is still happening after the side effect, so a crash before
update_timeline_item can re-execute the same approval. Move the
idempotency/state transition ahead of owner.execute_action in the main
dispatcher flow, or make both the owner.execute_action and fallback call_grok
paths explicitly idempotent using a durable key based on item_id and action.
Keep the guard logic around processed_action and the item/action handling branch
in mcp_dispatcher.py.

Source: MCP tools

agents/team/tradedesk.py (1)

103-132: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Validation and dedup are fixed. Approver identity still isn't checked — anyone can fill anyone's trade.

Side/quantity validation and the idempotency key fix the two prior blockers. Still nothing here binds "who clicked Approve" to metadata["author_id"]. execute_action(item, action) takes no approver identity at all, and mcp_dispatcher.py's owner.execute_action(item, action) call site doesn't pass one either. Today it's paper trades, so worst case is a fake fill in a JSON file — but this is the exact spot that gets wired to a real broker per the module docstring, and right now anyone with timeline access approves anyone else's trade proposal. This was already flagged in a prior review and is still open.

Plumb the approver through execute_action (and the dispatcher call site) and reject on mismatch before touching self.broker.execute.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agents/team/tradedesk.py` around lines 103 - 132, The approver identity is
not enforced in tradedesk approval flow, so anyone can execute another user’s
trade proposal. Update execute_action in tradedesk.py to accept the approver
identity, compare it against metadata["author_id"] before calling
self.broker.execute, and reject mismatches early with a clear message. Also
update the owner.execute_action call site in mcp_dispatcher.py to pass the
clicked approver identity through so the authorization check is actually
applied.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@listener.py`:
- Around line 84-91: The mention handling in member.handle_mention currently
treats any exception as successfully processed, which causes the watermark to
advance and drops retryable mentions. Update the exception path in listener.py
around handle_mention so transient handler failures return False instead of
True, and only advance the watermark when processing actually succeeds. If you
need to preserve unrecoverable mentions, route them to a dead-letter path before
returning success.
- Around line 155-161: The watermark update in get_users_mentions processing is
not monotonic because the loop in listener.py saves each mention’s created_at as
it goes while iterating newest-to-oldest, which can move the stored last-seen
timestamp backward. Update the mention-handling flow so process_mention and the
save_last_seen call only persist the maximum successful created_at once after
all successful mentions are processed, or sort the mentions into ascending order
before iterating, so the stored watermark always advances forward.

---

Outside diff comments:
In `@agents/broker.py`:
- Around line 95-103: The positions() method only uses _LEDGER_LOCK and misses
the cross-process protection already used by execute(), so corrupt-ledger
recovery can race during _read(). Update positions() to also wrap the ledger
access in _ledger_file_lock(...) for the same ledger path, alongside the
existing _LEDGER_LOCK, so the rename-and-backup recovery path is serialized
consistently. Use the existing positions(), _read(), _LEDGER_LOCK, and
_ledger_file_lock symbols to locate the change.

In `@mcp_dispatcher.py`:
- Around line 157-171: The fallback in the action execution path is too
permissive for owned cards, since mcp_dispatcher’s owned-item branch can still
drop into the legacy Grok workflow when find_member() fails or execute_action()
returns None. Update the logic around owner.execute_action so that if item_meta
has an agent_id, the code fails closed and does not call call_grok at all;
reserve the legacy fallback only for items without an agent_id. Keep the
handling inside the mcp_dispatcher action flow and the
owner/find_member/execute_action branch so the member’s safety policy remains
the only execution path for owned cards.
- Around line 138-163: The structured timeline action handling in
mcp_dispatcher.py currently dispatches directly from item_id and action to
owner.execute_action without verifying who is allowed to approve it. Update the
timeline_action path in the dispatcher to require and validate an approver
identity or ACL before calling find_member() and execute_action(), and reject
the message if the actor is missing or unauthorized. Use the existing
metadata/item handling around get_timeline_item(), item_meta, and
owner.execute_action() to locate the fix and enforce the check before any side
effect runs.

---

Duplicate comments:
In `@agents/team/tradedesk.py`:
- Around line 103-132: The approver identity is not enforced in tradedesk
approval flow, so anyone can execute another user’s trade proposal. Update
execute_action in tradedesk.py to accept the approver identity, compare it
against metadata["author_id"] before calling self.broker.execute, and reject
mismatches early with a clear message. Also update the owner.execute_action call
site in mcp_dispatcher.py to pass the clicked approver identity through so the
authorization check is actually applied.

In `@mcp_dispatcher.py`:
- Around line 147-176: The replay guard in the action handling flow is still
happening after the side effect, so a crash before update_timeline_item can
re-execute the same approval. Move the idempotency/state transition ahead of
owner.execute_action in the main dispatcher flow, or make both the
owner.execute_action and fallback call_grok paths explicitly idempotent using a
durable key based on item_id and action. Keep the guard logic around
processed_action and the item/action handling branch in mcp_dispatcher.py.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2e055e15-ceef-41b8-9b6e-a0256024ea39

📥 Commits

Reviewing files that changed from the base of the PR and between dcfc6a7 and 2380fea.

📒 Files selected for processing (17)
  • Makefile
  • README.md
  • a2a_store.py
  • agents/base.py
  • agents/broker.py
  • agents/registry.py
  • agents/router.py
  • agents/team/general.py
  • agents/team/research.py
  • agents/team/shopping.py
  • agents/team/tradedesk.py
  • docs/AGENTS.md
  • listener.py
  • mcp_dispatcher.py
  • tests/test_broker.py
  • tests/test_router.py
  • tests/test_tradedesk.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • groupthinking/uvai-skills (manual)
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: copilot-pull-request-reviewer
⚠️ CI failures not shown inline (2)

GitHub Actions: 🤖 Autonomous Agent CI/CD / 🏷️ Auto-Label PRs: feat: add @handle-routed agent team (interactive agents + API bots)

Conclusion: failure

View job details

##[group]Run actions/labeler@v5
 with:
   repo-***REDACTED***
   configuration-path: .github/labeler.yml
   sync-labels: false
   dot: true
 ##[endgroup]
 The configuration file (path: .github/labeler.yml) was not found locally, fetching via the api
 ##[warning]The config file was not found at .github/labeler.yml. Make sure it exists and that this action has the correct access rights.
 ##[error]HttpError: Not Found

GitHub Actions: 🤖 Autonomous Agent CI/CD / 1_🏷️ Auto-Label PRs.txt: feat: add @handle-routed agent team (interactive agents + API bots)

Conclusion: failure

View job details

##[group]Run actions/labeler@v5
 with:
   repo-***REDACTED***
   configuration-path: .github/labeler.yml
   sync-labels: false
   dot: true
 ##[endgroup]
 The configuration file (path: .github/labeler.yml) was not found locally, fetching via the api
 ##[warning]The config file was not found at .github/labeler.yml. Make sure it exists and that this action has the correct access rights.
 ##[error]HttpError: Not Found
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:32:00.173Z
Learning: Run MyXstack as a team of handle-addressable members on the A2A layer, where tagging a member in a mention routes the request to that member via the listener/router.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:32:00.173Z
Learning: Classify each member by `kind` in its profile and the A2A registry: `agent` for conversational, LLM-backed interactive agents; `bot` for deterministic API bots.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:32:00.173Z
Learning: Members may message each other directly on the A2A bus using `POST /v1/a2a/messages` via `agents.base.send_a2a_message()`, allowing interactive agents to drive sub-agents and bots.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:32:00.173Z
Learning: Trades must remain paper-only: `PaperBroker` records simulated fills locally and must not place real exchange orders.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:32:00.173Z
Learning: Shopping approvals are intents only: approving a shopping card records the intent, but no payment adapter is wired in by default.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:32:00.173Z
Learning: All side-effecting actions must be approval-gated on the timeline before the dispatcher executes them.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:32:00.173Z
Learning: Configure member handles through `TRADEDESK_HANDLE`, `RESEARCH_HANDLE`, `SHOPPING_HANDLE`, and `TICKERBOT_HANDLE` in `.env`.
🪛 ast-grep (0.44.0)
agents/router.py

[warning] 24-24: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.search(r"@" + re.escape(handle) + r"\b", lowered)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

agents/broker.py

[warning] 30-30: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(lock_file, "w")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)


[info] 62-62: use jsonify instead of json.dumps for JSON output
Context: json.dumps(fills, indent=2)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

agents/registry.py

[warning] 74-74: Request-controlled URL passed to requests; validate against an allowlist to prevent SSRF.
Context: requests.post(f"{timeline_url}/v1/a2a/agents", json=payload, timeout=10)
Note: [CWE-918] Server-Side Request Forgery (SSRF).

(ssrf-requests)

agents/base.py

[warning] 131-141: Request-controlled URL passed to requests; validate against an allowlist to prevent SSRF.
Context: requests.post(
f"{timeline_url}/v1/a2a/messages",
json={
"from": from_agent,
"to": to,
"type": message_type,
"content": content,
"metadata": metadata or {},
},
timeout=10,
)
Note: [CWE-918] Server-Side Request Forgery (SSRF).

(ssrf-requests)

🪛 Ruff (0.15.20)
agents/broker.py

[warning] 15-15: typing.Dict is deprecated, use dict instead

(UP035)


[warning] 15-15: typing.List is deprecated, use list instead

(UP035)


[warning] 21-21: Missing return type annotation for private function _ledger_file_lock

(ANN202)

agents/registry.py

[warning] 30-30: Using the global statement to update _TEAM is discouraged

(PLW0603)

agents/base.py

[warning] 143-143: Consider moving this statement to an else block

(TRY300)

listener.py

[warning] 86-86: Do not catch blind exception: Exception

(BLE001)


[warning] 96-96: Do not catch blind exception: Exception

(BLE001)


[warning] 108-108: Do not catch blind exception: Exception

(BLE001)

🔍 Remote MCP GitHub Copilot

Additional review context for PR #91

  • The PR is open on groupthinking/MyXstack into main, with 24 changed files, 1177 additions / 86 deletions, and mergeable_state: unstable.
  • GitHub validation flagged the PR title format and the PR size as large.
  • Copilot review ultimately marked the PR CHANGES_REQUESTED on the latest run.

Still-relevant review risks

  • Prompt injection remains in agents/team/shopping.py, agents/team/research.py, and agents/team/general.py: mention.text is still interpolated directly into Grok prompts; CodeRabbit explicitly called this out.
  • Trade approval replay/idempotency is still unresolved: Copilot’s latest review says TradeDeskAgent.execute_action() can still double-fill on repeated approvals, and it also notes there is still no check that the approver matches author_id.
  • Dispatcher replay protection is still incomplete: mcp_dispatcher.py now fetches timeline items and marks processed_action, but Copilot says there is still no idempotency guard before owner.execute_action().
  • Routing behavior is now earliest-tag-wins: agents/router.py scans all handles and selects the one whose @handle appears earliest in the text, case-insensitively and word-bounded.
  • A2A registry updates now merge existing records: register_agent() updates mutable fields, including kind, instead of returning early on duplicate IDs.
  • kind is constrained to agent | bot in timeline_server.py.

Review/supporting test coverage

  • Added tests cover routing, broker fill recording, duplicate key handling, corrupt-ledger recovery, trade parsing, approve/reject execution, and kind classification.
  • Copilot’s comments still ask for a multi-handle routing test and stronger duplicate-execution coverage at the PR-review level.
🔇 Additional comments (15)
Makefile (1)

30-33: Venv bootstrap fixed, past complaint resolved.

test -d .venv || python3 -m venv .venv finally stops this from face-planting on a clean checkout. Previous review flagged exactly this. Good, ship it.

One nit nobody will fix: pip install -q swallows install failures into silence — if a dep is broken you get a cryptic ModuleNotFoundError from pytest instead of a clear pip error. Not blocking.

docs/AGENTS.md (1)

1-88: LGTM!

tests/test_router.py (1)

41-47: LGTM!

agents/base.py (2)

119-146: 🔒 Security & Privacy

Try/except fixed, auth gap from the earlier review is still open.

Graceful-failure handling landed — good, handle_mention callers won't die on a bus hiccup anymore. But the second half of the earlier comment is untouched: this still POSTs from/to/content/metadata to /v1/a2a/messages with zero credentials. Same story in register_team(). If TIMELINE_API_URL is reachable by anything besides localhost, any client can forge messages and impersonate a team member on the A2A bus. Needs confirmation the timeline server enforces auth server-side, because nothing client-side is doing it.


71-116: LGTM!

agents/registry.py (3)

57-81: 🔒 Security & Privacy

Retry/backoff lands, auth gap doesn't.

3x retry with backoff before giving up fixes the startup-ordering race — that's the fix that was asked for. Same unresolved auth gap as send_a2a_message in agents/base.py though: this POSTs id/name/description/kind to /v1/a2a/agents with no credentials. One root cause, two call sites — see the comment on agents/base.py.


26-35: LGTM!


38-45: LGTM!

agents/router.py (1)

9-28: LGTM!

agents/team/general.py (1)

19-53: LGTM!

a2a_store.py (1)

84-109: LGTM!

agents/broker.py (1)

1-93: LGTM!

tests/test_broker.py (1)

21-38: LGTM!

agents/team/tradedesk.py (1)

64-102: LGTM!

tests/test_tradedesk.py (1)

72-106: LGTM!

Comment thread listener.py Outdated
Comment thread listener.py Outdated
…atch

Address CodeRabbit re-review:
- listener processes mentions oldest-first (X returns newest-first), so
  the last-seen watermark can never move backwards and replay mentions
- a handler crash now files a dead-letter card on the timeline instead of
  silently dropping the mention; if even that card can't land, the
  watermark holds and the mention retries next poll
- dispatcher fails closed for member-owned cards: an unhandled action on
  an owned card never falls through to the generic Grok executor
- PaperBroker.positions() takes the cross-process file lock like
  execute(), so corrupt-ledger recovery can't race between processes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTLFzAjoSosiPfHGaXSZkP
Copilot AI review requested due to automatic review settings July 1, 2026 23:44
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🔍 PR Validation

⚠️ Large PR detected (1307 lines changed)

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.

Comment thread mcp_dispatcher.py Outdated
Comment on lines +147 to +156
if item and action and item_meta.get("processed_action") == action:
# Replay guard: a crash between execute and save_last_seen
# re-delivers the message; don't run the side effect twice.
print(
f"Skipping already-processed '{action}' on item {item_id}",
flush=True,
)
last_seen = created_at or datetime.now(timezone.utc)
save_last_seen(last_seen.isoformat())
continue
Comment thread agents/registry.py
Comment on lines +73 to +81
for attempt in range(3):
try:
requests.post(f"{timeline_url}/v1/a2a/agents", json=payload, timeout=10)
break
except requests.RequestException as exc:
if attempt == 2:
print(f"Could not register agent {profile.id}: {exc}", flush=True)
else:
time.sleep(2**attempt)
Comment thread agents/team/tradedesk.py Outdated
Comment on lines +109 to +112
ticker = str(metadata.get("ticker", "?")).upper()
side = str(metadata.get("side", "buy")).lower()
if side not in ("buy", "sell"):
return f"⚠️ Invalid side '{side}' on trade card {item.get('id', '?')}; nothing executed."
Comment thread listener.py
Comment on lines +126 to +133
try:
client.create_tweet(
text=reply.text[:280],
in_reply_to_tweet_id=mention.id,
)
except Exception as exc:
print(f"Error replying to mention {mention.id}: {exc}", flush=True)
return True
…er format check, surfaced dropped replies

Address Copilot re-review round 2:
- dispatcher treats any processed_action as terminal, so a late Reject
  after an executed Approve (or vice versa) is ignored instead of run
- register_team raises on 4xx/5xx so registration retries during
  timeline-server boot instead of silently accepting an error response
- Tradedesk validates ticker against ^[A-Z]{1,10}$ before executing —
  a missing/None ticker can no longer reach the broker
- a failed reply tweet is surfaced as a timeline card so operators can
  recover it (the mention itself is not retried; its card already landed)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTLFzAjoSosiPfHGaXSZkP
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🔍 PR Validation

⚠️ Large PR detected (1336 lines changed)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
agents/base.py (1)

86-92: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Stop hard-coding the suffix budget.

Line 92 subtracts 30 instead of len(suffix), so the default suffix can already overshoot limit, and custom suffixes make this helper lie about its cap.

Fix the budget calculation
-    return text[: max(0, limit - 30)].rsplit(" ", 1)[0] + suffix
+    if len(suffix) >= limit:
+        return suffix[:limit]
+    budget = limit - len(suffix)
+    prefix = text[:budget].rsplit(" ", 1)[0] or text[:budget]
+    return prefix + suffix
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agents/base.py` around lines 86 - 92, The truncate_for_reply helper is
hard-coding the suffix budget instead of using the actual suffix length, so fix
the truncation calculation to subtract len(suffix) in the return path rather
than 30. Update the logic in truncate_for_reply to keep the final string within
limit for both the default suffix and any custom suffix values.
mcp_dispatcher.py (1)

145-176: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fail closed when the timeline item can’t be loaded.

If item_id is present but get_timeline_item() returns None, ownership is unknown and Line 176 falls into the generic Grok executor. That bypasses the member policy exactly when the dispatcher has lost the card context.

Block generic fallback when item context is missing
             result = None
             item = get_timeline_item(item_id) if item_id else None
+            if item_id and item is None:
+                result = f"Could not load timeline item {item_id}; nothing executed."
             item_meta = (item.get("metadata") or {}) if item else {}

Based on learnings, trades must remain paper-only through PaperBroker and purchases are intents only, so owned-card actions must not fall back to generic execution.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp_dispatcher.py` around lines 145 - 176, The dispatcher in
mcp_dispatcher.py should fail closed when an item_id is present but
get_timeline_item() returns None, instead of allowing the generic Grok fallback.
Update the action handling around item, item_meta, owned_agent_id, and the
result flow so missing item context blocks execution for owned cards, matching
the existing fail-closed behavior used when owner.execute_action() returns None.
Preserve the current processed_action replay guard, but ensure fallback to the
generic executor only happens when there is no item_id/ownership context at all.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mcp_dispatcher.py`:
- Around line 161-164: The member execution path in mcp_dispatcher.py is
incorrectly treating execute_action() exceptions as a normal result, which
allows failed actions to be persisted as processed. Update the logic around
owner.execute_action() so only successful executions reach the processed_action
persistence in the later block, and ensure failures from the exception path do
not record or advance the action state. Use the execute_action and
processed_action flow in the same dispatcher branch to keep failed approvals
retryable instead of marking them done.

---

Outside diff comments:
In `@agents/base.py`:
- Around line 86-92: The truncate_for_reply helper is hard-coding the suffix
budget instead of using the actual suffix length, so fix the truncation
calculation to subtract len(suffix) in the return path rather than 30. Update
the logic in truncate_for_reply to keep the final string within limit for both
the default suffix and any custom suffix values.

In `@mcp_dispatcher.py`:
- Around line 145-176: The dispatcher in mcp_dispatcher.py should fail closed
when an item_id is present but get_timeline_item() returns None, instead of
allowing the generic Grok fallback. Update the action handling around item,
item_meta, owned_agent_id, and the result flow so missing item context blocks
execution for owned cards, matching the existing fail-closed behavior used when
owner.execute_action() returns None. Preserve the current processed_action
replay guard, but ensure fallback to the generic executor only happens when
there is no item_id/ownership context at all.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 45cafbfa-a288-432c-913b-e0a38637cdf8

📥 Commits

Reviewing files that changed from the base of the PR and between 2380fea and 93f717c.

📒 Files selected for processing (6)
  • a2a_store.py
  • agents/base.py
  • agents/broker.py
  • listener.py
  • mcp_dispatcher.py
  • timeline_server.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • groupthinking/uvai-skills (manual)
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: copilot-pull-request-reviewer
⚠️ CI failures not shown inline (2)

GitHub Actions: 🤖 Autonomous Agent CI/CD / 3_🏷️ Auto-Label PRs.txt: feat: add @handle-routed agent team (interactive agents + API bots)

Conclusion: failure

View job details

##[group]Run actions/labeler@v5
 with:
   repo-***REDACTED***
   configuration-path: .github/labeler.yml
   sync-labels: false
   dot: true
 ##[endgroup]
 The configuration file (path: .github/labeler.yml) was not found locally, fetching via the api
 ##[warning]The config file was not found at .github/labeler.yml. Make sure it exists and that this action has the correct access rights.
 ##[error]HttpError: Not Found

GitHub Actions: 🤖 Autonomous Agent CI/CD / 🏷️ Auto-Label PRs: feat: add @handle-routed agent team (interactive agents + API bots)

Conclusion: failure

View job details

##[group]Run actions/labeler@v5
 with:
   repo-***REDACTED***
   configuration-path: .github/labeler.yml
   sync-labels: false
   dot: true
 ##[endgroup]
 The configuration file (path: .github/labeler.yml) was not found locally, fetching via the api
 ##[warning]The config file was not found at .github/labeler.yml. Make sure it exists and that this action has the correct access rights.
 ##[error]HttpError: Not Found
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:45:16.987Z
Learning: Tag the intended member handle in a mention so the listener can route the request to that member over A2A.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:45:16.987Z
Learning: Classify each member by `kind` in its profile and in the A2A registry, using `agent` for interactive LLM-backed members and `bot` for deterministic API executors.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:45:16.987Z
Learning: Use the A2A bus (`POST /v1/a2a/messages`) and `agents.base.send_a2a_message()` for member-to-member communication and delegation.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:45:16.987Z
Learning: Trades must remain paper-only; executions should go through `PaperBroker`, which records simulated fills locally and never sends live orders.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:45:16.987Z
Learning: Purchases are intents only, and side-effecting actions must be approval-gated before the dispatcher executes them.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:45:16.987Z
Learning: If members should run under separate real X handles in the future, account for the configurable handle environment variables (`TRADEDESK_HANDLE`, `RESEARCH_HANDLE`, `SHOPPING_HANDLE`, `TICKERBOT_HANDLE`) in `.env`.
🪛 Ruff (0.15.20)
mcp_dispatcher.py

[warning] 163-163: Do not catch blind exception: Exception

(BLE001)

listener.py

[warning] 112-112: Consider moving this statement to an else block

(TRY300)


[warning] 113-113: Do not catch blind exception: Exception

(BLE001)

🔍 Remote MCP GitHub Copilot

Additional PR review context

  • The latest Copilot review is still pending on commit 93f717c..., so the review is not finished yet. The PR also has a pending CodeRabbit status.
  • New unresolved review comments flag:
    • timeline_server.py: kind can still become null / invalid unless it is strictly constrained and normalized.
    • a2a_store.py: register_agent() still stores kind directly from payload, so None or arbitrary strings can be persisted.
    • listener.py: push_timeline_card() failure handling may miss non-2xx responses because requests.post() does not raise by default, which can advance the watermark and drop a card.
    • agents/base.py: truncate_for_reply() is reported to ignore its limit parameter.
  • A still-open functional risk remains in the trade path: TradeDeskAgent.execute_action() is still described as needing an idempotency guard so repeated approvals don’t double-fill.
  • The earlier batch of CodeRabbit findings shows several items were addressed in commit 2380fea, including get_timeline_item() exception handling, send_a2a_message() failure handling, routing by earliest handle occurrence, thread-safe get_team(), and the duplicate-fill regression test.
🔇 Additional comments (4)
timeline_server.py (1)

44-46: LGTM!

a2a_store.py (1)

91-107: LGTM!

agents/broker.py (1)

97-100: LGTM!

listener.py (1)

61-64: LGTM!

Also applies to: 94-114, 178-185

Comment thread mcp_dispatcher.py
…reply budget

Address CodeRabbit review of 93f717c:
- dispatcher no longer marks processed_action when execute_action raised
  or the timeline item couldn't be loaded, so a transient broker/ledger
  failure can't consume an approval; missing item context also fails
  closed instead of reaching the generic executor
- truncate_for_reply budgets by len(suffix) instead of a hardcoded 30,
  so replies never exceed the requested limit for any suffix

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTLFzAjoSosiPfHGaXSZkP
Copilot AI review requested due to automatic review settings July 1, 2026 23:52
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🔍 PR Validation

⚠️ Large PR detected (1350 lines changed)

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comment thread mcp_dispatcher.py
Comment on lines +178 to +182
if result is None:
result = (
f"Action '{action}' not handled by agent {owned_agent_id}; "
"nothing executed (owned cards never use the generic fallback)."
)
The dispatcher fails closed for member-owned cards, which silently
no-op'd Approve/Reject/Snooze on the general agent's mention cards
(agent_id x-agent, no execute_action). GeneralAgent now runs the legacy
generic Grok workflow itself, restoring pre-team behavior without
weakening fail-closed for trade/purchase cards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTLFzAjoSosiPfHGaXSZkP
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🔍 PR Validation

⚠️ Large PR detected (1367 lines changed)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
mcp_dispatcher.py (1)

184-198: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Don’t consume the action when Grok never ran.

call_grok() returns "Missing XAI_API_KEY." without executing anything, but Line 197 still writes processed_action. That burns the timeline action even though the legacy executor did nothing. Set execution_failed = True for that failure path before updating the item.

Fix the failure marker before persisting action state
-                result = call_grok(prompt)
+                result = call_grok(prompt)
+                if result == "Missing XAI_API_KEY.":
+                    execution_failed = True
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp_dispatcher.py` around lines 184 - 198, In the legacy Grok fallback inside
the MCP dispatcher flow, `call_grok()` can return the “Missing XAI_API_KEY.”
failure without actually running anything, but `update_timeline_item()` still
records `processed_action`. Update the `result is None` fallback path in
`mcp_dispatcher.py` to detect that missing-key response and set
`execution_failed = True` before building the `update` dict, so the
`processed_action` field is not persisted when Grok never executed.
agents/team/tradedesk.py (1)

116-120: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject non-finite quantities in agents/team/tradedesk.py:116-120. float() accepts nan and inf, and quantity <= 0 still lets both nan and +inf through. PaperBroker.execute() writes that value straight into the JSON ledger, and positions() later sums it, so one bad card poisons local fills and position totals. Add math.isfinite(quantity) before broker.execute().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agents/team/tradedesk.py` around lines 116 - 120, The trade quantity
validation in tradedesk should reject non-finite values, because
float(metadata.get("quantity", 1)) can produce nan or inf and the existing
quantity <= 0 check still allows them through. Update the validation in the
quantity parsing flow before PaperBroker.execute() is called so it also checks
math.isfinite(quantity), alongside the existing TypeError/ValueError handling
and positive-quantity check, using the nearby symbols execute(), positions(),
and the quantity assignment block to locate the fix.

Source: Learnings

♻️ Duplicate comments (1)
agents/team/tradedesk.py (1)

122-128: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Approval still has no actor check before the fill.

author_id is stored on the card, but execute_action() never receives or verifies the user who clicked Approve. Any timeline_action that reaches this path can execute broker.execute(). Pass the approver identity from the timeline action and reject anything outside the requester/authorized approver set before Line 128.

#!/bin/bash
set -euo pipefail

rg -n -C3 'author_id|approver|actor|timeline_action|execute_action|broker\.execute' \
  timeline_server.py mcp_dispatcher.py agents/team/tradedesk.py agents 2>/dev/null || true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agents/team/tradedesk.py` around lines 122 - 128, Approval handling in
execute_action() and the broker.execute() path currently lacks an actor
authorization check, so any timeline_action can trigger a fill. Update the
tradedesk approval flow to accept the approver identity from the timeline
action, compare it against the stored author_id/requester or other authorized
approver set before the fill is submitted, and reject unauthorized approvals
before calling self.broker.execute(). Use the execute_action() method and the
broker.execute() callsite as the main anchors when updating the approval logic.

Source: MCP tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@listener.py`:
- Around line 136-151: The recovery path in listener.py is swallowing failures
from push_timeline_card, which can let create_tweet() failures fall through and
be treated as handled. Update the failure handling around the push_timeline_card
call in the reply-posting flow so that if the safety card cannot be created and
no earlier safety card was successfully pushed, the code logs the exception and
returns False instead of continuing to the final success path in the mention
handling logic. Use the existing create_tweet(), push_timeline_card(), and
mention/reply handling branch to locate the fix.

---

Outside diff comments:
In `@agents/team/tradedesk.py`:
- Around line 116-120: The trade quantity validation in tradedesk should reject
non-finite values, because float(metadata.get("quantity", 1)) can produce nan or
inf and the existing quantity <= 0 check still allows them through. Update the
validation in the quantity parsing flow before PaperBroker.execute() is called
so it also checks math.isfinite(quantity), alongside the existing
TypeError/ValueError handling and positive-quantity check, using the nearby
symbols execute(), positions(), and the quantity assignment block to locate the
fix.

In `@mcp_dispatcher.py`:
- Around line 184-198: In the legacy Grok fallback inside the MCP dispatcher
flow, `call_grok()` can return the “Missing XAI_API_KEY.” failure without
actually running anything, but `update_timeline_item()` still records
`processed_action`. Update the `result is None` fallback path in
`mcp_dispatcher.py` to detect that missing-key response and set
`execution_failed = True` before building the `update` dict, so the
`processed_action` field is not persisted when Grok never executed.

---

Duplicate comments:
In `@agents/team/tradedesk.py`:
- Around line 122-128: Approval handling in execute_action() and the
broker.execute() path currently lacks an actor authorization check, so any
timeline_action can trigger a fill. Update the tradedesk approval flow to accept
the approver identity from the timeline action, compare it against the stored
author_id/requester or other authorized approver set before the fill is
submitted, and reject unauthorized approvals before calling
self.broker.execute(). Use the execute_action() method and the broker.execute()
callsite as the main anchors when updating the approval logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9dead3f6-3d47-4854-ba58-b81c44fdd21d

📥 Commits

Reviewing files that changed from the base of the PR and between 93f717c and 411647f.

📒 Files selected for processing (5)
  • agents/base.py
  • agents/registry.py
  • agents/team/tradedesk.py
  • listener.py
  • mcp_dispatcher.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • groupthinking/uvai-skills (manual)
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: copilot-pull-request-reviewer
⚠️ CI failures not shown inline (2)

GitHub Actions: 🤖 Autonomous Agent CI/CD / 🏷️ Auto-Label PRs: feat: add @handle-routed agent team (interactive agents + API bots)

Conclusion: failure

View job details

##[group]Run actions/labeler@v5
 with:
   repo-***REDACTED***
   configuration-path: .github/labeler.yml
   sync-labels: false
   dot: true
 ##[endgroup]
 The configuration file (path: .github/labeler.yml) was not found locally, fetching via the api
 ##[warning]The config file was not found at .github/labeler.yml. Make sure it exists and that this action has the correct access rights.
 ##[error]HttpError: Not Found

GitHub Actions: 🤖 Autonomous Agent CI/CD / 2_🏷️ Auto-Label PRs.txt: feat: add @handle-routed agent team (interactive agents + API bots)

Conclusion: failure

View job details

##[group]Run actions/labeler@v5
 with:
   repo-***REDACTED***
   configuration-path: .github/labeler.yml
   sync-labels: false
   dot: true
 ##[endgroup]
 The configuration file (path: .github/labeler.yml) was not found locally, fetching via the api
 ##[warning]The config file was not found at .github/labeler.yml. Make sure it exists and that this action has the correct access rights.
 ##[error]HttpError: Not Found
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:54:18.822Z
Learning: Trades are paper-only: `PaperBroker` must record simulated fills locally and must not send real exchange orders.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:54:18.822Z
Learning: Purchases are approval-gated intents only; no payment adapter is wired in by default.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-01T23:54:18.822Z
Learning: All side-effecting actions must be approval-gated before the dispatcher executes them.
🪛 Ruff (0.15.20)
listener.py

[warning] 136-151: Use contextlib.suppress(Exception) instead of try-except-pass

Replace try-except-pass with with contextlib.suppress(Exception): ...

(SIM105)


[error] 150-151: try-except-pass detected, consider logging the exception

(S110)


[warning] 150-150: Do not catch blind exception: Exception

(BLE001)

🔍 Remote MCP GitHub Copilot

Relevant PR context

  • PR #91 in groupthinking/MyXstack is open on claude/myxstack-agent-merge-myaock into main, with 24 files changed and mergeable_state: unstable.
  • Copilot review status is not clean: the PR has multiple CHANGES_REQUESTED reviews and the latest review on commit 93f717c... is still pending/active.

Open review risks still visible from the tool output

  • agents/team/tradedesk.py: trade approval still needs stronger safety checks; Copilot flags unresolved concerns around duplicate execution/idempotency, approver identity, and validation of ticker/side/quantity.
  • mcp_dispatcher.py: review comments still flag replay/idempotency issues and a fail-closed concern when timeline item context is missing.
  • listener.py: review comments still flag watermark/mention-loss risks when reply or card posting fails.
  • agents/base.py: truncate_for_reply() is still flagged for not honoring its limit correctly.
  • timeline_server.py and a2a_store.py: kind handling is still flagged as needing stricter normalization/validation to prevent null or arbitrary strings.

What appears addressed already

  • Earlier comments marked addressed in commit 2380fea include: earliest-handle routing in agents/router.py, thread-safe get_team(), retries in register_team(), send_a2a_message() failure handling, get_timeline_item() exception handling, and duplicate-fill regression coverage.
🔇 Additional comments (2)
agents/base.py (1)

92-96: LGTM!

agents/registry.py (1)

75-79: LGTM!

Comment thread listener.py Outdated
…nite quantities

Address CodeRabbit review of 411647f:
- listener logs recovery-card failures and holds the watermark for
  reply-only mentions whose reply AND recovery card both failed, so they
  retry instead of vanishing
- dispatcher doesn't mark processed_action when the legacy Grok fallback
  returned its missing-API-key sentinel without executing anything
- Tradedesk rejects non-finite quantities (nan/inf) so a bad card can't
  poison the paper ledger or position totals

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTLFzAjoSosiPfHGaXSZkP
Copilot AI review requested due to automatic review settings July 2, 2026 00:02
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🔍 PR Validation

⚠️ Large PR detected (1378 lines changed)

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Comment thread agents/base.py
Comment on lines +134 to +150
timeline_url = os.getenv("TIMELINE_API_URL", "http://127.0.0.1:8080")
try:
requests.post(
f"{timeline_url}/v1/a2a/messages",
json={
"from": from_agent,
"to": to,
"type": message_type,
"content": content,
"metadata": metadata or {},
},
timeout=10,
)
return True
except requests.RequestException as exc:
print(f"Could not send A2A message to {to}: {exc}", flush=True)
return False
Comment thread agents/team/tradedesk.py
Comment on lines +40 to +48
def parse_trade_command(text: str) -> Optional[Dict[str, Any]]:
match = _TICKER_FIRST.search(text) or _SIDE_FIRST.search(text)
if not match:
return None
return {
"ticker": match.group("ticker").upper(),
"side": match.group("side").lower(),
"quantity": float(match.group("qty")) if match.group("qty") else 1.0,
}
…ero/infinite quantities

Address Copilot re-review:
- send_a2a_message raises on 4xx/5xx so callers can't mistake a rejected
  message for a delivered one
- parse_trade_command rejects quantities that are zero or float to inf
  (huge digit strings), which would otherwise break JSON card
  serialization or produce invalid proposals

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTLFzAjoSosiPfHGaXSZkP
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🔍 PR Validation

⚠️ Large PR detected (1390 lines changed)

@groupthinking
groupthinking merged commit d76153f into main Jul 2, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

copilot-rabbit documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants