Skip to content

fix(a2a): agent card omits /a2a path in url field — fleet RPC dispatches POST to root and 404 #3536

@mabry1985

Description

@mabry1985

Symptom

protoMaker's A2A agent card returns the wrong endpoint URL. When a fleet client (workstacean's @a2a-js/sdk) reads the card and uses `card.url` as the JSON-RPC transport endpoint, the POST goes to the root path and gets a 404.

Filed from protoLabsAI/protoWorkstacean log evidence (workstacean container, 2026-04-20T16:30Z).

Repro

```
$ docker exec workstacean curl -s http://automaker-server:3008/.well-known/agent-card.json | jq '{url, additionalInterfaces, preferredTransport}'
{
"url": "http://automaker-server:3008\",
"additionalInterfaces": [],
"preferredTransport": null
}
```

The card URL is missing the `/a2a` path. Compare with the working agents in the fleet:

Agent Card url Result
quinn `http://quinn:7870/a2a\` ✓ dispatch works
researcher `http://researcher:7870/a2a\` ✓ dispatch works
protomaker `http://automaker-server:3008\` ✗ POST goes to / → 404 "Cannot POST /"

When workstacean's ceremony `board.health` dispatches the `board_health` skill (which protoMaker advertises in its card), the SDK reads card.url=`http://automaker-server:3008\` and POSTs to that. The Express/Fastify root handler returns:

```html

<title>Error</title>
Cannot POST /
\`\`\`

Verified the actual /a2a endpoint works fine (200 with valid JSON-RPC) — the bug is purely in the card's url advertisement.

Impact

  • workstacean's `board.health` ceremony fails every cron fire (30-min schedule)
  • GOAP `action.protomaker_triage_blocked` and `action.protomaker_start_auto_mode` (which target [protomaker] explicitly) work via the registry-target path that bypasses card url, but skill-name-only dispatches of `board_health`, `auto_mode`, etc. fail
  • Any other A2A consumer that uses standard SDK + card discovery hits the same 404

Suggested fix

The card builder in apps/server (wherever `/.well-known/agent-card.json` is constructed) should emit one of:

Option A — canonical (matches quinn/researcher pattern):
```json
{ "url": "http://automaker-server:3008/a2a\" }
```

Option B — A2A spec preferred (multi-transport):
```json
{
"url": "http://automaker-server:3008/a2a\",
"additionalInterfaces": [
{ "transport": "jsonrpc", "url": "http://automaker-server:3008/a2a" }
],
"preferredTransport": "jsonrpc"
}
```

The host should be configurable per-deploy (in dev compose it's `automaker-server:3008`; in prod it might be different). Whatever env var or config currently sets the card's host (`A2A_ADVERTISE_URL`, `PUBLIC_BASE_URL`, etc.) — append `/a2a` to it before emitting.

How to verify post-fix

```
docker exec workstacean curl -s http://automaker-server:3008/.well-known/agent-card.json | jq '.url'

expected: "http://automaker-server:3008/a2a\"

docker logs --since 30m workstacean | grep "FAILURE: board.health"

expected: empty (after next ceremony tick at top of half-hour)

docker logs --since 30m workstacean | grep "Executor.*a2a.*error.*board_health"

expected: empty

```

Cross-references

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions