Skip to content

feat(schema): support open agent discovery under shared base URL via API Catalog#642

Open
ognis1205 wants to merge 3 commits intoa2aproject:mainfrom
ognis1205:poc/agent-catalog
Open

feat(schema): support open agent discovery under shared base URL via API Catalog#642
ognis1205 wants to merge 3 commits intoa2aproject:mainfrom
ognis1205:poc/agent-catalog

Conversation

@ognis1205
Copy link
Contributor

@ognis1205 ognis1205 commented May 23, 2025

Description

Thank you for opening a Pull Request!
Before submitting your PR, there are a few things you can do to make sure it goes smoothly:

Fixes #641 🦕

Points for Review

  1. Does it make sense to include API Catalog-based Open Discovery in the specification?
  2. Should application/json+agent-card be added as an IANA-registered media type?
  3. The PR currently proposes the API Catalog-based notation but does not yet finalize a single canonical format. Should we make a decision to finalize it in this PR, or leave it flexible for now?

Note: This PR focuses on the schema/spec-level support. A separate PR demonstrating the PoC SDK implementation and demo using this proposal is available at a2a-python #109, and is intended to complement this spec-level change.

@ognis1205 ognis1205 force-pushed the poc/agent-catalog branch from 30ab1a1 to 60ba67b Compare May 23, 2025 14:01
@cdavernas
Copy link

Awesome!!!

@ognis1205
Copy link
Contributor Author

Hey @cdavernas , Thanks for the comment!

The schema defined in this PR is implemented to conform to the following RFC and Internet Draft.
I’d appreciate it if you could also review this along with the upcoming PRs to a2a-python:

@ognis1205 ognis1205 changed the title feat(schema): support agent discovery under shared base URL via API Catalog feat(schema): support open agent discovery under shared base URL via API Catalog May 24, 2025
@darkhaniop
Copy link
Contributor

The generated AgentLinkTarget schema definition does not include the additionalProperties property, without which link metadata, such as link title, cannot be supplied. Did it fail to pick up this declaration from LinkTarget?
https://github.com/google/A2A/blob/60ba67bc9a2ae82ee320424e6b2683334ebf4c3c/types/src/types.ts#L551-L555

By the way, I like the simplicity of this approach.

@ognis1205
Copy link
Contributor Author

ognis1205 commented May 24, 2025

Hey @darkhaniop, thanks again for the comment and review!

I double-checked the JSON Schema spec, and it turns out that by default, all additional properties are allowed—meaning the schema will accept extra fields even without explicitly specifying additionalProperties:

https://json-schema.org/understanding-json-schema/reference/object#additionalproperties

It seems that the presence of additionalProperties: {} (which is equivalent to additionalProperties: true) in the LinkTarget schema—but not in AgentLinkTarget—is due to how typescript-json-schema generates the output:

https://github.com/ognis1205/A2A/blob/60ba67bc9a2ae82ee320424e6b2683334ebf4c3c/specification/json/a2a.json#L1139

That can definitely make the generated schema a bit confusing to read. That said, a couple of points to keep in mind:

  • When generating downstream code like google/a2a-python, we might need to ensure the appropriate options are set—even though there’s no issue with how things are currently handled within google/A2A itself.
  • From a schema validation perspective, the current setup is fine. However, when thinking in terms of SDK models, it would mean we don’t expose APIs to set fields like title, for instance (though this shouldn’t be a problem when parsing raw JSON).

What do you think? Let me know if I’m missing anything important.

P.S.
Many thanks to @Kevsy and @darrelmiller for the elegant simplicity of the schema.

@darkhaniop
Copy link
Contributor

@ognis1205, you are right about the current schema output being okay for validation, I did not know that. I just checked the json-schema.org docs you linked, and realized that I was incorrect in thinking that by default any unmatched property is disallowed. From the linked JSON schema docs page:

By default any additional properties are allowed.

I think your bullet points raise a valid question about SDKs implementing the interfaces as defined in a2a.json. If our goal is to explicitly show that additional metadata in AgentLinkTarget objects should be allowed (I do not know if you propose to show that or not), maybe including additionalProperties in its schema definition would be beneficial. Because doing so would result in more consistently generated downstream code.

Pydantic model generation test

Code generation test

To check how explicitly stating additionalProperties vs. omitting it in the schema may affect downstream SDKs and libraries that generate interfaces from a2a.json, I tested type generation for Pydantic.

I used the datamodel-code-generator package to generate types from the following schema:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "WithoutAddProps": {
      "properties": {
        "href": {"type": "string"}
      }
    },
    "WithAddProps": {
      "additionalProperties": true,
      "properties": {
        "href": {"type": "string"}
      }
    }
  }
}

