Skip to content

fix: A2A send reports HTTP errors as failures, trade parser rejects zero/infinite quantities - #106

Open
groupthinking wants to merge 12 commits into
mainfrom
claude/myxstack-agent-merge-myaock
Open

fix: A2A send reports HTTP errors as failures, trade parser rejects zero/infinite quantities#106
groupthinking wants to merge 12 commits into
mainfrom
claude/myxstack-agent-merge-myaock

Conversation

@groupthinking

Copy link
Copy Markdown
Owner

No description provided.

claude added 10 commits July 1, 2026 23:07
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
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
…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
… 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
…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
…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
…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
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
…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
…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
Copilot AI review requested due to automatic review settings July 24, 2026 22:41
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1eece840-2d47-4216-a50e-f2c275fd4126

📥 Commits

Reviewing files that changed from the base of the PR and between 8005bb6 and 833f189.

📒 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
 _____________________________________
< No code too small, no bug too tall. >
 -------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • 🔄 Committing to branch...
🧪 Generate unit tests (beta)

✅ Unit Tests committed locally.

  • 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.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Note

Docstrings generation - SUCCESS
Generated docstrings and committed to branch claude/myxstack-agent-merge-myaock (commit: 5635c781c66d5178ff61f49c620bacb20c72529c)

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Note

Unit test generation is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

Docstrings generation was requested by @groupthinking.

The following files were modified:

* `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/tickerbot.py`
* `agents/team/tradedesk.py`
* `listener.py`
* `mcp_dispatcher.py`

These files were kept as they were:
* `tests/test_broker.py`
* `tests/test_router.py`
* `tests/test_tradedesk.py`

These file types are not supported:
* `Makefile`
* `README.md`
* `docs/AGENTS.md`
* `env.example`
* `pytest.ini`
* `requirements-dev.txt`

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” architecture for the Python listener/dispatcher pipeline, adds a paper-trading TradeDesk agent (with stricter trade parsing), and hardens approval-gated workflows so HTTP failures don’t silently drop safety-critical timeline artifacts.

Changes:

  • Add a team registry/router and multiple members (TradeDesk/Research/Shopping/TickerBot + fallback GeneralAgent) with kind classification (agent vs bot).
  • Make mention processing safety-first by pushing timeline cards before X replies and surfacing timeline HTTP failures as hard failures (retry by holding watermark).
  • Add paper broker + TradeDesk parsing/execution semantics and a pytest-based Python test suite.

Reviewed changes

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

Show a summary per file
File Description
timeline_server.py Adds kind to agent registration payload validation (agent vs bot).
a2a_store.py Normalizes/persists kind and updates existing agent records on re-registration.
mcp_dispatcher.py Routes approved actions to owning team members; adds processed-action tracking and timeline item lookup.
listener.py Routes mentions to team members; makes timeline card push fail-fast; processes mentions oldest-first with watermark semantics.
agents/base.py Introduces core team types (profiles/replies), Grok helper, and A2A send helper with HTTP error raising.
agents/registry.py Defines team roster, routing helpers, and timeline A2A registration with retry/backoff.
agents/router.py Implements earliest-@handle routing with case-insensitive, word-bounded matching.
agents/broker.py Adds a paper broker with idempotent fills + ledger locking and recovery of corrupt ledgers.
agents/team/tradedesk.py Adds deterministic trade parsing (rejects 0/inf qty), approval card creation, and approval execution via PaperBroker.
agents/team/tickerbot.py Adds deterministic “bot” member for cashtag lookup.
agents/team/research.py Adds research member that replies concisely + logs full brief to timeline.
agents/team/shopping.py Adds shopping member that logs approval-gated purchase intents (no executor).
agents/team/general.py Adds fallback general Grok-backed behavior for untagged mentions.
agents/init.py Initializes agents package.
agents/team/init.py Initializes agents.team package.
tests/test_tradedesk.py Adds unit tests for TradeDesk parsing, card creation, and execution idempotency.
tests/test_router.py Adds routing tests (case-insensitive, word boundary, earliest tag wins, kind classification).
tests/test_broker.py Adds tests for PaperBroker fills, positions aggregation, idempotency keying, and corrupt-ledger preservation.
requirements-dev.txt Adds dev requirements for running pytest.
pytest.ini Configures pytest discovery and python path.
Makefile Adds make test target to provision venv and run pytest.
README.md Documents the agent team concept and routing examples.
env.example Adds agent team configuration env vars (handles, paper ledger path, Grok toggle).
docs/AGENTS.md Adds detailed documentation for team members, classification, flow, and safety defaults.
Comments suppressed due to low confidence (2)

