Skip to content

Engine API: FastAPI reference adapter + /policies gap-fix (Epic 0, issue 3/32) #3066

Description

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.

Deliverables

Path Purpose
agent-governance-python/agent-mesh/src/agentmesh/engine_api/app.py FastAPI app factory create_app() returning a fully wired FastAPI instance with inject_capability_extension already applied to its OpenAPI generator.
agent-governance-python/agent-mesh/src/agentmesh/engine_api/routes/ One module per route group: 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.py Error envelope model + handler that maps HTTPException and validation errors to §10.2 shape with §10.3 codes.
agent-governance-python/agent-mesh/src/agentmesh/engine_api/pagination.py PaginationParams dependency + Pagination response model per §11.
agent-governance-python/agent-mesh/src/agentmesh/engine_api/models.py Pydantic response models: PolicySummary, PolicyDetail, AuditEntry, TrustScore, TrustGraph, AgentSummary, DecisionEntry, VersionsResponse, etc. Match field names from §7 exactly.
agent-governance-python/agent-mesh/tests/engine_api/test_app.py App factory + capability extension wiring.
agent-governance-python/agent-mesh/tests/engine_api/test_routes_*.py One test module per route group. Use fastapi.testclient.TestClient.
agent-governance-python/agent-mesh/tests/engine_api/test_errors.py Envelope shape, all standard codes from §10.3.
agent-governance-python/agent-mesh/tests/engine_api/test_pagination.py Query parsing + response object shape from §11.
agent-governance-python/agent-mesh/tests/engine_api/test_openapi_routes.py Generated OpenAPI doc has x-capability-flags on every operation; allowlist derivation produces the spec-mandated 11-route list (per §6.2).

Scope (in)

  1. Implement all 11 read-only routes from §7.1-7.3, §7.4-7.5, §7.7-7.12 plus the single mutating route 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.
  2. Fix GET /api/v1/policies to return paginated PolicySummary objects per §7.2, not the current totals dict (policy_server.py:168).
  3. Wire inject_capability_extension so GET /openapi.json carries x-capability-flags on every operation.
  4. 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.
  5. 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.
  6. 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:
    • Paginated empties (items: [], pagination with total: 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).
  7. 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.
  8. 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).

Scope (out)

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.
  • capability_flags is 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/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": [...]}.
  • Pydantic version: PR feat(engine-api): add capability-metadata library for AGT Studio #3027 settled this. Use the same import pattern (pydantic.BaseModel) and the same version pin already in agent-mesh/pyproject.toml. Don't bump it.
  • Tests must use TestClient, not live uvicorn. Spawning a real server in CI is flaky on Windows runners.

References

  • Contract spec: docs/studio/engine-api-contract.md §5, §6, §7, §10, §11
  • OpenAPI doc: docs/studio/openapi.yaml
  • Capability library: agentmesh.engine_api (PR feat(engine-api): add capability-metadata library for AGT Studio #3027, merged 2026-06-15)
  • Counts-only gap: agent-governance-python/agent-mesh/src/agentmesh/server/policy_server.py lines 165-175

Notes for picking this up

  • Run git fetch origin main && git log origin/main --oneline -5 before starting. Confirm PR feat(engine-api): add capability-metadata library for AGT Studio #3027 is in.
  • 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions