Last validated: Langflow 1.12.x (nightly 1.12.0.dev18)
Issue: #1242 · Scoped by: #1195 → a2a-coverage-scope.md (row T3) ·
Depends on: #1240 · Jira: epic LE-1588, registry LE-1719
GET /api/v1/a2a/agents is the catalog an orchestrator or the A2AAgent
component in Internal mode reads to find out which local flows are reachable as
A2A agents. It is authenticated and owner-scoped — deliberately not a
cross-user directory, because a public one would strip the per-flow card's
unguessable-id obscurity and expose other users' agents.
Its contract is a filter, and the filter is the whole point:
- Publishing puts a flow in the list, with a
cardUrlthat actually resolves. - A
flow_type=workflowflow is never listed, no matter what else is set. - An
agent-typed flow witha2a_enabledfalse is never listed — the two conditions are ANDed, not ORed. - Unpublishing removes it, so the catalog reflects current state rather than history.
If this regresses, the failure is quiet and asymmetric: a missing row makes a
published agent invisible to every consumer (the A2AAgent dropdown goes empty),
while an extra row advertises a flow that answers 404 on its card — an
orchestrator wires itself to a dead endpoint.
@stable @api @a2a
@api— pure REST, no UI.@a2a— this area's functional tag (added by #1242).- No
@release: discovery is not on the critical publish path — a caller that already knows the flow id reaches the card directly. The card andmessage/sendspecs carry@release. @stable— validated by the team and promoted in #1349: the batch ran 51/51 green (17 tests × 3,--retries=0) on nightly1.12.0.dev18, with no leaked flow and no backend error logged.
With three flows created in one test — A (flow_type=agent,
a2a_enabled=true), B (flow_type=workflow, a2a_enabled=true) and C
(flow_type=agent, a2a_enabled=false) — a single GET /api/v1/a2a/agents
returns 200 and its id set contains A and contains neither B nor
C. Fetching A's cardUrl verbatim from the response returns 200. After
PATCHing A to a2a_enabled=false, a fresh GET no longer contains it.
The three flows exist simultaneously on purpose: asserting only "A is present" would pass against an endpoint that lists everything, and asserting only "B is absent" would pass against an endpoint that lists nothing.
LANGFLOW_A2A_ENABLED=true(#1240); enforced in-test byrequireA2aEnabled().- No LLM, no provider key, no external network — three copies of the
Chat Input → Chat Output passthrough (
createRunnableChatFlowViaApi()). - Auto-login superuser: under
AUTO_LOGINthe caller and the flow owner are the same user, which is what makes the owner-scoped list observable at all.
- A2A-enabled Langflow at
PLAYWRIGHT_BASE_URL. - The assertions are set-membership, never list length. The superuser account
is shared and other specs may hold published agents concurrently; asserting
agents.length === 1would be a cross-spec flake. Every assertion is about the presence or absence of this test's ids.
Two tests. All flows are created by the test and deleted by id in afterEach.
Test 1 — discovery lists only agent-typed, A2A-enabled flows
requireA2aEnabled(request).- Create flows A, B, C via
createRunnableChatFlowViaApi(); keep the three ids. PATCHA →{ flow_type: "agent", a2a_enabled: true }.PATCHB →{ flow_type: "workflow", a2a_enabled: true }.PATCHC →{ flow_type: "agent", a2a_enabled: false }.GET /api/v1/a2a/agents→ assert200and that the body is an array.- Build the id set; assert it contains A, does not contain B, does not contain C.
- Read A's row; assert its keys are exactly
id,name,description,cardUrl, and thatcardUrlends in/api/v1/a2a/{A}/.well-known/agent-card.json. GETthatcardUrlverbatim from the response → assert200. This is what proves the catalog hands out a working address rather than a plausible string.- Delete A, B, C.
Test 2 — unpublishing removes the flow from discovery
- Create and publish flow A;
GET /api/v1/a2a/agents→ assert A present. PATCHA →{ a2a_enabled: false }.GET /api/v1/a2a/agents→ assert A absent.GETA's card → assert404, tying the catalog and the card to the same state (a catalog that dropped the row while the card still served would be the worse half-failure).- Delete A.
| # | Test | Observable |
|---|---|---|
| 1 | filter | one GET: A present, B and C absent; A's row keys exact; A's cardUrl fetched verbatim → 200 |
| 2 | unpublish | A present → PATCH a2a_enabled=false → A absent and its card 404 |
- The row serves the FLOW's name, not the card override. A flow published with
a2a_card_overrides.name = "Scout Agent"was still listed asscout2-agent-068a93. Asserting on the name would encode a bug as expected behaviour; the spec assertsidandcardUrl. - Row keys are exactly
['cardUrl', 'description', 'id', 'name']— noflow_type, noa2a_enabled, so the reason a flow is listed is not observable from the row. That is why the filter is proven by absence of B and C rather than by inspecting fields. - The endpoint is 404 when the server flag is off (the guard is a route
dependency, resolved before auth, so an anonymous caller cannot tell a disabled
route from an unmounted one).
requireA2aEnabled()exists so this spec reports that as a configuration error instead of a filter failure.