mcp_dispatcher.py:201

  • The dispatcher records processed_action via update_timeline_item(), but requests.patch() does not raise on 4xx/5xx. If the timeline server returns an HTTP error, this code will still advance the last-seen watermark and the action may be effectively dropped without any signal. Consider performing the PATCH here with raise_for_status() (or updating update_timeline_item() to do so) and at least logging failures.
                    try:
                        result = owner.execute_action(item, action)
                    except Exception as exc:
                        execution_failed = True

mcp_dispatcher.py:207

  • send_message() posts the A2A result but does not check for HTTP errors. Since requests.post() won’t raise on 4xx/5xx, delivery failures can be silently ignored while the dispatcher advances its watermark. Consider issuing the POST with raise_for_status() (or updating send_message() accordingly) so failed deliveries are at least logged.
                # Fail closed: a card owned by a member must never fall
                # through to the generic Grok executor, or the member's
                # safety policy (paper-only trades, intent-only purchases)
                # would be bypassed.
                if result is None:

Comment thread mcp_dispatcher.py
Comment on lines +38 to +41
response = requests.get(f"{timeline_url}/v1/timeline/items/{item_id}", timeout=10)
if response.status_code != 200:
return None
return response.json()
Copilot AI review requested due to automatic review settings July 24, 2026 22:47

@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

🤖 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 `@a2a_store.py`:
- Around line 91-107: Update DEFAULT_AGENTS to include kind: "agent" for every
seeded record, and modify _read_store to normalize existing persisted agents by
assigning "agent" when kind is missing or invalid, then persist the normalized
data before returning it. Ensure all registry reads return agents with a valid
kind even when they never re-register.

In `@agents/broker.py`:
- Around line 20-37: Update _ledger_file_lock so it retains cross-process
serialization when fcntl is unavailable; replace the current unlocked yield with
a portable inter-process locking mechanism, or explicitly enforce single-process
broker operation on those platforms. Preserve the existing lock-file lifecycle
and exclusive critical-section behavior.
- Around line 44-58: Update _read() to validate that parsed JSON is a list of
valid fill objects with the required schema before returning it. Route non-list
values and malformed list entries through the same corrupt-ledger backup path
used for JSONDecodeError, preserving the original content and returning an empty
ledger; ensure valid fill lists remain unchanged.

In `@agents/team/general.py`:
- Around line 63-69: Update the prompt passed to grok_chat in the approval
execution flow to include item["body"], which contains the approved mention
stored earlier. Wrap the body with wrap_untrusted() before inserting it into the
executor prompt, while preserving the existing action, title, and concise status
instructions.

In `@agents/team/tradedesk.py`:
- Around line 27-48: Update _TICKER_FIRST and _SIDE_FIRST so the optional
quantity captures the complete numeric token, including exponent notation and
trailing non-whitespace characters, rather than partially matching prefixes. In
parse_trade_command, convert the captured token inside error handling and return
None on conversion failure before applying the existing math.isfinite and
positive checks, ensuring malformed quantities such as 1e309 and 10abc are
rejected.
- Around line 77-83: In handle_mention, wrap the optional grok_chat enrichment
in a targeted exception handler so failures are logged and context falls back to
an empty value; preserve the valid trade proposal and continue the approval flow
instead of propagating the error to listener.py.

In `@listener.py`:
- Around line 155-158: Bound the retry path around the `if not reply.card` check
so an ambiguously failed mention cannot block watermark advancement
indefinitely. Track attempts by mention ID and, after the configured maximum,
treat the mention as handled and allow the caller to advance the watermark;
preserve retries for attempts below the limit and use the existing
duplicate-content success handling if available.
- Around line 50-64: Update push_timeline_card to accept a dedupe_key argument
and include that deterministic key in the POST request so the timeline API can
upsert repeated deliveries. Ensure all callers pass stable mention-based keys,
including the “-error” and “-failed-reply” suffixes for recovery cards, while
preserving the existing status-error handling.
- Around line 76-88: Update the mention handling flow around MentionContext and
route_mention to enforce authorization before invoking the selected member:
allow only the configured owner author ID, or otherwise mark non-owner authors
in the proposal metadata so the UI suppresses the Approve action. Ensure
unauthorized mentions cannot produce an approval-gated trade card, and use the
existing author_id captured in MentionContext.
- Around line 204-211: Update the watermark persistence in the mention polling
flow around process_mention so the saved cursor is exclusive of the last
successfully processed mention, preventing it from being replayed on the next
poll. Preserve oldest-first ordering and stop processing after a failed
process_mention; use the mention ID for deduplication if that is the existing
cursor mechanism.

In `@mcp_dispatcher.py`:
- Around line 157-164: The replay-skip branch in the dispatch loop currently
continues without notifying the requester. Before the continue after logging the
already-processed action, use send_message to return an already-processed status
to the originating UI, while preserving the existing last_seen update and skip
behavior.
- Around line 146-151: Update the timeline action guard around get_timeline_item
so any message without a valid timeline_item_id fails closed before reaching the
legacy Grok fallback or MCP tool execution. Treat missing identifiers and
unloadable identified items as execution failures, while preserving the existing
ownership path for successfully loaded timeline items.
- Around line 195-201: Update the message-processing flow around
execution_failed and the unconditional last_seen persistence so failed
executions remain retryable instead of being filtered out by the watermark. Do
not advance or persist the watermark for failures, but add a bounded
retry/attempt cap so permanently missing timeline items cannot be retried
indefinitely; preserve normal watermark advancement for successful executions.
- Around line 165-182: Update the owned-agent execution flow around find_member
and owner.execute_action so an unresolved owned_agent_id is treated as a
retryable configuration failure: set execution_failed and return an appropriate
failure result when owner is None, preventing processed_action from being
written. Preserve the existing terminal “not handled” result only when a
resolved owner executes and returns None, and keep owned cards from reaching the
generic fallback.
- Around line 35-44: Update get_timeline_item to validate or URL-encode item_id
as a single path segment before constructing the request URL, preventing
path/query/fragment injection. Preserve the existing 404/missing-item behavior,
but propagate or distinguish other non-200 responses so callers and the retry
loop can treat transient failures differently from permanently missing items.

In `@pytest.ini`:
- Around line 1-3: Update the pytest configuration/workflow setup associated
with the [pytest] configuration to install requirements-dev.txt in addition to
requirements.txt, and remove the `|| true` suffix from the pytest command in
agent-ci.yml so test failures correctly fail the workflow.

In `@README.md`:
- Around line 18-29: Update the mention-routing flow immediately before the
“Agent Team” section to match the documented behavior: route recognized handles
to their configured interactive agents or API bots, and send untagged mentions
to the generic Grok fallback without claiming every mention creates a timeline
card. Keep the examples and classifications in the “Agent Team” section
unchanged.

In `@tests/test_broker.py`:
- Around line 30-38: Extend test_corrupt_ledger_is_preserved_not_wiped with a
regression case using valid JSON whose root is not a list, such as an object.
Verify PaperBroker backs up the original content under trades.corrupt-*,
preserves it unchanged, and continues with an empty ledger so execute() produces
the expected position; update _read() accordingly to treat every non-list root
as corrupt rather than replacing it with an empty list silently.

In `@tests/test_router.py`:
- Around line 6-8: Update the routing tests around
test_route_to_tradedesk_by_handle and the referenced tests to avoid hardcoded
production handles. Before constructing the roster, set deterministic values for
TRADEDESK_HANDLE, RESEARCH_HANDLE, and TICKERBOT_HANDLE, or inject a test roster
with explicit handles, then build each mention using those configured values
while preserving the existing route assertions.
🪄 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: 1eece840-2d47-4216-a50e-f2c275fd4126

📥 Commits

Reviewing files that changed from the base of the PR and between 8005bb6 and 833f189.

📒 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
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-24T22:41:50.637Z
Learning: Member handles are configurable through `TRADEDESK_HANDLE`, `RESEARCH_HANDLE`, `SHOPPING_HANDLE`, and `TICKERBOT_HANDLE` environment variables.
Learnt from: CR
Repo: groupthinking/MyXstack

Timestamp: 2026-07-24T22:41:50.637Z
Learning: Until separate X handles are available, all members share the listener's X account and are selected by tagged member handles.
🪛 ast-grep (0.44.1)
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/registry.py

[warning] 74-76: 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/broker.py

[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)


[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)

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)

agents/base.py

[warning] 135-145: 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)

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)

🪛 Ruff (0.15.21)
agents/router.py

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

(UP035)

agents/team/research.py

[warning] 24-24: 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/team/general.py

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

(UP035)


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

Add return type annotation: None

(ANN204)

agents/team/shopping.py

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

(UP035)


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

Add return type annotation: None

(ANN204)

agents/registry.py

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

(UP035)


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

(PLW0603)

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)

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)


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

Add return type annotation: None

(ANN204)

mcp_dispatcher.py

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

(BLE001)

agents/team/tradedesk.py

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

(UP035)


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

Add return type annotation: None

(ANN204)


[warning] 109-109: Too many return statements (9 > 6)

(PLR0911)

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)


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

(TRY300)

listener.py

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

(BLE001)


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

(TRY300)


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

(BLE001)


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

(BLE001)


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

(BLE001)


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

(BLE001)

🔍 Remote MCP GitHub Copilot
  • PR #106 is open and non-draft, with mergeable_state: dirty, 24 changed files, 10 commits, and a net +1301/−89 lines; the head branch is claude/myxstack-agent-merge-myaock and review was requested from Copilot.
  • Current PR activity shows only the autogenerated CodeRabbit “review in progress” comment; get_reviews returned no reviews, validate passed, and copilot-pull-request-reviewer is still in progress.
  • The core refactor introduces a routed agent team: listener.py now registers the team, routes mentions by handle, writes timeline cards with fail-fast error handling, and processes mentions oldest-first; mcp_dispatcher.py now fetches the referenced timeline item, fails closed if it can’t load it, and only marks actions processed after successful execution.
  • Trade-related behavior is tightened: agents/team/tradedesk.py rejects zero/non-finite quantities and uses PaperBroker with duplicate-key idempotency; agents/broker.py atomically writes the paper ledger, preserves corrupted ledgers as timestamped backups, and aggregates positions from fills.
  • Classification plumbing was added across a2a_store.py and timeline_server.py so member kind is normalized/required as agent or bot, and new tests cover broker behavior, router matching/fallback, and trade parsing/approval/rejection flows.
🔇 Additional comments (22)
Makefile (1)

1-1: LGTM!

Also applies to: 30-32

requirements-dev.txt (1)

1-2: LGTM!

agents/team/__init__.py (1)

1-1: LGTM!

tests/test_router.py (1)

16-19: LGTM!

Also applies to: 22-24, 33-39, 49-52

agents/__init__.py (1)

1-1: LGTM!

agents/base.py (1)

1-152: LGTM!

agents/team/general.py (1)

21-55: LGTM!

agents/team/research.py (1)

1-56: LGTM!

agents/team/shopping.py (1)

1-78: LGTM!

docs/AGENTS.md (1)

1-88: LGTM!

env.example (1)

55-66: LGTM!

tests/test_broker.py (1)

4-27: LGTM!

tests/test_tradedesk.py (1)

1-132: LGTM!

agents/team/tickerbot.py (1)

15-38: LGTM!

agents/team/tradedesk.py (1)

109-140: LGTM!

agents/router.py (1)

9-28: LGTM!

agents/registry.py (2)

14-54: LGTM!


57-85: LGTM!

timeline_server.py (1)

2-2: LGTM!

Also applies to: 38-47

listener.py (2)

11-13: LGTM!


169-169: LGTM!

mcp_dispatcher.py (1)

13-13: LGTM!

