You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tracker: #2729 Read first: docs/studio/engine-api-contract.md §7 (Endpoint Catalog), §10 (Errors), §11 (Pagination); agentmesh.engine_api public surface from #3026 (PR #3027).
Summary
Stand up the reference FastAPI adapter for the Engine API contract that landed in PR #3013. One app, one process, all the v1 HTTP routes from §7. Every route uses the capability_flags decorator from agentmesh.engine_api and the generated OpenAPI document carries the x-capability-flags extension on every operation. This issue also fixes the long-standing counts-only gap on GET /api/v1/policies in agent-governance-python/agent-mesh/src/agentmesh/server/policy_server.py (lines 168 ff.).
This is the largest single issue in Epic 0. Treat it as the canonical reference implementation that all downstream Studio panels (Epics 2-9) will be wired against.
Pydantic response models: PolicySummary, PolicyDetail, AuditEntry, TrustScore, TrustGraph, AgentSummary, DecisionEntry, VersionsResponse, etc. Match field names from §7 exactly.
Generated OpenAPI doc has x-capability-flags on every operation; allowlist derivation produces the spec-mandated 11-route list (per §6.2).
Scope (in)
Implement all 11 read-only routes from §7.1-7.3, §7.4-7.5, §7.7-7.12 plus the single mutating routePOST /api/v1/policy/save (§7.6) — 12 HTTP operations total. Every route decorated with @capability_flags(...) using the exact flag values from §5.2.
Fix GET /api/v1/policies to return paginated PolicySummary objects per §7.2, not the current totals dict (policy_server.py:168).
Wire inject_capability_extension so GET /openapi.json carries x-capability-flags on every operation.
Error envelope (§10.2). errors.py defines the full §10.3 code enum (POLICY_NOT_FOUND, POLICY_PARSE_ERROR, FIXTURE_LOAD_ERROR, VALIDATION_ERROR, UNAUTHORIZED, RATE_LIMITED, ENGINE_UNAVAILABLE, INTERNAL_ERROR). This issue's routes can actually emit four of them: POLICY_NOT_FOUND, POLICY_PARSE_ERROR, FIXTURE_LOAD_ERROR, VALIDATION_ERROR. UNAUTHORIZED is reserved for the auth layer (issue build(deps): Bump hono from 4.11.7 to 4.12.5 in /packages/agent-os/extensions/mcp-server #7) — define the code, don't wire it here. FastAPI's default RequestValidationError handler must be overridden to emit the envelope shape.
Pagination (§11) on the 5 endpoints §11.3 marks paginated: /policies, /audit/log, /trust/scores, /agents, /decisions. PaginationParams as a FastAPI dependency (page default 1 min 1; limit default 20 min 1 max 100). Response wrapper is top-level items + pagination object (page, limit, total, has_next) per §11.2.
Backends for routes that don't have one yet return spec-conformant empties — do not invent fake data, mark each with a single # TODO(#<followup-when-filed>): comment pointing at the owning epic:
/trust/graph (§7.9) is not paginated — it returns a graph object { "nodes": [], "edges": [] } (Epic 8).
GET /api/v1/versions (§7.12) returns: api: "1.0.0", engine: <agent-mesh package version via importlib.metadata>, python: <optional runtime version>, capabilities: <optional array of capability identifier strings>. Wire api and engine against live values, not hardcoded literals.
Sidecar entry point: python -m agentmesh.engine_api that runs uvicorn against create_app(), default bind 127.0.0.1:8080 (the spec §4 default). Port configurable via --port. Loopback-only by default (host arg defaults to 127.0.0.1, not 0.0.0.0).
Real implementations for trust/audit/agents/decisions backends — those are their own epics. Item 6 above defines the placeholder contract.
Streamlit dashboards, HTML pages, any UI work. This issue produces an HTTP surface only.
Acceptance criteria
python -c "from agentmesh.engine_api import create_app; create_app()" succeeds with no warnings.
All 12 HTTP operations (11 read-only + POST /api/v1/policy/save) registered on the app. Verified by iterating app.routes in a test.
Every registered route has __capability_flags__ set and the flag values match §5.2 of the contract doc exactly.
app.openapi() returns a document where every operation under paths.*.[get|post|...] carries an x-capability-flags object with all three boolean fields.
derive_studio_client_allowlist(app.openapi()) returns exactly the 11 routes listed in §6.2 of the contract, in sorted order. No more, no fewer.
POST /api/v1/policy/reload does NOT appear in the app. A test asserts its absence.
GET /api/v1/policies returns a paginated PolicySummary list when there are loaded policies and an empty paginated response otherwise. Test covers both.
GET /api/v1/policies/{id} returns 404 with code: "POLICY_NOT_FOUND" envelope when id is unknown.
POST /api/v1/policy/validate with malformed YAML returns 422 with code: "POLICY_PARSE_ERROR" envelope.
GET /api/v1/versions returns api == "1.0.0" and a non-empty engine string. (capabilities is optional; assert it is a list if present.)
GET /api/v1/trust/graph returns a { "nodes": [...], "edges": [...] } object, not a paginated items/pagination wrapper.
All FastAPI RequestValidationError 422 responses are remapped to the envelope shape with code: "VALIDATION_ERROR". Test covers one validation failure end-to-end (assert the body is the envelope, not FastAPI's default {"detail": [...]}).
Pagination parses per §11.1 (page default 1 / min 1; limit default 20 / min 1 / max 100). Out-of-range values produce a VALIDATION_ERROR envelope. The 4 placeholder list routes and /policies all return the items + pagination wrapper from §11.2.
python -m agentmesh.engine_api --help prints usage; default bind is 127.0.0.1:8080. A test asserts the default host and port in the CLI parser.
ruff check --select E,F,W --ignore E501 passes on changed files.
pytest agent-governance-python/agent-mesh/tests/engine_api/ passes. All new test modules ship green.
Design notes
No business-logic refactor in this PR. Where data is needed, import from existing modules (e.g., the policy registry helpers in policy_server.py, agentmesh.governance.audit, agentmesh.services.registry.agent_registry). Do not move or rewrite them. If a clean source isn't available, use the empty placeholder pattern from scope item 6.
One app, one OpenAPI doc. Resist the temptation to mount sub-apps. The whole point of this issue is to give Studio one URL to discover the entire surface.
POST /api/v1/policy/save is the only operation in this app that mutates. Make this loud in code: keep it in its own route module (policy_ops.py) and add a module-level comment naming the spec invariant. Per §8.1, save triggers a policy reload as a side effect — that is the intended reload path, and the reason the standalone POST /api/v1/policy/reload route is excluded. Wire save so it persists then reloads; do not expose reload separately.
Error envelope handler ordering matters. Register the validation handler before the generic handler, or FastAPI's default will win. Add an integration test that confirms a RequestValidationError produces the envelope, not FastAPI's default {"detail": [...]}.
Implement the route groups in this order so each PR-internal commit is reviewable: errors.py -> pagination.py -> models.py -> health.py -> versions.py -> policies.py -> policy_ops.py -> audit.py -> trust.py -> agents.py -> decisions.py -> app.py -> CLI entry point.
app.py exposes create_app(). Assemble the FastAPI instance, register every route module, then apply inject_capability_extension to the OpenAPI generator last so the extension sees all operations.
PR title MUST end with (Epic 0, issue #<this-issue-number>) so GitHub auto-closes on merge.
PR description must follow the repo template in .github/AGENTS.md (Summary / Problem / Changes table / Testing).
Expected PR size: ~1200-1800 LOC including tests. If you find yourself over 2500, stop and split.
Tracker: #2729
Read first:
docs/studio/engine-api-contract.md§7 (Endpoint Catalog), §10 (Errors), §11 (Pagination);agentmesh.engine_apipublic surface from #3026 (PR #3027).Summary
Stand up the reference FastAPI adapter for the Engine API contract that landed in PR #3013. One app, one process, all the v1 HTTP routes from §7. Every route uses the
capability_flagsdecorator fromagentmesh.engine_apiand the generated OpenAPI document carries thex-capability-flagsextension on every operation. This issue also fixes the long-standing counts-only gap onGET /api/v1/policiesinagent-governance-python/agent-mesh/src/agentmesh/server/policy_server.py(lines 168 ff.).This is the largest single issue in Epic 0. Treat it as the canonical reference implementation that all downstream Studio panels (Epics 2-9) will be wired against.
Deliverables
agent-governance-python/agent-mesh/src/agentmesh/engine_api/app.pycreate_app()returning a fully wiredFastAPIinstance withinject_capability_extensionalready applied to its OpenAPI generator.agent-governance-python/agent-mesh/src/agentmesh/engine_api/routes/health.py,policies.py,policy_ops.py,audit.py,trust.py,agents.py,decisions.py,versions.py.agent-governance-python/agent-mesh/src/agentmesh/engine_api/errors.pyHTTPExceptionand validation errors to §10.2 shape with §10.3 codes.agent-governance-python/agent-mesh/src/agentmesh/engine_api/pagination.pyPaginationParamsdependency +Paginationresponse model per §11.agent-governance-python/agent-mesh/src/agentmesh/engine_api/models.pyPolicySummary,PolicyDetail,AuditEntry,TrustScore,TrustGraph,AgentSummary,DecisionEntry,VersionsResponse, etc. Match field names from §7 exactly.agent-governance-python/agent-mesh/tests/engine_api/test_app.pyagent-governance-python/agent-mesh/tests/engine_api/test_routes_*.pyfastapi.testclient.TestClient.agent-governance-python/agent-mesh/tests/engine_api/test_errors.pyagent-governance-python/agent-mesh/tests/engine_api/test_pagination.pyagent-governance-python/agent-mesh/tests/engine_api/test_openapi_routes.pyx-capability-flagson every operation; allowlist derivation produces the spec-mandated 11-route list (per §6.2).Scope (in)
POST /api/v1/policy/save(§7.6) — 12 HTTP operations total. Every route decorated with@capability_flags(...)using the exact flag values from §5.2.GET /api/v1/policiesto return paginatedPolicySummaryobjects per §7.2, not the current totals dict (policy_server.py:168).inject_capability_extensionsoGET /openapi.jsoncarriesx-capability-flagson every operation.errors.pydefines the full §10.3 code enum (POLICY_NOT_FOUND,POLICY_PARSE_ERROR,FIXTURE_LOAD_ERROR,VALIDATION_ERROR,UNAUTHORIZED,RATE_LIMITED,ENGINE_UNAVAILABLE,INTERNAL_ERROR). This issue's routes can actually emit four of them:POLICY_NOT_FOUND,POLICY_PARSE_ERROR,FIXTURE_LOAD_ERROR,VALIDATION_ERROR.UNAUTHORIZEDis reserved for the auth layer (issue build(deps): Bump hono from 4.11.7 to 4.12.5 in /packages/agent-os/extensions/mcp-server #7) — define the code, don't wire it here. FastAPI's defaultRequestValidationErrorhandler must be overridden to emit the envelope shape./policies,/audit/log,/trust/scores,/agents,/decisions.PaginationParamsas a FastAPI dependency (pagedefault 1 min 1;limitdefault 20 min 1 max 100). Response wrapper is top-levelitems+paginationobject (page,limit,total,has_next) per §11.2.# TODO(#<followup-when-filed>):comment pointing at the owning epic:items: [],paginationwithtotal: 0):/audit/log(Epic 9),/trust/scores(Epic 8),/agents(Epic 8),/decisions(Epic 7)./trust/graph(§7.9) is not paginated — it returns a graph object{ "nodes": [], "edges": [] }(Epic 8).GET /api/v1/versions(§7.12) returns:api: "1.0.0",engine: <agent-mesh package version via importlib.metadata>,python: <optional runtime version>,capabilities: <optional array of capability identifier strings>. Wireapiandengineagainst live values, not hardcoded literals.python -m agentmesh.engine_apithat runsuvicornagainstcreate_app(), default bind127.0.0.1:8080(the spec §4 default). Port configurable via--port. Loopback-only by default (host arg defaults to127.0.0.1, not0.0.0.0).Scope (out)
/api/v1/events(§12) — that's Epic 7a, issue build(deps-dev): Bump eslint from 8.57.1 to 10.0.2 in /packages/agent-os/extensions/copilot #16. Do not implement, do not register, do not stub.POST /api/v1/policy/reload(§8.1) — explicitly forbidden by spec. Do not add.agt serve).policy_server.py,sidecar.py,audit_collector.py,trust_engine.py,api_gateway.py). They stay in tree for backward compatibility. Their deprecation is Epic 10 (issues fix: address Dependabot and CodeQL security alerts #23-chore: add CODEOWNERS and SBOM generation for CELA compliance #27).agt serve) — Epic 1b, issue build(deps): Bump hono from 4.11.7 to 4.12.5 in /packages/agent-os/extensions/mcp-server #7.Acceptance criteria
python -c "from agentmesh.engine_api import create_app; create_app()"succeeds with no warnings.POST /api/v1/policy/save) registered on the app. Verified by iteratingapp.routesin a test.__capability_flags__set and the flag values match §5.2 of the contract doc exactly.app.openapi()returns a document where every operation underpaths.*.[get|post|...]carries anx-capability-flagsobject with all three boolean fields.derive_studio_client_allowlist(app.openapi())returns exactly the 11 routes listed in §6.2 of the contract, in sorted order. No more, no fewer.POST /api/v1/policy/reloaddoes NOT appear in the app. A test asserts its absence.GET /api/v1/policiesreturns a paginatedPolicySummarylist when there are loaded policies and an empty paginated response otherwise. Test covers both.GET /api/v1/policies/{id}returns 404 withcode: "POLICY_NOT_FOUND"envelope when id is unknown.POST /api/v1/policy/validatewith malformed YAML returns 422 withcode: "POLICY_PARSE_ERROR"envelope.GET /api/v1/versionsreturnsapi == "1.0.0"and a non-emptyenginestring. (capabilitiesis optional; assert it is a list if present.)GET /api/v1/trust/graphreturns a{ "nodes": [...], "edges": [...] }object, not a paginateditems/paginationwrapper.RequestValidationError422 responses are remapped to the envelope shape withcode: "VALIDATION_ERROR". Test covers one validation failure end-to-end (assert the body is the envelope, not FastAPI's default{"detail": [...]}).pagedefault 1 / min 1;limitdefault 20 / min 1 / max 100). Out-of-range values produce aVALIDATION_ERRORenvelope. The 4 placeholder list routes and/policiesall return theitems+paginationwrapper from §11.2.python -m agentmesh.engine_api --helpprints usage; default bind is127.0.0.1:8080. A test asserts the default host and port in the CLI parser.ruff check --select E,F,W --ignore E501passes on changed files.pytest agent-governance-python/agent-mesh/tests/engine_api/passes. All new test modules ship green.Design notes
policy_server.py,agentmesh.governance.audit,agentmesh.services.registry.agent_registry). Do not move or rewrite them. If a clean source isn't available, use the empty placeholder pattern from scope item 6.capability_flagsis the ONLY way flags get attached. Do not hand-set__capability_flags__. Do not write your own decorator. The library from Engine API: capability metadata implementation (Epic 0, issue 2/32) #3026 is the contract.POST /api/v1/policy/saveis the only operation in this app that mutates. Make this loud in code: keep it in its own route module (policy_ops.py) and add a module-level comment naming the spec invariant. Per §8.1,savetriggers a policy reload as a side effect — that is the intended reload path, and the reason the standalonePOST /api/v1/policy/reloadroute is excluded. Wire save so it persists then reloads; do not expose reload separately.RequestValidationErrorproduces the envelope, not FastAPI's default{"detail": [...]}.pydantic.BaseModel) and the same version pin already inagent-mesh/pyproject.toml. Don't bump it.TestClient, not liveuvicorn. Spawning a real server in CI is flaky on Windows runners.References
docs/studio/engine-api-contract.md§5, §6, §7, §10, §11docs/studio/openapi.yamlagentmesh.engine_api(PR feat(engine-api): add capability-metadata library for AGT Studio #3027, merged 2026-06-15)agent-governance-python/agent-mesh/src/agentmesh/server/policy_server.pylines 165-175Notes for picking this up
git fetch origin main && git log origin/main --oneline -5before starting. Confirm PR feat(engine-api): add capability-metadata library for AGT Studio #3027 is in.errors.py->pagination.py->models.py->health.py->versions.py->policies.py->policy_ops.py->audit.py->trust.py->agents.py->decisions.py->app.py-> CLI entry point.app.pyexposescreate_app(). Assemble theFastAPIinstance, register every route module, then applyinject_capability_extensionto the OpenAPI generator last so the extension sees all operations.(Epic 0, issue #<this-issue-number>)so GitHub auto-closes on merge..github/AGENTS.md(Summary / Problem / Changes table / Testing).