feat: bring FuzeAgent to the platform's 4-pod standard - #144
Open
github-actions[bot] wants to merge 1 commit into
Open
feat: bring FuzeAgent to the platform's 4-pod standard#144github-actions[bot] wants to merge 1 commit into
github-actions[bot] wants to merge 1 commit into
Conversation
FuzeAgent already deployed a backend (orchestrator), a frontend (ui) and the family's A2A server. What it had NO contract for was the API that dispatches autonomous agents — which is exactly the API where an unclassified tool surface is dangerous. This adds that contract, serves it, and adds the config-driven MCP gateway pod configured from it. contracts/openapi.yaml — NEW 123 paths / 142 operations, extracted statically (ast, no imports) from every @app.<method> decorator in services/orchestrator/main.py. Summaries are the handlers' own docstrings. Nothing invented. FINDING — seven duplicate (path, method) registrations. FastAPI keeps the FIRST and the later handler never runs. Two of them are not redundant definitions but DIFFERENT implementations that are dead code: register_agent_capabilities (shadowed by register_agent) and get_agent_tasks_list (shadowed by get_agent_tasks). All seven are tabulated in contracts/README.md. FINDING — the contract covers ONE of FuzeAgent's two backends. services/ hierarchy_API is a second FastAPI app on its own Service (port 8006) with ~40 routes. One OpenAPI document maps to one upstream base URL, so it needs its own contract and its own gateway pod; until then those operations are not on the MCP surface. Said plainly rather than reported as four green pods. The contract is SERVED, not just committed GET /openapi.yaml is new in main.py, reading the copy baked into the image. This is NOT /openapi.json: FastAPI generates that from the code and it says nothing about which operations dispatch an agent that cannot be recalled. Both are served; the curated one is the contract. A missing document is DEGRADED, not fatal — /health keeps returning 200 and gains `"openapi": "loaded" | "unavailable"`, and the endpoint answers 503. MCP pod — and why it is NOT the same as the mcpServer already deployed The chart already runs mcp-servers/fuzeagent-server: a hand-written MCP SSE server with ~15 curated tools and, per .fuze/manifest.json's own note, NO mutates classification at all. The new pod runs @fuzefront/mcp-gateway over contracts/openapi.yaml, which derives the classification mechanically and refuses to boot on a contradictory one. Both are left in place; whether to retire the hand-written one is an owner call, flagged not silently resolved. VERIFIED, not asserted: the real gateway was booted from the exact ConfigMap bytes `helm template` renders. 142 tools enumerate, 74 reads / 68 writes, 16 irreversible, no tool is both a read and irreversible, and startTaskExecution reaches the model prefixed "[WRITE — IRREVERSIBLE]". IRREVERSIBLE (16) — classified on EFFECT, never on verb: dispatch / execute (POST, defaults to reversible — overridden): startTaskExecution, initiateTaskCoordination, executeCommandInSandbox, executeContainerCommand, sendClaudeSessionInput, sendAgentCommunication, sendMessageToAgent, submitHumanResponse, deployMemoryEnabledAgent creates something this API cannot remove: createAgent, createAgentFromTemplate, assignTask, storeProviderCredentials (there is NO DELETE /agents/{id} and NO DELETE /tasks/{id} in the contract) DELETE default, left as-is: deleteAgentDocument, deleteOrganizationDocument, deleteTeamDocument Deliberately NOT irreversible, with reasons in the overrides: approveFileOperations writes files, but the contract contains the exact compensating operation (.../rollback) plus a preview stopMemoryEnabledAgent a DELETE that stops an agent which can be redeployed removeAgentContainer / destroySandbox re-creatable runtime artefacts cancelTaskExecution pinned reversible ONLY so nobody reads it as the undo for startTaskExecution. IT IS NOT. POST /rag/search is already a read via the gateway's suffix allowlist (verified: mutates=false), so it needs no entry. POST /mcp/call-tool keeps the default and is flagged: it is a passthrough whose reversibility belongs to the downstream tool and is not derivable from this contract. Slug: fuzeagent -> agent, FuzeAgent -> Agent. DECLARATION ONLY; `slug` is immutable and the live migration is owned elsewhere. No A2A pod is added: A2A is one shared multi-tenant server and this repo IS its home. deploy/argocd/README.md records the evidence that a2a-shared is wired for deployment (Application on main, enabled:true, pinned image, Service named a2a-shared:8080 matching the card's hardcoded URL, three tenants enabled) and is explicit that a running healthy pod could NOT be verified without cluster access. No Argo Application added, edited or removed — FuzeInfra owns those. No auth wiring touched (services/orchestrator/fuze_security.py, auth.py and the whole a2a-shared chart are untouched, leaving claude/fuze-security-migration clean). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GaPa3JgrVNtWrGvqQEAEqv
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
FuzeAgent already deployed a backend (
orchestrator), a frontend (ui) and the family's A2A server. What it had no contract for was the API that dispatches autonomous agents — precisely the API where an unclassified tool surface is dangerous.🟢 The A2A question, answered
This was the open question for the whole family, so here is evidence rather than a claim.
a2a-sharedis wired for deployment, not merely implemented:deploy/argocd/applications/a2a-shared.yamlexists onmain→path: deploy/helm/a2a-shared,targetRevision: main,valueFiles: [values-prod.yaml],namespace: fuzeagent,automated: {prune, selfHeal}.values-prod.yamlonmainhasa2a.enabled: trueand a pinned imageghcr.io/izzywdev/fuzeagent-a2a:e2d7d1c2b55a.helm templateagainst those prod values renders a Deployment and Service both nameda2a-shared, port 8080, selector matching exactly one workload — i.e. exactly the address the image hardcodes on every card (card_generator.py:29,http://a2a-shared.fuzeagent.svc.cluster.local:8080/rpc)._helpers.tplpins that name deliberately and explains why.FuzeAgent(agent-orchestrator),FuzeFront(app-shell-platform, refmaster),FuzePlan(product-manager).What could NOT be verified: whether the pod is actually running and healthy. No cluster access, by constraint. Everything above is GitOps declaration — the source of truth for what Argo will apply, but "declared" is not "up". Check
kubectl -n fuzeagent get deploy,svc a2a-sharedand its/healthzbefore relying on it. Also confirma2a-repos-gitcan clone FuzePlan, which is private.Minor dead value found:
deploy.stateConfigMap: a2a-stateis set in prod values buttemplates/deployment.yamlmounts anemptyDirfor/stateand never reads it; nothing creates that ConfigMap. Harmless, but a no-op.No A2A pod is added anywhere. A per-product A2A Deployment is structurally unreachable —
_interface()returns that module constant for every non-external card, so such a pod would publish the shared server's address and never be called.FuzeAgent's real A2A configuration contract
Recorded here because other convergence batches are guessing at it. A repo onboards by adding a
tenants[]entry toFuzeAgent deploy/helm/a2a-shared/values-prod.yaml— never a pod in its own chart.Frozen schema:
agent-templates/contracts/a2a/v1/schema/values-interface.schema.json—additionalProperties: falseat every level, so an invented key is a hard failure. Parsed byagent-templates/a2a/config.py::load_config.The
a2ablock (server-wide):Per-tenant entry (
$defs/tenant;tenant,repo,enabledrequired):Deployment mechanics live OUTSIDE
a2a(in a siblingdeploy:block) precisely soa2astays byte-conformant to the frozen interface — it is serialised verbatim into thevalues.jsonthe server parses:replicas,imagePullSecrets,resources,externalDomain,ingressClassName,agentProvider,gitImage,reposGitTokenSecretRef,providerApiKeySecretRef,stateConfigMap.Container env surface (
agent-templates/a2a/Dockerfile+runtime.build_from_env) — this is the entire set:A2A_VALUES_FILE/config/values.json— the{"a2a": …}documentA2A_REPOS_DIR/repos— tenant checkouts, read byLocalRepoResolverAGENT_PROVIDERanthropicHOST0.0.0.0FUZE_STATE_DIR/state, must be writableANTHROPIC_API_KEYproviderApiKeySecretRefThe listen port is NOT an env var — the server reads
a2a.service.portfrom the values file, so image and chart share one source.Prerequisites in the tenant's own repo — the card is a pure projection and will fail at boot without them:
.fuze/manifest.jsonwith ana2ablock (entryRole,servingRoles), andagent-templates/roles/<entryRole>/role.json.FuzePlan has both. FuzeContact has neither (no
a2akey, noagent-templates/at all), which is why it cannot be registered yet.Runtime surface:
POST /rpc(JSON-RPC 2.0, bare PascalCase methods),GET /.well-known/agent-card.json(unauthenticated,?tenant=selects),GET /extendedAgentCard(authenticated),GET /healthz.LocalRepoResolverreads<A2A_REPOS_DIR>/<repo-name-after-slash>, so the chart must clone each enabled tenant out of band.tools,mcp_servers,persona,model,environmentandvaultare never projected onto a card.contracts/openapi.yaml— new123 paths / 142 operations, extracted statically (
ast, no imports) from every@app.<method>decorator inservices/orchestrator/main.py. Summaries are the handlers' own docstrings.🔴 Finding: seven duplicate route registrations
FastAPI keeps the first handler for a
(path, method); the later one is dead code. Two are not redundant definitions but different implementations that never run:register_agent_capabilities(shadowed byregister_agent) andget_agent_tasks_list(shadowed byget_agent_tasks). All seven are tabulated incontracts/README.md.🔴 Finding: the contract covers ONE of FuzeAgent's TWO backends
services/hierarchy_APIis a second FastAPI app on its own Service (port 8006) with ~40 routes. One OpenAPI document maps to one upstream base URL, so it needs its own contract and its own gateway pod. Until then those operations are not on the MCP surface — said plainly rather than reported as four green pods.The contract is SERVED
GET /openapi.yamlis new inmain.py, reading the copy baked into the image. This is not/openapi.json: FastAPI generates that from the code and it says nothing about which operations dispatch an agent that cannot be recalled. Both are served; the curated one is the contract. A missing document is degraded, not fatal —/healthstays 200 and gains"openapi": "loaded" | "unavailable", the endpoint answers 503.MCP pod — and why it is not the
mcpServeralready deployedThe chart already runs
mcp-servers/fuzeagent-server: a hand-written MCP SSE server with ~15 curated tools and, per.fuze/manifest.json's own note, nomutatesclassification at all. The new pod runs@fuzefront/mcp-gatewayovercontracts/openapi.yaml, deriving the classification mechanically and refusing to boot on a contradictory one.That difference matters more here than anywhere else in the family. Both are left in place; whether to retire the hand-written one is an owner call, flagged rather than silently resolved.
mutatesclassification — on effect, never on verbVerified, not asserted: the real gateway was booted from the exact ConfigMap bytes
helm templaterenders. 142 tools, 74 reads / 68 writes, 16 irreversible, no tool both a read and irreversible, andstartTaskExecution's description reaches the model prefixed[WRITE — IRREVERSIBLE].Two structural facts drive the list: there is no
DELETE /agents/{id}and noDELETE /tasks/{id}, and the only genuine undo in the whole contract isPOST /tasks/{task_id}/file-operations/{batch_id}/rollback.IRREVERSIBLE (16)
Dispatch / execute — POST, which defaults to reversible:
startTaskExecution(hands the task to an agent that writes files, runs commands, calls external APIs;cancelstops future work and undoes nothing),initiateTaskCoordination,executeCommandInSandbox,executeContainerCommand,sendClaudeSessionInput,sendAgentCommunication,sendMessageToAgent,submitHumanResponse,deployMemoryEnabledAgent.Creates what this API cannot remove:
createAgent,createAgentFromTemplate,assignTask,storeProviderCredentials.DELETE default, left as-is: the three
delete*Documentoperations.Deliberately NOT irreversible, with reasons:
approveFileOperations— writes files, but the contract contains the exact compensating operation (…/rollback) plus apreview. A compensating operation that actually exists is what reversible means.stopMemoryEnabledAgent— aDELETEon/agents/{id}/memorythat (verified in the handler) stops the agent; the exact inverse ofdeployMemoryEnabledAgent.removeAgentContainer,destroySandbox— re-creatable runtime artefacts.cancelTaskExecution/cancelCoordination— pinned reversible only so nobody reads them as the undo forstartTaskExecution. They are not.POST /rag/searchis already a read via the gateway's suffix allowlist (verifiedmutates=false).POST /mcp/call-toolis a genuine gap: a passthrough whose reversibility belongs to the downstream tool and is not derivable from this contract. It keeps the reversible-write default, the least-wrong claim available.Slug de-prefix — declaration only
fuzeagent→agent,FuzeAgent→Agent.slugis immutable; no migration code, no registry call.validate-registrationpasses;agent(5 chars) satisfies the ≥3-characterSlugminimum.Verified / not verified
Verified
helm lint --strict;helm templatedefault /values-prod.yaml/--set mcp.enabled=true.scripts/check-rendered-chart.py: 8 Services each selecting exactly one workload, no empty image refs, probes on declared ports./healthz→{"product":"agent","tools":142}.a2a-sharedrendered with prod values and inspected (Service name, port, selector, image, volumes, env).operationId(two were wrong on the first pass and were corrected against the spec).python3 -m py_compile services/orchestrator/main.py.NOT verified
a2a-sharedis actually running is unknown.ghcr.io/izzywdev/fuze-mcp-gateway:0.1.0is FuzeFront's image; no workflow in this repo builds it. Absent from GHCR ⇒ImagePullBackOff, not a working pod.GET /openapi.yamlresponse was not exercised against a running app, for the same reason.resourceGovernance.quotawas sized fora2a-sharedplus its rolling-update surge; the MCP pod fits on paper (50m/64Mi requested) but re-check before enabling.Coordination
No auth wiring touched —
services/orchestrator/fuze_security.py,auth.pyand the entiredeploy/helm/a2a-sharedchart are untouched, soclaude/fuze-security-migrationstays clean. No ArgoApplicationadded, edited or removed;deploy/argocd/README.mdcarries the handoff.