Comment thread a2a_store.py
Comment on lines +91 to +107
# Classification: "agent" (interactive, LLM-backed, autonomous) vs
# "bot" (deterministic function executor). Normalized so null or
# unknown values can never enter the registry.
"kind": payload.get("kind") if payload.get("kind") in ("agent", "bot") else "agent",
"tags": payload.get("tags", []),
"created_at": _utc_now(),
}
with A2A_STORE_LOCK:
data = _read_store()
if any(existing.get("id") == agent["id"] for existing in data["agents"]):
return agent
for existing in data["agents"]:
if existing.get("id") == agent["id"]:
# Re-registration updates mutable fields so seeded records
# (e.g. x-agent without kind) don't stay stale forever.
for field in ("name", "description", "status", "endpoint", "kind", "tags"):
existing[field] = agent[field]
_write_store(data)
return existing

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Normalize existing registry records too.

This only normalizes incoming registrations. The seeded agents at Lines 12-37 and old persisted records can still be returned without kind; several will never re-register. Normalize and persist kind: "agent" during store loading, and add it to DEFAULT_AGENTS.

🤖 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 91 - 107, Update DEFAULT_AGENTS to include kind:
"agent" for every seeded record, and modify _read_store to normalize existing
persisted agents by assigning "agent" when kind is missing or invalid, then
persist the normalized data before returning it. Ensure all registry reads
return agents with a valid kind even when they never re-register.

Comment thread agents/broker.py
Comment on lines +20 to +37
@contextmanager
def _ledger_file_lock(ledger_path: Path):
"""Cross-process advisory lock so concurrent dispatchers can't corrupt
the ledger (fcntl is Unix-only; degrades to the thread lock elsewhere)."""
ledger_path.parent.mkdir(parents=True, exist_ok=True)
try:
import fcntl
except ImportError:
yield
return
lock_file = ledger_path.with_suffix(".lock")
with open(lock_file, "w") as handle:
fcntl.flock(handle, fcntl.LOCK_EX)
try:
yield
finally:
fcntl.flock(handle, fcntl.LOCK_UN)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not drop the cross-process lock on non-Unix hosts.

When fcntl is unavailable, two dispatcher processes can both read the same ledger and race os.replace; the last writer wins and an approved fill disappears. Use a portable inter-process lock or explicitly restrict this broker to a single process on those platforms.

🧰 Tools
🪛 ast-grep (0.44.1)

[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)

🪛 Ruff (0.15.21)

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

(ANN202)

🤖 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 20 - 37, Update _ledger_file_lock so it
retains cross-process serialization when fcntl is unavailable; replace the
current unlocked yield with a portable inter-process locking mechanism, or
explicitly enforce single-process broker operation on those platforms. Preserve
the existing lock-file lifecycle and exclusive critical-section behavior.

