Skip to content

feat(agent-card): add metadata field and expose list filter - #502

Open
declan-scale wants to merge 1 commit into
nextfrom
declan-scale/agx1-1048-agent-card-metadata
Open

feat(agent-card): add metadata field and expose list filter#502
declan-scale wants to merge 1 commit into
nextfrom
declan-scale/agx1-1048-agent-card-metadata

Conversation

@declan-scale

@declan-scale declan-scale commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

🏆 Brought to you by the Golden Agent (Try it out)

Problem

The SDK's AgentCard has no free-form metadata field, so agents that want to advertise opt-in protocol capabilities (e.g. Permits' workflow submission protocol) have no place to attach that self-description data. The generated agents.list client also lacks the paired containment filter so consumers can't enumerate agents that opted in.

Linear: AGX1-1048

Change

  • Adds metadata: dict[str, Any] = Field(default_factory=dict) to AgentCard in src/agentex/lib/types/agent_card.py. Default factory keeps each instance's dict independent (no shared mutable default).
  • Threads an optional metadata= kwarg through AgentCard.from_states and AgentCard.from_state_machine so callers building a card from a state machine can attach metadata inline.
  • Serializes through the existing registration_metadata.agent_card path (agentex.lib.utils.registration.register_agent) — no wire-shape change required.
  • Exposes an agent_card_metadata: Optional[str] param on both sync and async agents.list (a JSON-encoded object matched with exact key/value containment on the server) and adds it to AgentListParams. The platform PR wires the server-side JSONB @> filter.

Test coverage added

  • tests/lib/test_agent_card.py
    • Default metadata is an empty dict and instances don't share the default.
    • metadata accepts arbitrary nested JSON and round-trips through model_dump / model_validate.
    • from_states and from_state_machine forward the value.
    • register_agent propagates metadata inside registration_metadata.agent_card.metadata on the outgoing HTTP request.
  • tests/api_resources/test_agents.py: extended the `with_all_params` list tests (sync + async) to pass `agent_card_metadata`. (Still `@pytest.mark.skip`-guarded like the rest of the mock-server tests.)

Test plan (for reviewer)

  • CI `Validate PR title` and `Validate PR base branch` pass (PR targets `next`, title uses Conventional Commits).
  • `./scripts/test` on `next` locally to run the AgentCard + api_resources tests.
  • Once the paired platform PR (feat(agentex): filter GET /agents by agent card metadata scale-agentex#411) is merged, verify Stainless regeneration keeps the manually-added `agent_card_metadata` param intact (or supersedes this file with an equivalent generated version — either is fine because the surface matches the OpenAPI spec).

Notes

Greptile Summary

The PR adds free-form metadata to AgentCard, forwards it through state-machine constructors and registration, and exposes a matching sync/async agents.list filter.

  • Adds independently allocated AgentCard metadata with serialization and forwarding tests.
  • Adds agent_card_metadata to generated list resource methods and request parameters.
  • Extends sync and async resource tests, although those mock-server tests remain skipped.

Confidence Score: 4/5

The PR appears safe to merge at runtime, but the generated list-filter changes should be made reproducible from the upstream schema before relying on them.

The metadata model and sync/async request paths are internally aligned; the remaining non-blocking concern is that regeneration can discard the manually edited generated client surface.

Files Needing Attention: src/agentex/resources/agents/agents.py, src/agentex/types/agent_list_params.py

Important Files Changed

Filename Overview
src/agentex/lib/types/agent_card.py Adds an independent free-form metadata dictionary and forwards it through both AgentCard factory methods.
src/agentex/resources/agents/agents.py Adds the sync and async list query parameter consistently, but does so directly in a generated artifact without a reproducible schema change.
src/agentex/types/agent_list_params.py Adds the generated request parameter type consistently with the resource method, subject to the same code-generation boundary concern.
tests/lib/test_agent_card.py Covers default independence, model round-tripping, constructor forwarding, and registration payload propagation.
tests/api_resources/test_agents.py Extends both generated list tests with the filter, but the affected mock-server tests remain skipped.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    Builder[AgentCard constructors] --> Card[AgentCard metadata]
    Card --> Registration[registration_metadata.agent_card.metadata]
    Registration --> API[AgentEx API]
    Filter[agents.list agent_card_metadata] --> API
    API --> Matches[Agents whose card metadata contains filter]
Loading

Fix all with Greploop Fix All in Claude Code

Prompt To Fix All With AI
### Issue 1
src/agentex/resources/agents/agents.py:119
**Generated filter is unreproducible**

The filter is added directly to Stainless-generated files without updating the generator configuration or upstream schema. A subsequent regeneration from the current source removes this public parameter or creates recurring generated-code conflicts.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat(agent-card): add metadata field and..." | Re-trigger Greptile

Context used (4)

Adds an optional `metadata: dict[str, Any]` field to the SDK's
`AgentCard` model (defaulting to an empty dict) and threads the value
through `AgentCard.from_states` / `AgentCard.from_state_machine` so
callers can attach opt-in capability flags without subclassing.

Also plumbs the paired platform `agent_card_metadata` list filter
through the Stainless-generated `agents.list` surface so consumers can
enumerate agents whose card metadata contains a given JSON object with
exact key/value semantics.

The card continues to serialize through the existing
`registration_metadata.agent_card` path — no wire-shape or database
migration is required.

@basselatscale basselatscale 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.

The AgentCard.metadata addition and registration propagation look good and provide the missing agent-authored descriptor path.

The list-filter SDK surface needs one change for the intended consumer contract. It is currently typed as Optional[str], which requires callers to know the wire encoding and manually json.dumps the metadata.

Please expose a mapping/JSON-object input and serialize it internally, so both sync and async clients support:

client.agents.list(
    agent_card_metadata={"permits_capable": True},
)

Please also add an exercised request-level test for both clients that asserts the mapping becomes a JSON-encoded query parameter. The current generated resource tests pass a sentinel string and are skip-guarded, so they do not prove this behavior.

There is also a currently failing Ruff import-order check in agent_card.py that needs to be fixed.

With that SDK change, these paired PRs provide enough plumbing for the permits harness to discover registered descriptors and remove input-contracts.generated.yaml.

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.

2 participants