Which results in (omitting the root model):

class WithoutAddProps(BaseModel):
    href: Optional[str] = None


class WithAddProps(BaseModel):
    class Config:
        extra = Extra.allow

    href: Optional[str] = None

While a link object with extra fields validates successfully against both types, the first variant would lose extra fields on deserialization:

link_obj = {"href": "https://example.com", "title": "Example"}
obj_wo_add_props = WithoutAddProps.model_validate(link_obj)
obj_w_add_props = WithAddProps.model_validate(link_obj)

print("Loaded using WithoutAddProps:\n", obj_wo_add_props.model_dump())
print("Loaded using WithAddProps:\n", obj_w_add_props.model_dump())

Output:

Loaded using WithoutAddProps:
 {'href': 'https://example.com'}
Loaded using WithAddProps:
 {'href': 'https://example.com', 'title': 'Example'}

P.S. Although it looks like datamodel-code-generator outputs some deprecated syntax, I used this method because Pydantic docs features it, so other A2A library developers may rely on it.

@ognis1205
Copy link
Contributor Author

Hey @darkhaniop, thank you for sharing your investigation! This really helps!

For now, it’s still uncertain whether this feature request itself will be accepted by the community, so I think it might be better to prioritize getting the protocol evaluated by a broader audience—through PR demos including google/a2a-python—rather than pushing for this specific change. (The properties other than href aren’t critical at this point.)

That said, depending on how things develop, the points you raised could become important, so it’s definitely worth keeping them in mind for the future.

@ognis1205
Copy link
Contributor Author

I’ve removed the WIP status from this PR due to changes in the repository structure. The related SDK implementation will continue in a2a-python, but for the demo, I plan to publish it using a fork of the relevant SDK—either in the official sample repository or in my personal repository. Looking forward to your review.

@ognis1205 ognis1205 force-pushed the poc/agent-catalog branch from 60ba67b to 615e33b Compare May 28, 2025 00:41
Copy link
Contributor Author

@ognis1205 ognis1205 left a comment

Choose a reason for hiding this comment

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

TODO: To complete this change, the specification document docs/specification.md should also be updated accordingly.

Done.

@ognis1205
Copy link
Contributor Author

ognis1205 commented May 29, 2025

PoC: SDK Example for Agent Mounting and API Catalog Discovery

I’ve implemented a proof-of-concept SDK that demonstrates how to define and mount multiple A2A agents using A2AStarletteRouteBuilder and A2AStarletteBuilder. Each agent is configured with its own AgentCard, skill set, and handler, and mounted to a shared Starlette application. The resulting server responds with a valid A2A catalog at /.well-known/api-catalog.json, like the following:

{
  "linkset": [
    {
      "anchor": "http://localhost:9999/a2a/hello/",
      "describedby": [
        {
          "href": "http://localhost:9999/a2a/hello/agent.json",
          "type": "application/json"
        }
      ]
    },
    {
      "anchor": "http://localhost:9999/a2a/echo/",
      "describedby": [
        {
          "href": "http://localhost:9999/a2a/echo/agent.json",
          "type": "application/json"
        }
      ]
    }
  ]
}

Minimal Example Code

@click.command()
@click.option('--host', default='localhost')
@click.option('--port', default=9999)
def main(host: str, port: int):
    hello_skill = AgentSkill(
        id='hello_world',
        name='Returns hello world',
        description='just returns hello world',
        tags=['hello world'],
        examples=['hi', 'hello world'],
    )
    hello_card = AgentCard(
        name='Hello World Agent',
        description='Just a hello world agent',
        url=f'http://{host}:{port}/a2a/hello',
        version='1.0.0',
        defaultInputModes=['text'],
        defaultOutputModes=['text'],
        capabilities=AgentCapabilities(streaming=True),
        skills=[hello_skill],
        supportsAuthenticatedExtendedCard=False,
    )
    hello_handler = DefaultRequestHandler(
        agent_executor=HelloWorldAgentExecutor(),
        task_store=InMemoryTaskStore(),
    )
    hello_agent = A2AStarletteRouteBuilder(
        agent_card=hello_card,
        http_handler=hello_handler,
    )

    echo_skill = AgentSkill(
        id="echo",
        name="Echo input",
        description="Returns the input text as is",
        tags=["echo"],
        examples=["Hello!", "Repeat after me"],
    )
    echo_card = AgentCard(
        name="Echo Agent",
        description="An agent that echoes back your input.",
        url=f"http://{host}:{port}/a2a/echo",
        version="1.0.0",
        defaultInputModes=["text"],
        defaultOutputModes=["text"],
        capabilities=AgentCapabilities(streaming=True),
        skills=[echo_skill],
        supportsAuthenticatedExtendedCard=False,
    )
    echo_handler = DefaultRequestHandler(
        agent_executor=EchoAgentExecutor(),
        task_store=InMemoryTaskStore(),
    )
    echo_agent = A2AStarletteRouteBuilder(
        agent_card=echo_card,
        http_handler=echo_handler,
    )

    server = (
        A2AStarletteBuilder()
            .mount(hello_agent)
            .mount(echo_agent)
            .build()
    )
    uvicorn.run(server, host=host, port=port)