Comment thread agents/broker.py
Comment on lines +44 to +58
def _read(self) -> List[Dict[str, Any]]:
if not self.ledger_path.exists():
return []
try:
data = json.loads(self.ledger_path.read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
# Never silently discard trade history: preserve the corrupt
# file for reconciliation and start a fresh ledger.
backup = self.ledger_path.with_suffix(
f".corrupt-{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}"
)
self.ledger_path.rename(backup)
print(f"Ledger corrupt ({exc}); preserved as {backup}", flush=True)
return []
return data if isinstance(data, list) else []

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve semantically corrupted ledgers instead of overwriting them.

Valid JSON that is not a fill list ({}, [1], malformed fill objects) bypasses the backup path. {} is silently replaced on the next execution, while [1] crashes at existing.get(...). Validate the list and fill schema in _read(), then preserve invalid content through the same corrupt-backup path.

🤖 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 44 - 58, Update _read() to validate that
parsed JSON is a list of valid fill objects with the required schema before
returning it. Route non-list values and malformed list entries through the same
corrupt-ledger backup path used for JSONDecodeError, preserving the original
content and returning an empty ledger; ensure valid fill lists remain unchanged.

Comment thread agents/team/general.py
Comment on lines +63 to +69
result = grok_chat(
f"You are a workflow agent. A user took the action '{action}' on "
f"timeline item {item.get('id', '?')} titled "
f"'{item.get('title', '')}'.\n"
"Use MCP tools to execute any required external steps. "
"Return a concise status update."
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Pass the approved request to the executor.

This prompt contains the action and title, but not item["body"], where Line 45 stored the mention. Approval actions are therefore context-free and can execute the wrong thing—or nothing. Include the card body with wrap_untrusted().

Proposed fix
+        request = wrap_untrusted(str(item.get("body", "")))
         result = grok_chat(
             f"You are a workflow agent. A user took the action '{action}' on "
             f"timeline item {item.get('id', '?')} titled "
             f"'{item.get('title', '')}'.\n"
             "Use MCP tools to execute any required external steps. "
-            "Return a concise status update."
+            "Return a concise status update.\n\n"
+            f"Approved request:\n{request}"
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
result = grok_chat(
f"You are a workflow agent. A user took the action '{action}' on "
f"timeline item {item.get('id', '?')} titled "
f"'{item.get('title', '')}'.\n"
"Use MCP tools to execute any required external steps. "
"Return a concise status update."
)
request = wrap_untrusted(str(item.get("body", "")))
result = grok_chat(
f"You are a workflow agent. A user took the action '{action}' on "
f"timeline item {item.get('id', '?')} titled "
f"'{item.get('title', '')}'.\n"
"Use MCP tools to execute any required external steps. "
"Return a concise status update.\n\n"
f"Approved request:\n{request}"
)
🤖 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/general.py` around lines 63 - 69, Update the prompt passed to
grok_chat in the approval execution flow to include item["body"], which contains
the approved mention stored earlier. Wrap the body with wrap_untrusted() before
inserting it into the executor prompt, while preserving the existing action,
title, and concise status instructions.

Comment thread agents/team/tradedesk.py
Comment on lines +27 to +48
_TICKER_FIRST = re.compile(
r"\$(?P<ticker>[A-Za-z]{1,10})\s+(?P<side>buy|sell)\b(?:\s+(?P<qty>\d+(?:\.\d+)?))?",
re.IGNORECASE,
)
# "buy $TSLA 100" — side first ($ required so plain words never match)
_SIDE_FIRST = re.compile(
r"\b(?P<side>buy|sell)\s+\$(?P<ticker>[A-Za-z]{1,10})\b(?:\s+(?P<qty>\d+(?:\.\d+)?))?",
re.IGNORECASE,
)

USAGE = "Format: @Tradedesk $TICKER buy|sell [quantity] — e.g. @Tradedesk $TSLA buy 100"


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
quantity = float(match.group("qty")) if match.group("qty") else 1.0
# Huge digit strings float to inf (breaking JSON cards) and zero is
# not a tradable quantity — treat both as unparseable.
if not math.isfinite(quantity) or quantity <= 0:
return None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject malformed numeric quantities instead of silently changing the order.

$TSLA buy 1e309 matches only 1, so this creates a one-share proposal instead of being rejected as non-finite. Inputs such as 10abc have the same partial-match problem. Capture the full quantity token, convert it, and reject conversion failures before the finite/positive check.

🤖 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 27 - 48, Update _TICKER_FIRST and
_SIDE_FIRST so the optional quantity captures the complete numeric token,
including exponent notation and trailing non-whitespace characters, rather than
partially matching prefixes. In parse_trade_command, convert the captured token
inside error handling and return None on conversion failure before applying the
existing math.isfinite and positive checks, ensuring malformed quantities such
as 1e309 and 10abc are rejected.

Comment thread mcp_dispatcher.py
Comment on lines 195 to +201
if item_id:
update_timeline_item(item_id, {"mcp_result": result})
# A failed execution must stay retryable: record the result
# but don't mark the action processed.
update = {"mcp_result": result}
if not execution_failed:
update["processed_action"] = action
update_timeline_item(item_id, update)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

"Failures stay retryable" is not what this code does — the message watermark advances anyway.

The comment claims a failed execution stays retryable because processed_action isn't written. It doesn't. At the end of this same iteration last_seen is advanced and persisted unconditionally, so the A2A message that carried the action is filtered out on every subsequent poll (created_at <= last_seen). Nothing re-reads it. Net effect of execution_failed: one missing metadata key. The approval is dropped, the user got a "Could not load timeline item" message, and the trade never executes — silently, permanently.

Either don't advance the watermark for failed executions, or persist a retry queue. If you go with the former, bound it — a deleted card would otherwise re-fail every 5 seconds forever:

Proposed change
             if item_id:
-                # A failed execution must stay retryable: record the result
-                # but don't mark the action processed.
                 update = {"mcp_result": result}
                 if not execution_failed:
                     update["processed_action"] = action
+                else:
+                    attempts = int(item_meta.get("execution_attempts", 0)) + 1
+                    update["execution_attempts"] = attempts
                 update_timeline_item(item_id, update)
+
+            if execution_failed and attempts < MAX_ACTION_ATTEMPTS:
+                # Hold the watermark so this action is re-read next poll.
+                send_message(...)
+                time.sleep(RETRY_BACKOFF_SECONDS)
+                break

(sketch — the point is that the watermark advance below has to be conditional, with an attempt cap so a permanently-missing card doesn't become a retry storm.)

🤖 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 195 - 201, Update the message-processing flow
around execution_failed and the unconditional last_seen persistence so failed
executions remain retryable instead of being filtered out by the watermark. Do
not advance or persist the watermark for failures, but add a bounded
retry/attempt cap so permanently missing timeline items cannot be retried
indefinitely; preserve normal watermark advancement for successful executions.

Comment thread pytest.ini
Comment on lines +1 to +3
[pytest]
pythonpath = .
testpaths = tests

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== pytest.ini ==\n'
cat -n pytest.ini

printf '\n== workflow files mentioning pytest ==\n'
rg -n "pytest|requirements-dev|requirements.txt|\|\| true|make test" .github Makefile requirements*.txt pytest.ini -S || true

printf '\n== list relevant files ==\n'
fd -a 'agent-ci\.yml|Makefile|requirements-dev\.txt|requirements\.txt' . .github 2>/dev/null || true

Repository: groupthinking/MyXstack

Length of output: 1480


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== .github/workflows/agent-ci.yml ==\n'
cat -n .github/workflows/agent-ci.yml

printf '\n== requirements files ==\n'
for f in requirements.txt requirements-dev.txt; do
  echo "--- $f ---"
  cat -n "$f"
done

printf '\n== Makefile ==\n'
[ -f Makefile ] && cat -n Makefile || true

Repository: groupthinking/MyXstack

Length of output: 9957


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Waiting for file inspection results."

Repository: groupthinking/MyXstack

Length of output: 197


Install the dev test deps and stop swallowing pytest failures
.github/workflows/agent-ci.yml only installs requirements.txt and then runs pytest ... || true, so a bad test run can still greenlight the workflow. Install requirements-dev.txt here and let pytest fail the job.

🤖 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 `@pytest.ini` around lines 1 - 3, Update the pytest configuration/workflow
setup associated with the [pytest] configuration to install requirements-dev.txt
in addition to requirements.txt, and remove the `|| true` suffix from the pytest
command in agent-ci.yml so test failures correctly fail the workflow.

Source: MCP tools

Comment thread README.md
Comment on lines +18 to +29
### Agent Team

Mentions are routed to a **team of @handle-addressable members** (see [docs/AGENTS.md](docs/AGENTS.md)):

```text
@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)
```

Members are classified as **interactive agents** (`kind: agent` — conversational, LLM-backed, can delegate over A2A) or **API bots** (`kind: bot` — deterministic input → function → output). Untagged mentions fall back to the original generic Grok behavior.

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

Fix the stale architecture flow above this section.

The new routing docs say TickerBot is deterministic and untagged mentions use Grok, but README.md Line 16 still claims every mention goes to Grok and creates a timeline card. Update that flow or users will configure against the wrong behavior.

🤖 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 18 - 29, Update the mention-routing flow immediately
before the “Agent Team” section to match the documented behavior: route
recognized handles to their configured interactive agents or API bots, and send
untagged mentions to the generic Grok fallback without claiming every mention
creates a timeline card. Keep the examples and classifications in the “Agent
Team” section unchanged.

Comment thread tests/test_broker.py
Comment on lines +30 to +38
def test_corrupt_ledger_is_preserved_not_wiped(tmp_path):
path = tmp_path / "trades.json"
path.write_text("{not json", encoding="utf-8")
broker = PaperBroker(str(path))
broker.execute("TSLA", "buy", 1)
backups = list(tmp_path.glob("trades.corrupt-*"))
assert len(backups) == 1
assert backups[0].read_text(encoding="utf-8") == "{not json"
assert broker.positions() == {"TSLA": 1.0}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve schema-invalid ledgers too.

This only covers malformed JSON. The current _read() also treats valid non-list JSON as [], then execute() overwrites it without a backup. Treat every non-list root as corrupt and add this regression case.

Regression test
+def test_non_list_ledger_is_preserved_not_wiped(tmp_path):
+    path = tmp_path / "trades.json"
+    path.write_text('{"legacy": "data"}', encoding="utf-8")
+    broker = PaperBroker(str(path))
+    broker.execute("TSLA", "buy", 1)
+    backups = list(tmp_path.glob("trades.corrupt-*"))
+    assert len(backups) == 1
+    assert backups[0].read_text(encoding="utf-8") == '{"legacy": "data"}'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_corrupt_ledger_is_preserved_not_wiped(tmp_path):
path = tmp_path / "trades.json"
path.write_text("{not json", encoding="utf-8")
broker = PaperBroker(str(path))
broker.execute("TSLA", "buy", 1)
backups = list(tmp_path.glob("trades.corrupt-*"))
assert len(backups) == 1
assert backups[0].read_text(encoding="utf-8") == "{not json"
assert broker.positions() == {"TSLA": 1.0}
def test_corrupt_ledger_is_preserved_not_wiped(tmp_path):
path = tmp_path / "trades.json"
path.write_text("{not json", encoding="utf-8")
broker = PaperBroker(str(path))
broker.execute("TSLA", "buy", 1)
backups = list(tmp_path.glob("trades.corrupt-*"))
assert len(backups) == 1
assert backups[0].read_text(encoding="utf-8") == "{not json"
assert broker.positions() == {"TSLA": 1.0}
def test_non_list_ledger_is_preserved_not_wiped(tmp_path):
path = tmp_path / "trades.json"
path.write_text('{"legacy": "data"}', encoding="utf-8")
broker = PaperBroker(str(path))
broker.execute("TSLA", "buy", 1)
backups = list(tmp_path.glob("trades.corrupt-*"))
assert len(backups) == 1
assert backups[0].read_text(encoding="utf-8") == '{"legacy": "data"}'
🤖 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 30 - 38, Extend
test_corrupt_ledger_is_preserved_not_wiped with a regression case using valid
JSON whose root is not a list, such as an object. Verify PaperBroker backs up
the original content under trades.corrupt-*, preserves it unchanged, and
continues with an empty ledger so execute() produces the expected position;
update _read() accordingly to treat every non-list root as corrupt rather than
replacing it with an empty list silently.

Comment thread tests/test_router.py
Comment on lines +6 to +8
def test_route_to_tradedesk_by_handle():
member = route_mention(MentionContext(text="@MyXstack @Tradedesk $TSLA buy 100"))
assert member.profile.id == "tradedesk"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Stop hardcoding configurable handles in routing tests.

These tests assume Tradedesk, Research, and TickerBot are permanent handles, but production handles come from TRADEDESK_HANDLE, RESEARCH_HANDLE, and TICKERBOT_HANDLE. A configured deployment will make these tests fail or validate the wrong route. Set deterministic environment values before constructing the roster, or inject a test roster with explicit handles.

Also applies to: 11-13, 27-30, 41-46

🤖 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 6 - 8, Update the routing tests around
test_route_to_tradedesk_by_handle and the referenced tests to avoid hardcoded
production handles. Before constructing the roster, set deterministic values for
TRADEDESK_HANDLE, RESEARCH_HANDLE, and TICKERBOT_HANDLE, or inject a test roster
with explicit handles, then build each mention using those configured values
while preserving the existing route assertions.

Source: Learnings

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 +227 to +230
update = {"mcp_result": result}
if not execution_failed:
update["processed_action"] = action
update_timeline_item(item_id, update)
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

✅ Unit tests committed locally. Commit: 361b33568599e7d721d026d58cd6713e6665b10b

Copilot AI review requested due to automatic review settings July 24, 2026 22:55

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 34 out of 34 changed files in this pull request and generated 1 comment.

Comment thread agents/team/tradedesk.py
Comment on lines +153 to +156
try:
quantity = float(metadata.get("quantity", 1))
except (TypeError, ValueError):
return f"⚠️ Invalid quantity on trade card {item.get('id', '?')}; nothing executed."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants