feat(agentex): filter GET /agents by agent card metadata - #411
feat(agentex): filter GET /agents by agent card metadata#411declan-scale wants to merge 3 commits into
Conversation
✱ Stainless preview buildsThis PR will update the openapi python typescript Edit this comment to update them. They will appear in their respective SDK's changelogs. ✅ agentex-sdk-openapi studio · code · diff
✅ agentex-sdk-typescript studio · code · diff
✅ agentex-sdk-python studio · code · diff
This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push. |
basselatscale
left a comment
There was a problem hiding this comment.
The JSONB containment direction is right, and the integration coverage proves the important subset behavior: an agent whose card contains additional metadata still matches {"permits_capable": true}.
A few changes are needed before this is ready:
-
list_agents() now fails when called directly without agent_card_metadata. Its default is a FastAPI Query object, so json.loads(agent_card_metadata) raises TypeError. This is currently failing the two authorization unit tests. Please use the Annotated[..., Query(...)] = None form, or otherwise ensure the Python default is actually None, and keep the direct-call tests passing.
-
In AgentRepository.list, use if agent_card_metadata is not None: rather than a truthiness check. Otherwise an explicitly supplied {} silently bypasses the metadata predicate and includes agents with missing metadata.
-
Please ensure the OpenAPI/SDK contract supports an ergonomic mapping input rather than requiring every caller to manually json.dumps it. The required consumer shape is:
client.agents.list(
agent_card_metadata={"permits_capable": True},
)
If the wire parameter must remain JSON encoded, the generated/client layer should perform that encoding. The current string schema generates string-typed SDK parameters.
Once those are fixed, this server-side capability is sufficient for our immediate goal: discovering AgentCard-published workflow descriptors and removing the generated input-contract bundle.
|
Addressed all three review items in 52b3277:
|
basselatscale
left a comment
There was a problem hiding this comment.
Follow-up review — all three original items are addressed ✓
Two new warnings worth confirming before merge:
1. Non-finite JSON values in agent_card_metadata
Python's json.loads() accepts NaN, Infinity, and extreme exponents like 1e1000000 which aren't valid interoperable JSON. These will pass the current isinstance(parsed, dict) check but can fail at the PostgreSQL JSONB binding layer with an uncontrolled 500 instead of a clean 400. Low likelihood in practice, but a defense-in-depth gap. Could be a follow-up — e.g. json.loads(agent_card_metadata, parse_constant=lambda _: None) + a math.isfinite walk, or just parse_float=decimal.Decimal.
2. Stainless SDK Parameter/MissingSchema warning
The Stainless bot reports "Defaulted parameter to type: string because no schema was defined" on all three SDK previews. Can you confirm the generated Python and TypeScript SDK type signatures actually expose agent_card_metadata as a dict/object (not str)? If the preview SDKs are correct the warning is cosmetic, but if they defaulted to str it defeats the intent of the content: application/json spec encoding.
|
Both follow-up warnings addressed in 1a4c251. 1. Non-finite JSON values — fixed.
Nested cases ( 2. Stainless I checked the preview build directly rather than assuming: # stainless-sdks/agentex-sdk-python @ preview/.../agx1-1048-agent-card-metadata-filter
# src/agentex/types/agent_list_params.py
class AgentListParams(TypedDict, total=False):
agent_card_metadata: strSo the generator ignores I've dropped it. - name: agent_card_metadata
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: 'JSON-encoded object used to filter agents on ...'That clears the warning, makes the two containment filters consistent, and means the generated client surface is reproducible from the spec with no hand-editing. The ergonomics you asked for now live in the SDK's hand-written layer instead of fighting the generator — see the paired SDK PR, which adds from agentex.lib.utils.metadata_filters import encode_metadata_filter
client.agents.list(
agent_card_metadata=encode_metadata_filter({"permits_capable": True}),
)It's one call rather than a bare Tests: 26 passed in |
Adds an optional `agent_card_metadata` query parameter to `GET /agents` that applies an exact JSONB containment (`@>`) filter against `registration_metadata.agent_card.metadata`. Agents whose card is missing or does not contain every requested key/value are excluded; the existing pagination, ordering, task filtering and authorization behavior are preserved. Enables discovery flows where consumers publish opt-in capability flags via the AgentCard and need to enumerate only agents that advertise them.
- Use Annotated[str | None, Query(...)] = None so list_agents() called
directly (outside FastAPI) defaults to None instead of a Query object
- Apply the containment predicate on `is not None` so an explicit {}
filter still requires a card metadata object to be present
- Declare the query parameter with `content: application/json` and an
object schema so SDK generators expose a mapping-typed parameter and
perform the JSON wire encoding themselves
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tent-typed param Two follow-up review items on the agent card metadata filter. Python's json module accepts values that are not interoperable JSON: the bare NaN/Infinity constants, float literals that overflow to infinity (1e1000000), and integers too large for CPython to render. All of them satisfy the isinstance(..., dict) check and only fail later at the JSONB bind parameter, turning caller error into an uncontrolled 500. Parse with parse_constant/parse_float hooks that reject them so every malformed input surfaces as a 400. The parameter was declared with content: application/json in the hope that SDK generators would expose it as a mapping and do the JSON encoding themselves. They do not: the generator reports "no schema was defined" and falls back to type: string, so the generated client parameter is a plain str either way. Drop the content-typed override and declare it the same way the already-shipped GET /tasks?task_metadata= filter is declared -- a nullable string carrying a JSON-encoded object -- which clears the generator warning and keeps the two containment filters consistent.
1a4c251 to
ed1708f
Compare
🏆 Brought to you by the Golden Agent (Try it out)
Problem
AgentCardpublishes self-description data throughregistration_metadata.agent_card, butGET /agentshas no way to filter on its contents. Discovery flows that want to enumerate agents opting into a specific protocol capability (e.g. Permits' workflow submission protocol) have no server-side hook and must fetch everything client-side.Linear: AGX1-1048
Change
agent_card_metadataquery parameter toGET /agents. The value is a JSON-encoded object; malformed JSON or non-object payloads return400.AgentsUseCase.list, which reserves the keyagent_card_metadatain the repository filters dict.AgentRepository.listapplies a JSONB@>filter at the top level:registration_metadataisNULL, missingagent_card, or missingagent_card.metadataare naturally excluded, and every requested key/value must be present at the correct nesting level.agents.registration_metadatais alreadyJSONB.task_idjoin, authorization id set, andstatus != DELETEDclause).openapi.yamlregenerated by hand to reflect the new query parameter; the paired SDK PR consumes the same spec.Test coverage added
tests/integration/api/agents/test_agents_api.py:limit/page_number.400.tests/unit/use_cases/test_agents_use_case.pythat seed agents directly via the repository and exercise the use-case-to-repo plumbing against real Postgres (single-key, multi-key, absent-card, and omitted-filter cases). Tests use a per-invocation tag so they are safe against session-scoped container reuse.Test plan (for reviewer, since local yarn/uv installs are skipped per Golden Agent policy)
make test-unit) pass, including the two new use-case tests.make test-integration) pass, including the four new API tests.GET /agents?agent_card_metadata={\"permits_capable\":true}against a dev backend seeded with an agent card and confirm only that agent is returned.openapi.yamlstill matches the FastAPI-generated spec (make gen-openapishould produce no further diff).Out of scope / follow-ups
production_deployment_id.Greptile Summary
The PR adds server-side filtering of agents by JSON-encoded Agent Card metadata while preserving existing authorization, deletion, ordering, and pagination constraints.
agent_card_metadataquery parameter, returning400for malformed or non-object JSON.registration_metadata.agent_card.metadata.Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
{}, while retaining authorization, task, and soft-deletion filtering.Sequence Diagram
sequenceDiagram participant C as Client participant R as GET /agents route participant U as AgentsUseCase participant DB as AgentRepository/PostgreSQL C->>R: agent_card_metadata JSON query R->>R: Parse and validate object R->>U: list(agent_card_metadata, authorized IDs) U->>DB: list(filters) DB->>DB: Apply JSONB containment, authorization, and status filters DB-->>U: Matching agents U-->>R: Agent entities R-->>C: 200 agent listReviews (4): Last reviewed commit: "fix(agentex): reject non-finite JSON in ..." | Re-trigger Greptile