if __name__ == "__main__":
    try:
        main()
    except Exception:
        print(traceback.format_exc(), file=sys.stderr)

This can serve as a practical SDK example when reviewing this PR. The following is the related PoC implementation of the SDK:

@ognis1205 ognis1205 force-pushed the poc/agent-catalog branch from 615e33b to 10a75d9 Compare May 29, 2025 22:25
@ognis1205 ognis1205 requested a review from a team as a code owner May 29, 2025 22:25
@ognis1205 ognis1205 force-pushed the poc/agent-catalog branch from 10a75d9 to b2ea7c0 Compare May 31, 2025 12:41
@ognis1205 ognis1205 requested a review from holtskinner May 31, 2025 23:06
@ognis1205 ognis1205 force-pushed the poc/agent-catalog branch from 82c177a to 746f844 Compare June 3, 2025 17:57
@holtskinner holtskinner requested a review from kthota-g June 3, 2025 19:45
@ognis1205 ognis1205 force-pushed the poc/agent-catalog branch 2 times, most recently from d25b439 to 33ed3bb Compare June 22, 2025 20:19
@holtskinner holtskinner requested a review from a team as a code owner June 23, 2025 19:50
@ognis1205 ognis1205 force-pushed the poc/agent-catalog branch 2 times, most recently from 749d40b to 63940dd Compare June 26, 2025 16:57
@darrelmiller
Copy link
Contributor

The API Catalog draft is now a finalized RFC https://datatracker.ietf.org/doc/rfc9727/

@ognis1205
Copy link
Contributor Author

The API Catalog draft is now a finalized RFC https://datatracker.ietf.org/doc/rfc9727/

Thanks @Kevsy @darrelmiller for all the hard work in getting this over the line — huge respect for pushing this through the standardization process. I’ll go over the finalized RFC again carefully. Looking forward to seeing it adopted more widely!

@ognis1205
Copy link
Contributor Author

Also, if you (and/or the community) think this issue/PR would be better opened or led by you, @darrelmiller, please don’t hesitate to let me know — happy to step back if needed.

@ognis1205 ognis1205 force-pushed the poc/agent-catalog branch 2 times, most recently from a5497f3 to 6e903e9 Compare August 13, 2025 15:54
@ognis1205 ognis1205 force-pushed the poc/agent-catalog branch 3 times, most recently from d9d7b62 to da16733 Compare September 10, 2025 15:53
@ognis1205 ognis1205 force-pushed the poc/agent-catalog branch 2 times, most recently from e5b9f52 to d075e00 Compare December 8, 2025 07:34
@holtskinner holtskinner added the TSC Review To be reviewed by the Technical Steering Committee label Dec 8, 2025
@github-project-automation github-project-automation bot moved this to Backlog in TSC Review Dec 8, 2025
Copy link
Contributor Author

@ognis1205 ognis1205 left a comment

Choose a reason for hiding this comment

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

TODO:

Migrate this definition to the protobuf schema according to
https://github.com/a2aproject/A2A/blob/33f065d3daf6c4bc6f0ada184bab149eadc1985a/specification/json/README.md.

Done.

@ognis1205 ognis1205 force-pushed the poc/agent-catalog branch 3 times, most recently from 696401e to 02f23ef Compare December 27, 2025 23:39
…API Catalog

Signed-off-by: Shingo OKAWA <shingo.okawa.g.h.c@gmail.com>
Signed-off-by: Shingo OKAWA <shingo.okawa.g.h.c@gmail.com>

chore(proto): disable pluralized-name lint for RFC-defined fields

Signed-off-by: Shingo OKAWA <shingo.okawa.g.h.c@gmail.com>

chore(proto): disable pluralized-name lint for RFC-defined fields

Signed-off-by: Shingo OKAWA <shingo.okawa.g.h.c@gmail.com>
Signed-off-by: Shingo OKAWA <shingo.okawa.g.h.c@gmail.com>

fix(docs): replace typographic apostrophe

Signed-off-by: Shingo OKAWA <shingo.okawa.g.h.c@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

TSC Review To be reviewed by the Technical Steering Committee

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

[Feat]: Support multi-agent open discovery under a single base URL via RFC 9264-compatible API Catalog

7 participants