chore: drop generated artifacts not tracked on master - #225
Conversation
📝 WalkthroughWalkthroughA2A send and read operations now support optional recipients. Recipients are validated, persisted, included in receipts and remote payloads, filterable during reads, and resolvable from configured role handles. ChangesA2A recipient support
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant HTTPServer
participant RoleResolver
participant A2AService
participant Archive
Client->>HTTPServer: POST /a2a/send with recipient
HTTPServer->>RoleResolver: resolve role recipient
RoleResolver-->>HTTPServer: holder identity or resolution error
HTTPServer->>A2AService: a2a_send recipient
A2AService->>Archive: persist recipient in event
Archive-->>A2AService: archived event
A2AService-->>Client: send receipt
Client->>A2AService: a2a_read recipient filter
A2AService->>Archive: read matching events
Archive-->>A2AService: filtered messages
A2AService-->>Client: messages with optional resolved_to
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Summary by QodoAdd A2A recipient envelope field with configurable role resolution
AI Description
Diagram
High-Level Assessment
Files changed (6)
|
| remote = _get_remote(data_dir) | ||
| if remote is not None: | ||
| # Remote servers support recipient filtering | ||
| return await remote.a2a_read(thread=thread, since=since, limit=limit, recipient=recipient) |
There was a problem hiding this comment.
CRITICAL: remote.a2a_read is called here but RemoteClient has no a2a_read method, causing AttributeError at runtime when a remote server is configured.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| """ | ||
|
|
||
| def resolve(self, role: str) -> str | None: | ||
| raise RuntimeError("no resolver configured: cannot resolve role %r", role) |
There was a problem hiding this comment.
CRITICAL: NoopRoleResolver.resolve uses %r without formatting, so the raised error message literally contains %r instead of the role name.
| raise RuntimeError("no resolver configured: cannot resolve role %r", role) | |
| raise RuntimeError(f"no resolver configured: cannot resolve role {role!r}") |
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| # If we have a resolver and recipient is a role, apply resolution | ||
| if resolver is not None and recipient.startswith("@"): | ||
| try: | ||
| resolved_to = resolver.resolve(recipient) |
There was a problem hiding this comment.
WARNING: The resolver is called here but its result is discarded (the comment acknowledges it is "for future use"). Then on line 549 it is called again to populate resolved_to. Two separate resolution calls for the same recipient can return inconsistent values if the resolver state changes between calls.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| resolved_to = resolver.resolve(recipient) | ||
| # If we need resolved_to in the message, compute it | ||
| # (This is for future use - currently we just validate that it resolves) | ||
| except Exception: |
There was a problem hiding this comment.
WARNING: When the resolver raises an exception during recipient filtering, the message is silently skipped (continue) instead of propagating the error. This is inconsistent with send-time behavior where resolver failures return 503, and it can cause messages to disappear without any indication to the caller.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
Code Review SummaryStatus: 5 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)CRITICAL
WARNING
Files Reviewed (6 files)
Fix these issues in Kilo Cloud Reviewed by step-3.7-flash · Input: 149K · Output: 33.5K · Cached: 1.6M |
Code Review by Qodo
Context used✅ Compliance rules (platform):
13 rules 1. Handles treated as roles
|
| resolved_to = resolver.resolve(recipient) | ||
| if resolved_to is None: | ||
| raise _BadRequest(f"role {recipient!r} resolves to no holder") | ||
| except Exception as exc: | ||
| # Resolver error (e.g., unreachable) -> 503 | ||
| self._send_json(503, {"error": f"role resolution failed: {exc}"}) | ||
| return |
There was a problem hiding this comment.
1. _handle_a2a_send 503 not archived 📘 Rule violation ☼ Reliability
The new role-resolution failure path returns a 503 before calling service.a2a_send(), so no archive.record() is emitted for that interaction. This violates the requirement to archive every interaction (including failure paths), creating an auditing/traceability gap.
Agent Prompt
## Issue description
`/a2a/send` requests that fail during recipient role resolution return early with a 503, skipping the normal archiving path (which happens inside `service.a2a_send()`), leaving the interaction unarchived.
## Issue Context
The compliance requirement expects archiving to occur even on error paths. The newly added `return` in the resolver-exception branch prevents reaching the `service.a2a_send()` call that writes the archive event.
## Fix Focus Areas
- taosmd/http_server.py[1526-1532]
- taosmd/service.py[382-417]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| # Only resolve if we have a resolver AND the recipient is a role | ||
| # (roles are @taOS-PA and other non-agent handles - best effort detection) | ||
| if resolver is not None: | ||
| # For now, treat any recipient starting with @ as a role for resolution | ||
| # In a real implementation, this would check against a known roles list | ||
| # or have a way to differentiate agents from roles | ||
| try: | ||
| resolved_to = resolver.resolve(recipient) | ||
| if resolved_to is None: |
There was a problem hiding this comment.
2. Handles treated as roles 🐞 Bug ≡ Correctness
In _handle_a2a_send, any recipient starting with @ is resolved via the role resolver when configured, which will reject normal agent-handle recipients (e.g. @alice) unless they exist in the role map. This contradicts the service contract that recipient may be either an agent handle or a role.
Agent Prompt
### Issue description
`taosmd/http_server._handle_a2a_send` currently calls the role resolver for **all** `@...` recipients when a resolver is configured. This makes valid direct-handle sends fail (because most role maps won’t contain every agent handle).
### Issue Context
The service layer explicitly documents that `recipient` may be a handle (agent) or a role. Role resolution should only apply to role recipients.
### Fix Focus Areas
- taosmd/http_server.py[1498-1533]
- taosmd/service.py[368-370]
### Suggested fix
- Implement a real role/handle distinction before calling `resolver.resolve(...)`.
- Minimal approach: only treat `recipient` as a role if it is present in the configured `role_map` keys (requires exposing the configured roles list from `get_resolver_from_config`, or returning `(resolver, role_map_keys)`).
- Alternative: introduce a dedicated role syntax/prefix and only resolve those.
- Ensure direct handles (`@alice`) pass validation without resolution.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| resolved_to = resolver.resolve(recipient) | ||
| if resolved_to is None: | ||
| raise _BadRequest(f"role {recipient!r} resolves to no holder") | ||
| except Exception as exc: | ||
| # Resolver error (e.g., unreachable) -> 503 | ||
| self._send_json(503, {"error": f"role resolution failed: {exc}"}) | ||
| return |
There was a problem hiding this comment.
3. Badrequest becomes 503 🐞 Bug ≡ Correctness
_handle_a2a_send raises _BadRequest for a role that resolves to no holder, but catches it in a broad except Exception and returns HTTP 503 instead of 400. This misclassifies client validation errors as server failures and will trigger incorrect retry behavior.
Agent Prompt
### Issue description
`_BadRequest` is raised inside a `try` block that catches `Exception`, so expected validation failures (e.g. role has no holder) are returned as 503.
### Issue Context
Callers should get a 400 for invalid recipient/role state, and 503 only for resolver infrastructure failures.
### Fix Focus Areas
- taosmd/http_server.py[1525-1532]
### Suggested fix
- Narrow the `try` to only the resolver call, and catch a resolver-specific exception (e.g. `ResolutionError`).
- Or add an `except _BadRequest: raise` (or return a 400) before the generic exception handler.
- Keep 503 only for true resolver failures/unreachability.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| remote = _get_remote(data_dir) | ||
| if remote is not None: | ||
| # Remote servers support recipient filtering | ||
| return await remote.a2a_read(thread=thread, since=since, limit=limit, recipient=recipient) |
There was a problem hiding this comment.
4. Remote a2a_read missing 🐞 Bug ≡ Correctness
service.a2a_read calls remote.a2a_read(...) when a remote server is configured, but RemoteClient has no a2a_read method. This will raise AttributeError at runtime for remote-mode reads.
Agent Prompt
### Issue description
`taosmd.service.a2a_read` delegates to `remote.a2a_read`, but `taosmd.remote.RemoteClient` does not implement it.
### Issue Context
Remote mode is a supported execution path for service functions. The new API must either be implemented end-to-end or explicitly rejected in remote mode.
### Fix Focus Areas
- taosmd/service.py[437-440]
- taosmd/remote.py[206-253]
### Suggested fix
One of:
1) Implement `RemoteClient.a2a_read(...)` (and corresponding server support if needed), OR
2) Change `service.a2a_read` to call existing `remote.a2a_feed(...)` and apply recipient filtering client-side (once feed output includes `recipient`), OR
3) If remote does not support it yet, raise a clear error (e.g. `NotImplementedError`) rather than calling a missing method.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| ``recipient`` is optional and may be a handle (agent @handle) or a | ||
| role (e.g., @taOS-PA). Stored verbatim (append-only, never rewritten), | ||
| returned on GET/SSE; absent = omitted. | ||
|
|
||
| Returns ``{"id", "from", "thread", "reply_to"}`` plus ``refs``, ``blocks`` | ||
| and ``recipient`` when those were supplied. |
There was a problem hiding this comment.
5. Recipient not in feed 🐞 Bug ≡ Correctness
service.a2a_send stores recipient and documents it as returned on GET/SSE reads, but service.a2a_feed (used by /a2a/messages and SSE streaming) never emits the recipient field. As a result, HTTP/SSE consumers cannot observe the new field.
Agent Prompt
### Issue description
The PR adds and stores `recipient`, and the docstring claims it is returned on GET/SSE reads, but the existing feed output path omits it.
### Issue Context
HTTP `/a2a/messages` and SSE `/a2a/stream` use `service.a2a_feed`, not the newly added `service.a2a_read`.
### Fix Focus Areas
- taosmd/service.py[368-373]
- taosmd/service.py[653-667]
- taosmd/http_server.py[1560-1563]
- taosmd/http_server.py[1611-1614]
### Suggested fix
- In `service.a2a_feed`, include `recipient` in the emitted message dict when present (mirroring the `refs`/`blocks` pattern):
- `if "recipient" in data: msg["recipient"] = data["recipient"]`
- Add/adjust tests under `tests/` to cover `recipient` roundtrip via `/a2a/messages` and SSE if required by the feature.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| async def test_recipient_send_and_read(): | ||
| """Test that recipient field is stored and returned correctly.""" | ||
| with tempfile.TemporaryDirectory() as temp_dir: | ||
| data_dir = Path(temp_dir) | ||
|
|
||
| # First send a message with recipient | ||
| print("Test 1: Send with agent recipient (@alice)") | ||
| receipt1 = await a2a_send( | ||
| sender="@test", | ||
| body="Hello agent", | ||
| thread="general", | ||
| recipient="@alice", | ||
| data_dir=data_dir | ||
| ) | ||
| assert "recipient" in receipt1, "Receipt should contain recipient" | ||
| assert receipt1["recipient"] == "@alice", f"Expected @alice, got {receipt1.get('recipient')}" | ||
| print(f" ✓ Receipt contains recipient: {receipt1['recipient']}") |
There was a problem hiding this comment.
6. Async tests unmarked 🐞 Bug ☼ Reliability
The new test_recipient_* files define async def pytest tests without @pytest.mark.asyncio, unlike existing async tests in the repo. This can cause these tests to be skipped/mishandled and undermines CI coverage for the new functionality.
Agent Prompt
### Issue description
New test files are written as async tests but don’t follow the repo’s established pytest-asyncio convention (explicit `@pytest.mark.asyncio`), and they live at repo root with script-style printing.
### Issue Context
The existing test suite uses `pytest.mark.asyncio` for async tests.
### Fix Focus Areas
- test_recipient_field.py[1-71]
- test_recipient_read.py[1-65]
- tests/test_archive.py[8-15]
### Suggested fix
- Move these tests under `tests/` (e.g. `tests/test_a2a_recipient.py`).
- Add `import pytest` and decorate async tests with `@pytest.mark.asyncio`.
- Replace `print(...)` with assertions; remove `if __name__ == "__main__"` blocks.
- Add coverage for the HTTP `/a2a/messages` output once `recipient` is propagated through `a2a_feed`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
taosmd/service.py (1)
344-354: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
recipienthas no format validation at the service layer.
http_server._handle_a2a_sendenforces@-prefix and non-empty checks before callingservice.a2a_send, but direct Python-API/MCP callers ofa2a_sendbypass that validation entirely. Sincerecipientis "stored verbatim (append-only, never rewritten)", a malformed value written through the unvalidated path can never be corrected later. Consider moving the minimal format check intoa2a_senditself so all entry points share one contract.Also applies to: 378-381
🤖 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 `@taosmd/service.py` around lines 344 - 354, Add the minimal recipient validation directly to a2a_send so every caller requires a non-empty value beginning with “@” before storing it; preserve the existing append-only, verbatim storage behavior for valid recipients and align the service-layer contract with _handle_a2a_send.
🧹 Nitpick comments (3)
test_recipient_read.py (1)
36-36: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUnnecessary
fprefix on a string with no placeholders.🧹 Proposed fix
- print(f" ✓ Receipt does not contain recipient") + print(" ✓ Receipt does not contain recipient")🤖 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 `@test_recipient_read.py` at line 36, Remove the unnecessary f-string prefix from the receipt validation print statement, keeping the message text and behavior unchanged.Source: Linters/SAST tools
taosmd/role_resolver.py (1)
98-135: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winResolver TTL cache is defeated by per-call reconstruction.
make_resolver/get_resolver_from_configbuild a brand-newConfigRoleResolver(with an empty_cache) on every invocation. Both call sites (service.a2a_readandhttp_server._handle_a2a_send) callget_resolver_from_config(data_dir)fresh per request, so the documented TTL cache — "TTL ensures rapid queries can read the latest mapping while avoiding excessive registry calls for role resolution at send time" — never actually caches anything across requests, andconfig.jsonis re-read/parsed on every send/read. Consider caching resolver instances (e.g., keyed bydata_dir, with its own short-lived invalidation) inside this module so both callers benefit automatically.🤖 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 `@taosmd/role_resolver.py` around lines 98 - 135, Cache resolver instances in get_resolver_from_config using a data_dir-keyed module-level cache so repeated service.a2a_read and http_server._handle_a2a_send calls reuse the same ConfigRoleResolver and its TTL cache. Reuse cached resolvers while the configuration remains valid, and invalidate or replace entries when config.json changes; preserve None for missing, invalid, or empty role_map configurations.taosmd/service.py (1)
443-450: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winResolver exceptions are silently swallowed, and
resolve()is called twice per matching row.Lines 517 and 549 both call
resolver.resolve(recipient)for the same row — the first call's result is never used (per the comment, "for future use"), so it's pure overhead for any resolver implementation that isn't cheaply cached. Separately, resolver failures at 448, 520-522, and 552-554 are caught with bareexcept Exceptionand turned into a silentcontinue/pass, which is inconsistent withNullSafeRoleResolver's documented "FAIL-LOUD: configured but unreachable -> 503 on role sends and role-filtered reads... Prevents silent drop or fallback" contract — here a resolver outage just silently drops matching messages from read results instead of surfacing an error. Consider reusing the firstresolve()result forresolved_to, and at least logging swallowed exceptions.Also applies to: 508-522, 546-554
🤖 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 `@taosmd/service.py` around lines 443 - 450, Update the resolver handling around the message-read path: capture the result of the first resolver.resolve(recipient) call and reuse it for resolved_to instead of resolving the same row twice. Replace silent exception swallowing in resolver initialization and both resolution blocks with contextual logging, while preserving the existing control flow and NullSafeRoleResolver fail-loud behavior where applicable.Source: Linters/SAST 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 `@taosmd/http_server.py`:
- Around line 1497-1538: Adjust the exception handling around resolver.resolve
in the recipient validation flow near resolved_to so _BadRequest raised for a
role with no holder is re-raised and reaches the dispatch-level 400 handler.
Keep genuine resolver failures mapped to the existing 503 response, preserving
the distinction between unassigned roles and unreachable or failing resolvers.
In `@taosmd/role_resolver.py`:
- Around line 38-39: Update the exception construction in resolve so the role is
interpolated into a single descriptive RuntimeError message rather than passed
as a second positional argument. Preserve the existing fail-loud behavior and
message context.
- Around line 1-6: Wrap the introductory module documentation at the top of the
role resolver module in triple-quote delimiters so it becomes a valid Python
module docstring. Preserve the existing text and ensure the following code is
parsed as Python normally.
In `@taosmd/service.py`:
- Around line 437-440: Implement a matching a2a_read method on the RemoteClient
class, using the existing remote request conventions and accepting thread,
since, limit, and recipient parameters. Route it to the remote server’s
supported A2A read/messages endpoint while preserving recipient filtering, so
the remote branch in service.py can invoke remote.a2a_read without raising
AttributeError.
- Around line 474-484: Update the recipient/thread-filtered read path using
`rows_all` so recipient resolution does not rely on the blanket `limit * 10`
fetch; query the complete candidate set when recipient filtering is applied,
while retaining the normal limit for unfiltered reads and only widening for
alias merging as needed. At the final sorting and slicing logic, select the most
recent `limit` matching rows rather than `rows[:limit]`, preserving the API’s
expected output order.
In `@test_recipient_read.py`:
- Around line 9-61: Configure pytest-asyncio auto mode or add explicit asyncio
markers so the async test functions in test_recipient_read.py lines 9-61 and
test_recipient_field.py lines 10-67 are awaited and their assertions execute;
apply the same pattern to any other unmarked async tests, using the repository’s
pytest configuration or `@pytest.mark.asyncio` consistently.
---
Outside diff comments:
In `@taosmd/service.py`:
- Around line 344-354: Add the minimal recipient validation directly to a2a_send
so every caller requires a non-empty value beginning with “@” before storing it;
preserve the existing append-only, verbatim storage behavior for valid
recipients and align the service-layer contract with _handle_a2a_send.
---
Nitpick comments:
In `@taosmd/role_resolver.py`:
- Around line 98-135: Cache resolver instances in get_resolver_from_config using
a data_dir-keyed module-level cache so repeated service.a2a_read and
http_server._handle_a2a_send calls reuse the same ConfigRoleResolver and its TTL
cache. Reuse cached resolvers while the configuration remains valid, and
invalidate or replace entries when config.json changes; preserve None for
missing, invalid, or empty role_map configurations.
In `@taosmd/service.py`:
- Around line 443-450: Update the resolver handling around the message-read
path: capture the result of the first resolver.resolve(recipient) call and reuse
it for resolved_to instead of resolving the same row twice. Replace silent
exception swallowing in resolver initialization and both resolution blocks with
contextual logging, while preserving the existing control flow and
NullSafeRoleResolver fail-loud behavior where applicable.
In `@test_recipient_read.py`:
- Line 36: Remove the unnecessary f-string prefix from the receipt validation
print statement, keeping the message text and behavior unchanged.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 388935a4-22cb-4120-a383-5b887c5d4d05
📒 Files selected for processing (6)
taosmd/http_server.pytaosmd/remote.pytaosmd/role_resolver.pytaosmd/service.pytest_recipient_field.pytest_recipient_read.py
|
|
||
| # Recipient validation: send-time resolution for roles when a resolver is configured. | ||
| # If recipient is a role (starts with @) and a resolver is configured, | ||
| # check the role resolves (exists, exactly one holder), 400 if not. | ||
| # Do NOT store the resolved identity - binding happens at delivery. | ||
| resolved_to = None | ||
| if recipient is not None: | ||
| # Recipient is optional; if present, validate format (handle or role prefix) | ||
| if not isinstance(recipient, str) or not recipient: | ||
| raise _BadRequest("'recipient' must be a non-empty string when provided") | ||
| # Validate recipient format: must start with @ for agents/roles, optionally followed by more | ||
| if not recipient.startswith("@"): | ||
| raise _BadRequest("'recipient' must be a handle (e.g., '@alice') or role (e.g., '@taOS-PA')") | ||
|
|
||
| # Load resolver from config if it exists | ||
| try: | ||
| from .role_resolver import get_resolver_from_config | ||
| resolver = get_resolver_from_config(data_dir) | ||
| except Exception: | ||
| # If the resolver fails to load (e.g., import error), fallback to None | ||
| resolver = None | ||
|
|
||
| # Only resolve if we have a resolver AND the recipient is a role | ||
| # (roles are @taOS-PA and other non-agent handles - best effort detection) | ||
| if resolver is not None: | ||
| # For now, treat any recipient starting with @ as a role for resolution | ||
| # In a real implementation, this would check against a known roles list | ||
| # or have a way to differentiate agents from roles | ||
| try: | ||
| resolved_to = resolver.resolve(recipient) | ||
| if resolved_to is None: | ||
| raise _BadRequest(f"role {recipient!r} resolves to no holder") | ||
| except Exception as exc: | ||
| # Resolver error (e.g., unreachable) -> 503 | ||
| self._send_json(503, {"error": f"role resolution failed: {exc}"}) | ||
| return | ||
|
|
||
| result = runner.run( | ||
| service.a2a_send( | ||
| sender=from_, body=body_text, | ||
| thread=thread, reply_to=reply_to, | ||
| refs=refs, blocks=blocks, | ||
| refs=refs, blocks=blocks, recipient=recipient, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
"No holder" _BadRequest gets swallowed by the surrounding except Exception, returning 503 instead of 400.
The raise _BadRequest(f"role {recipient!r} resolves to no holder") on line 1528 sits inside the same try whose except Exception as exc: (1529) sends a 503. Since _BadRequest is caught ahead of the generic Exception handler at the dispatch level (lines 1057-1064), it is itself an Exception subclass, so this inner except catches it too — every "role resolves to no holder" case is misreported as 503 role resolution failed: role '...' resolves to no holder instead of the intended 400. This breaks the documented contract in role_resolver.py: "FAIL-LOUD: configured but unreachable -> 503 on role sends and role-filtered reads... Prevents silent drop or fallback" — an unreachable resolver and a resolvable-but-unassigned role are different failure modes and should map to different status codes.
🐛 Proposed fix
if resolver is not None:
- try:
- resolved_to = resolver.resolve(recipient)
- if resolved_to is None:
- raise _BadRequest(f"role {recipient!r} resolves to no holder")
- except Exception as exc:
- # Resolver error (e.g., unreachable) -> 503
- self._send_json(503, {"error": f"role resolution failed: {exc}"})
- return
+ try:
+ resolved_to = resolver.resolve(recipient)
+ except Exception as exc:
+ # Resolver error (e.g., unreachable) -> 503
+ self._send_json(503, {"error": f"role resolution failed: {exc}"})
+ return
+ if resolved_to is None:
+ raise _BadRequest(f"role {recipient!r} resolves to no holder")📝 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.
| # Recipient validation: send-time resolution for roles when a resolver is configured. | |
| # If recipient is a role (starts with @) and a resolver is configured, | |
| # check the role resolves (exists, exactly one holder), 400 if not. | |
| # Do NOT store the resolved identity - binding happens at delivery. | |
| resolved_to = None | |
| if recipient is not None: | |
| # Recipient is optional; if present, validate format (handle or role prefix) | |
| if not isinstance(recipient, str) or not recipient: | |
| raise _BadRequest("'recipient' must be a non-empty string when provided") | |
| # Validate recipient format: must start with @ for agents/roles, optionally followed by more | |
| if not recipient.startswith("@"): | |
| raise _BadRequest("'recipient' must be a handle (e.g., '@alice') or role (e.g., '@taOS-PA')") | |
| # Load resolver from config if it exists | |
| try: | |
| from .role_resolver import get_resolver_from_config | |
| resolver = get_resolver_from_config(data_dir) | |
| except Exception: | |
| # If the resolver fails to load (e.g., import error), fallback to None | |
| resolver = None | |
| # Only resolve if we have a resolver AND the recipient is a role | |
| # (roles are @taOS-PA and other non-agent handles - best effort detection) | |
| if resolver is not None: | |
| # For now, treat any recipient starting with @ as a role for resolution | |
| # In a real implementation, this would check against a known roles list | |
| # or have a way to differentiate agents from roles | |
| try: | |
| resolved_to = resolver.resolve(recipient) | |
| if resolved_to is None: | |
| raise _BadRequest(f"role {recipient!r} resolves to no holder") | |
| except Exception as exc: | |
| # Resolver error (e.g., unreachable) -> 503 | |
| self._send_json(503, {"error": f"role resolution failed: {exc}"}) | |
| return | |
| result = runner.run( | |
| service.a2a_send( | |
| sender=from_, body=body_text, | |
| thread=thread, reply_to=reply_to, | |
| refs=refs, blocks=blocks, | |
| refs=refs, blocks=blocks, recipient=recipient, | |
| # Recipient validation: send-time resolution for roles when a resolver is configured. | |
| # If recipient is a role (starts with @) and a resolver is configured, | |
| # check the role resolves (exists, exactly one holder), 400 if not. | |
| # Do NOT store the resolved identity - binding happens at delivery. | |
| resolved_to = None | |
| if recipient is not None: | |
| # Recipient is optional; if present, validate format (handle or role prefix) | |
| if not isinstance(recipient, str) or not recipient: | |
| raise _BadRequest("'recipient' must be a non-empty string when provided") | |
| # Validate recipient format: must start with @ for agents/roles, optionally followed by more | |
| if not recipient.startswith("@"): | |
| raise _BadRequest("'recipient' must be a handle (e.g., '`@alice`') or role (e.g., '`@taOS-PA`')") | |
| # Load resolver from config if it exists | |
| try: | |
| from .role_resolver import get_resolver_from_config | |
| resolver = get_resolver_from_config(data_dir) | |
| except Exception: | |
| # If the resolver fails to load (e.g., import error), fallback to None | |
| resolver = None | |
| # Only resolve if we have a resolver AND the recipient is a role | |
| # (roles are `@taOS-PA` and other non-agent handles - best effort detection) | |
| if resolver is not None: | |
| # For now, treat any recipient starting with @ as a role for resolution | |
| # In a real implementation, this would check against a known roles list | |
| # or have a way to differentiate agents from roles | |
| try: | |
| resolved_to = resolver.resolve(recipient) | |
| except Exception as exc: | |
| # Resolver error (e.g., unreachable) -> 503 | |
| self._send_json(503, {"error": f"role resolution failed: {exc}"}) | |
| return | |
| if resolved_to is None: | |
| raise _BadRequest(f"role {recipient!r} resolves to no holder") | |
| result = runner.run( | |
| service.a2a_send( | |
| sender=from_, body=body_text, | |
| thread=thread, reply_to=reply_to, | |
| refs=refs, blocks=blocks, recipient=recipient, |
🧰 Tools
🪛 Ruff (0.16.0)
[warning] 1515-1515: Do not catch blind exception: Exception
(BLE001)
[warning] 1529-1529: Do not catch blind exception: Exception
(BLE001)
🤖 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 `@taosmd/http_server.py` around lines 1497 - 1538, Adjust the exception
handling around resolver.resolve in the recipient validation flow near
resolved_to so _BadRequest raised for a role with no holder is re-raised and
reaches the dispatch-level 400 handler. Keep genuine resolver failures mapped to
the existing 503 response, preserving the distinction between unassigned roles
and unreachable or failing resolvers.
| Role resolver configuration for taOSmd A2A. | ||
|
|
||
| Configurable resolver for role-to-identity mapping with short TTL cache. | ||
| Exposed via a minimal seam so the taOS binding/rotation/Observatory can inject | ||
| or rotate the implementation without modifying core taosmd code. | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Module docstring is missing triple-quote delimiters — file fails to parse.
Lines 1-6 read as bare text, not a string literal. This is a hard SyntaxError; the module cannot even be imported. This matches the cascading Ruff parse errors reported (unexpected for/with/in tokens) exactly.
🐛 Proposed fix
+"""
Role resolver configuration for taOSmd A2A.
Configurable resolver for role-to-identity mapping with short TTL cache.
Exposed via a minimal seam so the taOS binding/rotation/Observatory can inject
or rotate the implementation without modifying core taosmd code.
+"""
from __future__ import annotations📝 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.
| Role resolver configuration for taOSmd A2A. | |
| Configurable resolver for role-to-identity mapping with short TTL cache. | |
| Exposed via a minimal seam so the taOS binding/rotation/Observatory can inject | |
| or rotate the implementation without modifying core taosmd code. | |
| """ | |
| Role resolver configuration for taOSmd A2A. | |
| Configurable resolver for role-to-identity mapping with short TTL cache. | |
| Exposed via a minimal seam so the taOS binding/rotation/Observatory can inject | |
| or rotate the implementation without modifying core taosmd code. | |
| """ |
🧰 Tools
🪛 Ruff (0.16.0)
[warning] 1-1: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 1-1: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 1-1: Compound statements are not allowed on the same line as simple statements
(invalid-syntax)
[warning] 1-1: Expected in, found name
(invalid-syntax)
[warning] 1-2: Expected an identifier
(invalid-syntax)
[warning] 3-3: Expected an indented block after for statement
(invalid-syntax)
[warning] 3-3: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 3-3: Compound statements are not allowed on the same line as simple statements
(invalid-syntax)
[warning] 3-3: Invalid assignment target
(invalid-syntax)
[warning] 3-3: Expected in, found name
(invalid-syntax)
[warning] 3-3: Expected :, found with
(invalid-syntax)
[warning] 3-3: Expected ,, found name
(invalid-syntax)
[warning] 3-3: Expected ,, found name
(invalid-syntax)
[warning] 3-4: Expected an identifier
(invalid-syntax)
[warning] 4-4: Expected an indented block after with statement
(invalid-syntax)
[warning] 4-4: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 4-4: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 4-4: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 4-4: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 4-4: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 4-4: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 4-4: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 4-4: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 4-4: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 4-4: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 5-5: Expected a statement
(invalid-syntax)
[warning] 5-5: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 5-5: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 5-5: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 5-5: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 5-5: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 5-5: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 5-5: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 5-6: Expected an identifier
(invalid-syntax)
🤖 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 `@taosmd/role_resolver.py` around lines 1 - 6, Wrap the introductory module
documentation at the top of the role resolver module in triple-quote delimiters
so it becomes a valid Python module docstring. Preserve the existing text and
ensure the following code is parsed as Python normally.
Source: Linters/SAST tools
| def resolve(self, role: str) -> str | None: | ||
| raise RuntimeError("no resolver configured: cannot resolve role %r", role) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
RuntimeError gets two positional args instead of a formatted message.
RuntimeError("no resolver configured: cannot resolve role %r", role) does not apply %-formatting; str(exc) will render as the raw tuple-ish repr instead of an interpolated message, hurting debuggability of this fail-loud path.
🐛 Proposed fix
def resolve(self, role: str) -> str | None:
- raise RuntimeError("no resolver configured: cannot resolve role %r", role)
+ raise RuntimeError(f"no resolver configured: cannot resolve role {role!r}")📝 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.
| def resolve(self, role: str) -> str | None: | |
| raise RuntimeError("no resolver configured: cannot resolve role %r", role) | |
| def resolve(self, role: str) -> str | None: | |
| raise RuntimeError(f"no resolver configured: cannot resolve role {role!r}") |
🤖 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 `@taosmd/role_resolver.py` around lines 38 - 39, Update the exception
construction in resolve so the role is interpolated into a single descriptive
RuntimeError message rather than passed as a second positional argument.
Preserve the existing fail-loud behavior and message context.
| remote = _get_remote(data_dir) | ||
| if remote is not None: | ||
| # Remote servers support recipient filtering | ||
| return await remote.a2a_read(thread=thread, since=since, limit=limit, recipient=recipient) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift
RemoteClient.a2a_read does not exist — this call will crash with AttributeError.
taosmd/remote.py's RemoteClient class (full file reviewed) defines a2a_send, a2a_feed, a2a_channels, a2a_members, etc., but no a2a_read. Any deployment with a remote server URL configured will hit AttributeError the first time a2a_read is called, directly contradicting the AI summary's claim that recipients are "filterable during reads" over the remote path. Add RemoteClient.a2a_read(...) (mirroring a2a_feed, POSTing to a /a2a/read or reusing /a2a/messages with a recipient query param) before this ships.
🤖 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 `@taosmd/service.py` around lines 437 - 440, Implement a matching a2a_read
method on the RemoteClient class, using the existing remote request conventions
and accepting thread, since, limit, and recipient parameters. Route it to the
remote server’s supported A2A read/messages endpoint while preserving recipient
filtering, so the remote branch in service.py can invoke remote.a2a_read without
raising AttributeError.
| # Query archive with recipient resolution logic | ||
| # When filtering by recipient, we need role resolution at read time | ||
| # So we query all and apply the recipient filter with resolution logic | ||
| rows_all = [] | ||
| if alias_sources and thread is not None: | ||
| # Apply thread (including aliases) and recipient filters when applicable | ||
| rows_all = await archive.query(event_type=EVENT_A2A, since=since, limit=limit * 10) | ||
| elif thread is not None: | ||
| rows_all = await archive.query(event_type=EVENT_A2A, app_id=thread, since=since, limit=limit * 10) | ||
| else: | ||
| rows_all = await archive.query(event_type=EVENT_A2A, since=since, limit=limit * 10) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Recipient/thread-filtered reads return the oldest matches, not the most recent ones.
Rows are fetched newest-first bounded by limit * 10, then sorted ascending, then sliced with rows[:limit] (line 561). After an ascending sort, [:limit] keeps the oldest limit entries of the filtered set, silently dropping the most recent matches whenever more than limit rows pass the filter — the opposite of what a message-read API should return. Additionally, the blanket limit * 10 over-fetch (unlike a2a_feed, which only widens the query for the alias-merge case) means a highly selective recipient filter can still miss legitimately older matches that fall outside that window.
🐛 Proposed fix for the slicing bug
# Sort oldest-first for consistency
rows.sort(key=lambda m: m["ts"])
if limit:
- rows = rows[:limit]
+ rows = rows[-limit:]Also applies to: 558-561
🤖 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 `@taosmd/service.py` around lines 474 - 484, Update the
recipient/thread-filtered read path using `rows_all` so recipient resolution
does not rely on the blanket `limit * 10` fetch; query the complete candidate
set when recipient filtering is applied, while retaining the normal limit for
unfiltered reads and only widening for alias merging as needed. At the final
sorting and slicing logic, select the most recent `limit` matching rows rather
than `rows[:limit]`, preserving the API’s expected output order.
| async def test_recipient_send_and_read(): | ||
| """Test that recipient field is stored and returned correctly.""" | ||
| with tempfile.TemporaryDirectory() as temp_dir: | ||
| data_dir = Path(temp_dir) | ||
|
|
||
| # First send a message with recipient | ||
| print("Test 1: Send with agent recipient (@alice)") | ||
| receipt1 = await a2a_send( | ||
| sender="@test", | ||
| body="Hello agent", | ||
| thread="general", | ||
| recipient="@alice", | ||
| data_dir=data_dir | ||
| ) | ||
| assert "recipient" in receipt1, "Receipt should contain recipient" | ||
| assert receipt1["recipient"] == "@alice", f"Expected @alice, got {receipt1.get('recipient')}" | ||
| print(f" ✓ Receipt contains recipient: {receipt1['recipient']}") | ||
|
|
||
| # Send a message without recipient | ||
| print("\nTest 2: Send without recipient") | ||
| receipt2 = await a2a_send( | ||
| sender="@test", | ||
| body="Hello no recipient", | ||
| thread="general", | ||
| data_dir=data_dir | ||
| ) | ||
| assert "recipient" not in receipt2, "Receipt should not contain recipient" | ||
| print(f" ✓ Receipt does not contain recipient") | ||
|
|
||
| # Send a message with role recipient | ||
| print("\nTest 3: Send with role recipient (@taOS-PA)") | ||
| receipt3 = await a2a_send( | ||
| sender="@test", | ||
| body="Hello role", | ||
| thread="general", | ||
| recipient="@taOS-PA", | ||
| data_dir=data_dir | ||
| ) | ||
| assert "recipient" in receipt3, "Receipt should contain recipient" | ||
| assert receipt3["recipient"] == "@taOS-PA", f"Expected @taOS-PA, got {receipt3.get('recipient')}" | ||
| print(f" ✓ Receipt contains recipient: {receipt3['recipient']}") | ||
|
|
||
| # Now read messages and verify recipient is present | ||
| print("\nTest 4: Read all messages and verify recipient") | ||
| messages = await a2a_read(thread="general", data_dir=data_dir) | ||
| print(f" Found {len(messages)} messages") | ||
|
|
||
| for msg in messages: | ||
| print(f" Message from {msg.get('from')}: recipient present = {'recipient' in msg}") | ||
| if "recipient" in msg: | ||
| print(f" Recipient: {msg['recipient']}") | ||
|
|
||
| print("\n✓ All tests passed!") |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check pytest test discovery/async config
fd -e cfg -e ini -e toml pytest
cat pyproject.toml 2>/dev/null | rg -A5 '\[tool\.pytest'
cat pytest.ini 2>/dev/null
rg -n 'asyncio_mode|testpaths|python_files' pyproject.toml pytest.ini setup.cfg 2>/dev/null
pip show pytest-asyncio 2>/dev/nullRepository: jaylfc/taosmd
Length of output: 151
🏁 Script executed:
#!/bin/bash
set -u
printf '== tracked files likely config/tests ==\n'
git ls-files | rg -n '(^|/)(pytest\.ini|setup\.cfg|tox\.ini|pyproject\.toml|requirements.*\.txt|Pipfile|poetry\.lock|uv\.lock|\.github/workflows/.*\.ya?ml)$|(^|/)test_recipient.*\.py$|(^|/)tests/' || true
printf '\n== pyproject/test config snippets ==\n'
for f in pyproject.toml pytest.ini setup.cfg tox.ini; do
if [ -f "$f" ]; then
echo "--- $f ---"
rg -n 'pytest|asyncio|testpaths|python_files|async_mode|mode=' "$f" || true
fi
done
printf '\n== workflow trigger/test commands ==\n'
for f in $(git ls-files '.github/workflows/*.yml' '.github/workflows/*.yaml' 2>/dev/null); do
echo "--- $f ---"
rg -n 'pytest|tox|pip install|uv run|poetry run|requirements|test' "$f" || true
done
printf '\n== async test file top-level snippets ==\n'
for f in test_recipient_read.py test_recipient_field.py; do
if [ -f "$f" ]; then
echo "--- $f ---"
sed -n '1,90p' "$f"
fi
done
printf '\n== standalone pytest discovery check (no repo execution) ==\n'
python3 - <<'PY'
import ast
from pathlib import Path
for path in [Path('test_recipient_read.py'), Path('test_recipient_field.py')]:
if not path.exists():
print(f'{path}: missing')
continue
tree = ast.parse(path.read_text(), filename=str(path))
funcs = [node.name for node in tree.body if isinstance(node, ast.AsyncFunctionDef) and node.name.startswith('test_')]
print(f'{path}: async test_ defs = {funcs}')
PYRepository: jaylfc/taosmd
Length of output: 9528
🌐 Web query:
pytest-asyncio pytest-asyncio mode pytest-asyncio mode not configured default strict
💡 Result:
In pytest-asyncio, the asyncio_mode configuration controls how asynchronous tests and fixtures are discovered and executed [1][2]. When you see a message or warning indicating that the mode is not configured, it means your project lacks an explicit declaration in your pytest configuration file, causing pytest-asyncio to fall back to its default behavior, which is strict mode [3][4][2]. There are two primary modes available: 1. Strict Mode (default): This is the current default behavior [3][2]. In this mode, pytest-asyncio only executes tests and fixtures that are explicitly marked with @pytest.mark.asyncio or decorated with @pytest_asyncio.fixture [1][5][2]. This mode is recommended if your project interacts with multiple asynchronous frameworks or if you prefer explicit control over which tests are treated as asyncio tests [1][2]. 2. Auto Mode: In this mode, pytest-asyncio automatically discovers and treats all async functions as asyncio tests and handles all async fixtures without requiring manual markers [1][2]. This is generally the recommended setting for projects that use asyncio as their sole asynchronous framework, as it eliminates the need for boilerplate markers [1][2]. To resolve warnings or ensure consistent behavior, you should explicitly set the mode in your project's configuration file (e.g., pytest.ini or pyproject.toml) [6]: For pytest.ini: [pytest] asyncio_mode = auto For pyproject.toml: [tool.pytest.ini_options] asyncio_mode = "auto" Setting this explicitly prevents reliance on default behavior, which can be fragile across library updates, and eliminates related deprecation warnings [7][8][6].
Citations:
- 1: https://pytest-asyncio.readthedocs.io/en/stable/concepts.html
- 2: https://qaskills.sh/blog/pytest-asyncio-async-testing-guide
- 3: https://pytest-asyncio.readthedocs.io/en/stable/reference/configuration.html
- 4: https://pytest-asyncio.readthedocs.io/en/v1.4.0/reference/configuration.html
- 5: https://pytest-asyncio.readthedocs.io/en/v1.2.0/concepts.html
- 6: [2026-03-21] LOW - pytest-asyncio mode not configured in pytest.ini matiaszanolli/Auralis#2942
- 7: pytest-asyncio mode not configured — relies on implicit STRICT default HomericIntelligence/Keystone#231
- 8: Warning about
asyncio_modeeven when unused in tests. pytest-dev/pytest-asyncio#293
🌐 Web query:
pytest default unmarked async function coroutine passed to pytest fixture test passed warning
💡 Result:
When you encounter a warning regarding an "unmarked async function" or a "coroutine passed to a pytest fixture," it generally indicates that you are attempting to use asynchronous functions (coroutines) with pytest without proper configuration or plugin support [1][2][3]. In standard pytest, native support for async functions does not exist; they are treated as regular synchronous functions [1][2]. When an async function is defined as a test or a fixture without the necessary plugin to handle it, the coroutine is created but never executed, leading to a RuntimeWarning stating that the coroutine was never awaited [4][5][3]. To resolve this and eliminate the warning, follow these practices: 1. Install an appropriate plugin: The most common solution is to install pytest-asyncio [5][2][3]. pip install pytest-asyncio 2. Use the correct decorators: In strict mode (the default), async tests must be explicitly marked with @pytest.mark.asyncio, and async fixtures should use the plugin-specific fixture decorator (e.g., @pytest_asyncio.fixture) rather than the standard @pytest.fixture [3][6][7]. 3. Use 'auto' mode if preferred: If you prefer not to mark every function, you can configure pytest-asyncio to auto-detect async functions by setting the following in your pytest.ini or pyproject.toml: [pytest] asyncio_mode = auto 4. Avoid mixing async and sync: Using an async fixture within a synchronous test is generally discouraged because it creates ambiguity and often leads to unawaited coroutine warnings [8]. If you must do this, it is recommended to wrap the async fixture in a synchronous fixture that handles the execution, or ensure your test structure does not rely on mixing these paradigms [8][6]. If the warning persists, ensure you are not using standard @pytest.fixture on asynchronous functions, as this is deprecated and often results in the fixture value being an unawaited coroutine [8][6][7]. Always prefer the dedicated async fixture helpers provided by your async plugin [3][6].
Citations:
- 1: Add a warning when setting up async fixtures pytest-dev/pytest#10839
- 2: https://www.developerload.com/how-to-test-async-function-using-pytest
- 3: https://qaskills.sh/blog/pytest-asyncio-async-testing-guide
- 4: https://matthewbrown.io/2026/02/03/pytest-asyncio-without-plugins
- 5: https://stackoverflow.com/questions/49350821/runtimewarning-coroutine-was-never-awaited-in-tests
- 6: warn if asyncio test requests async pytest fixture in strict mode pytest-dev/pytest-asyncio#979
- 7: https://pytest-asyncio.readthedocs.io/en/v1.3.0/reference/changelog.html
- 8: https://docs.pytest.org/en/stable/deprecations.html
Run these async A2A tests with pytest-asyncio auto mode or explicit markers.
test_recipient_read.py and test_recipient_field.py define bare async def test_* functions, but the repo only depends on pytest-asyncio; without pytest.ini / [tool.pytest.ini_options] asyncio_mode = auto or matching @pytest.mark.asyncio markers, pytest can report passes for co-routines without awaiting them, nullifying the assert-only coverage in test_recipient_read.py. Add the async mode/config and apply the same pattern to any other unmarked async tests.
🧰 Tools
🪛 Ruff (0.16.0)
[error] 36-36: f-string without any placeholders
Remove extraneous f prefix
(F541)
📍 Affects 2 files
test_recipient_read.py#L9-L61(this comment)test_recipient_field.py#L10-L67
🤖 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 `@test_recipient_read.py` around lines 9 - 61, Configure pytest-asyncio auto
mode or add explicit asyncio markers so the async test functions in
test_recipient_read.py lines 9-61 and test_recipient_field.py lines 10-67 are
awaited and their assertions execute; apply the same pattern to any other
unmarked async tests, using the repository’s pytest configuration or
`@pytest.mark.asyncio` consistently.
|
Closing for rework. role_resolver.py does not parse (docstring has no quotes, py_compile fails at line 1) and both import sites swallow the failure, so the whole feature is a silent no-op while claiming fail-loud. Any @-prefixed recipient is treated as a role, so direct messages to plain agents get rejected whenever a role_map exists. The no-holder 400 is unreachable because _BadRequest is caught by the blanket except and becomes a 503. service.a2a_read is unwired (no route, no MCP, not in all) and calls remote.a2a_read which does not exist. a2a_feed never emits recipient, so the stored field is invisible on the real read path. Tests sit at repo root, one has zero asserts, and neither would be collected. Salvage: the recipient plumbing through a2a_send/remote/archive is correct and small; extract that into its own clean PR first, redo role resolution separately after the ACL rework. Also retitle, the chore title came from the uv.lock cleanup commit. |
Autonomous build of board card tsk-b5fz5t.
Files:
taosmd/http_server.py | 42 ++++++++++++-
taosmd/remote.py | 5 +-
taosmd/role_resolver.py | 146 +++++++++++++++++++++++++++++++++++++++++++
taosmd/service.py | 161 +++++++++++++++++++++++++++++++++++++++++++++++-
test_recipient_field.py | 71 +++++++++++++++++++++
test_recipient_read.py | 65 +++++++++++++++++++
6 files changed, 485 insertions(+), 5 deletions(-)
Summary by CodeRabbit
New Features
Bug Fixes
Tests