Agent Development Reference¶
This is the detailed companion to the concise, binding .agents/AGENTS.md. Read the root instructions first, then use this reference when the task needs repository topology, protocol details, or operational constants. Topic specifications remain authoritative for settled design.
Project status¶
An open-standard, sovereignty-first AI-agent collaboration platform: humans and AI agents share Matrix rooms, @mention to delegate tasks over A2A to local kagent agents governed by agentgateway or to explicitly pinned remote agents, stitched by a small Go bridge — the core room/delegation path is self-hosted with a per-cluster choice of model backend (docs/design-decisions.md D16), and Matrix federation is the differentiating destination for cross-organization agent collaboration (the open alternative to closed, tenant-anchored agent platforms). Stock Element X background notification is an explicit external metadata exception through Element and APNs/FCM; the production notification decision records the accepted residual. Kubernetes-native, Flux GitOps. Read the topic specs under docs/ first (see the mapping table below): design decisions D1–D20 with evidence, security model, federation design, licensing + homeserver strategy. Status: the core @mention path is live on local k3d (fgentic.localhost), and M0–M6 controls have deterministic gates. M7 adds disabled-by-default Slack/Telegram units plus bridged-sender policy; provider-owner and bidirectional acceptance remain explicit human gates. M8 has a provider-free fgentic-fed lab with three participating homeservers, one denied control, a git-reloadable Synapse callback border, and an exact public docs-qa delegation route (mise run fed:up; mise run fed:policy-reload). The lab verifies its ES256/JCS AgentCard, authorizes org B with a client-credentials JWT, reserves maxTokens per verified azp, reaches the deterministic model without exposing kagent directly, and returns one task-bound seller-signed usage receipt whose reservation and nullable consumption fields remain distinct. Its bridge fixture separately proves Signed AgentCard verification, attribution, rate limits, metrics, and fail-closed post-signature tampering against a standalone plain a2a-go runtime with no kagent resources. The GKE reference (clusters/gcp) remains spend-gated (issue #59).
Detailed roadmap and issue conventions¶
- The executable roadmap is the release ladder of GitHub milestones —
v1.0→v1.1→v2…v10→Backlog — unscheduled(the milestones page is the source of truth; docs/roadmap.md has the dated history and map). The milestone is the pickup gate, and the Definitive v1 focus board (#316) is the cut line. Each theme keeps akind/epictracker with sweep order and definition of done; withinv1.0, sweep the wedge epics (#85 federation → #86 production reference) top-to-bottom first. The formerM0–M34thematic milestones are closed — they described what, which is now the job ofarea/*and the epic trackers. - The taxonomy is three axes and four flags.
area/*= what the issue is about (bridge, ci, community, docs, federation, identity, infra, matrix, models, observability, security) ·priority/p0 → p1 → p2= urgency within its milestone · milestone = when it ships. Every non-epic issue carries all three; nothing else encodes release timing. Flags:kind/epic(milestone tracker, not pickable work),needs-human,needs-cluster,status/in-progress, plusgood first issue. Do not reintroduce a label that restates the milestone — that sprawl is exactly what this taxonomy replaced. - Human newcomer reservation:
good first issueis excluded from autonomous agent sweeps. An agent may help only when the human who already owns the cooperative lease explicitly requests assistance inside that claim. Maintainers curate and refresh the pool under issue #191; agents never manufacture or relabel candidates to keep a quota full. - Autonomy is the default and there is no
agent-readylabel — an issue is agent-workable unless it carriesneeds-human. Pick it up and finish it autonomously, peer review included. Design and architecture decisions, including proposing, deciding, and accepting ADRs, are agent-owned and never require a human.needs-human= one of the few genuinely terminal gates an agent cannot perform — only money or other cost commitment; deploying to cloud or production; an external account/identity action (install a GitHub App, flip the repo public, publish a release/talk/blog/upstream-PR under the maintainer's name); an external counterparty (first adopter, AGPL provider terms, co-maintainer); or legal publication sign-off. A**Human:**bullet names the terminal step; the label means do every preparable part up to that one gate. Design trade-offs, ADR acceptance, acceptance criteria, internal review, and capturing live proof on a local cluster are neverneeds-human; an issue blocked only on unfinished upstream work stays open and blocked, notneeds-human.needs-clustermarks work that needs a live k3d or GKE cluster to perform or to prove. - Issue claims are cooperative leases. Before editing, check assignees,
status/in-progress, issue comments, branches, and open PRs. Claim one issue by assigning yourself, addingstatus/in-progress, and leaving a UTC-stamped comment that identifies the session and intended branch. Re-read the issue immediately; if another active claim appeared, stop. Hosted agents without GitHub mutation access receive an already-claimed issue and never sweep the queue themselves. A claim is stale only after 12 hours without a heartbeat, branch, commit, or PR; document the takeover before proceeding. Release abandoned claims explicitly. See the github-flow skill for commands. - Review is agent-owned. A PR is reviewed by a peer coding agent (the
code-reviewskill or a reviewing agent), not a blocking human step; the maintainer reviews async and spot-checks. Every PR still gates onmise run check+mise run testwarning-free before merge. - Per issue: follow its Tasks + Acceptance criteria literally. Use focused checks during the inner loop; do not repeatedly run the aggregate suites. In a normal installed checkout, commit and push hooks run the required warning-free
mise run checkthenmise run test; do not run the same aggregate gates manually first. In a hookless hosted/disposable environment, runmise run agent:gateonce near PR readiness. Re-run only the affected hook or gate after a change that can invalidate its result. Tick the epic checklist via the issue's closing keywords (Fixes #Nin the PR). -
The M8 federation lab topology is the prerequisite for later M8 work: validate it with
mise run fed:upbefore claiming a policy or cross-org flow works. Never merge anything that assumes a single homeserver forever (docs/roadmap.md standing rule). -
SPEC §Nmapping. The retired rootSPEC.mdwas split into topic docs (same§numbering); issue bodies citingSPEC §Nresolve as: §1–§3, §11 → docs/architecture.md · §4 (D1–D20) → docs/design-decisions.md · §5, §6, §12 → docs/bridge.md · §7 → docs/security.md · §8 → docs/federation.md · §9 → docs/observability.md · §10 → docs/licensing.md · §13 → docs/roadmap.md.PLAN.md(original research record) was retired to git history.
Layout¶
apps/matrix-a2a-bridge/— the custom Go bridge (a Matrix Application Service usingmautrix/go+ thea2a-goclient) for local kagent routes and pinned Signed AgentCard remote routes. Its image also carries the federation-only usage-receipt signer/verifier/external processor, which runs after gateway authorization and never becomes part of bridge intake. Self-contained: own Go module, Dockerfile, Helm chart, anddeploy/(its Flux unit — Namespace + HelmRelease, reconciled by thebridgeKustomization).apps/synapse-federation-policy/— standalone Python Synapse callback module, strict policy parser, tests, and namespace-neutral Kustomize Component. Federation A/B project its immutable versioned source and stable mutablepolicy.json; C intentionally does not.apps/activitypub-agent-gateway/— self-contained Go app exposing each agent as an ActivityPubServiceactor (WebFinger + actor + inbox→A2A→outbox), reusing thea2a-goclient to reach kagent through agentgateway. The second, additive federation transport (ADR 0014, docs/fediverse.md); deliberately not bundled into the mautrix bridge (AGPL-free, homeserver-portable). Own Go module, Dockerfile, Helm chart, anddeploy/unit — reconciled on the demo profile only (issue #489;local/gcpkeep it absent), wrapped by theactivitypubFlux Kustomization inclusters/demo/with a default-deny NetworkPolicy and cluster-only demo key material. Public exposure stays gated on every profile:httpRouteis disabled and the internet-facing AP border onlocal/gcpremains a separate, explicitly gated decision.check:activitypubgates the offline invariants; the runtime GoToSocial interop proof is the tracked M18 remainder.apps/matrix-group-sync/— self-contained Go reconciler from authoritative Keycloak groups to managed Matrix room membership (ADR 0009, docs/group-sync.md). Its scoped access-manager uses normal Matrix client APIs, keeps local IdP authority local to the homeserver, and defaults to audit-only mode. The Helm chart anddeploy/unit remain opt-in and outside the reconciled Flux DAG until their live acceptance path is complete.apps/knowledge-retrieval/— the independently gated Go module for permission-aware retrieval (ADR 0017, issue #333). It contains the typed RFC 8785 identity carrier, the MCPsearch_knowledgeservice with its database prefilter, and a fail-closed HTTP identity projector. Thelocalreference profile composes its scoped credentials, immutable multi-architecture image, gateway and kagent wiring, and default-deny NetworkPolicies;gcpretains the same reviewed component as an explicit opt-in.examples/agent-pack/— reference source layout for a third party to publish an Agent from its own Git repository at an immutable ref and compose it as an additional Flux Kustomization without forking core. The example is not reconciled by any cluster here and grants no policy shortcut; use Extending Fgentic Without Forking for the integration and adopter-owned acceptance gates..fgentic/knowledge-acl.json— strict repository-wide authorization metadata for the optional Git/Markdown knowledge connector. Its classification and normalized principal/group arrays apply to every selected document; it is an ingestion contract, not repository filesystem access control (grounding contract).clusters/— Flux entrypoints:base/(the shared Kustomization DAG reconcilinginfra/+apps/, parameterized by flux post-build substitution) + per-cluster overlayslocal/(k3d) andgcp/(GKE reference), each carrying itsplatform-settingsConfigMap (domain, GCP project, TLS issuer, model).demo/is the disposable small profile consumed bymise run demo:up; it uses an ephemeral local Git source and cluster-only secrets, never the production secret path.federation/is the separate provider-free lab consumed by canonicalmise run fed:up: three Synapse servers, org B's machine-client Keycloak realm, agentgateway, only docs-qa, and the deterministic model.federation-constrained/composes that unchanged profile plus the opt-in constrained-host Component forfed:up:constrained; it does not remove a proof or become the default. Neither federation entrypoint has a Matrix appservice or external provider dependency..github/workflows/—ci.ymlvalidates every PR andmainpush with the samemise rungates as the git hooks plus a clean-tree assertion, but resolves a gate receipt first and skips those gates when the tree that would actually land already passed them locally (scripts/gate-receipt.sh); it resolves the pull request's merge ref, so a branch whose base has moved matches no receipt and is gated against the real merge result, and a fork contribution can never carry a receipt at all. Its bridge integration is never receipt-gated — no local gate starts a kind cluster — and runs onmainand only for bridge, its own workflow, or rootmise.tomlchanges on PRs.work-provenance.ymlruns on pull requests and rejects missing closing issues or post-cutoff issues that do not predate the branch by 24 hours.cd.ymlbuilds, scans, attests, signs, and digest-pins bridge artifacts after bridge/CD-workflow changes onmainor anyv*tag; a published release triggers only its release SBOM.retrieval-image.ymlbuilds, scans, attests, and signs the independently pinned permission-aware retrieval image after retrieval/workflow changes onmainor amain-ref manual dispatch.docs.ymlbuilds aftermainchanges to docs, its workflow, or rootmise.toml, or on manual dispatch; publishing additionally requiresFGENTIC_PAGES_ENABLED.fuzz.ymlruns daily or manually dispatched bounded mutation;network-policy-conformance.ymlis a manually dispatched kind/Calico enforcement proof;scorecard.ymlrecords weekly or manually dispatched OpenSSF measurements;smoke.ymlruns nightly or manually: the small-profile delegation, authorization, MCP, tracing, NetworkPolicy, quota, Kubernetes-audit, and continuous Trivy Operator proofs; an opt-in dispatch also runs the two-control-plane federation acceptance, stop/reuse, and exact-teardown drill while preserving content-free evidence, and on the default branch it maintains the failure tracker. Deterministic fuzz seeds remain in the per-commit test gate, while nondeterministic mutation stays scheduled.release/— generated adopter release artifacts.release/bom.yamlis the reference-profile chart and image pin-set generated byscripts/gen-bom.sh;mise run check:bomverifies it and rejects drift. Never edit it by hand.infra/terraform/— GKE reference cluster (private nodes + Cloud NAT, Workload Identity, CNPG backups bucket;bootstrap/= one-time tfstate bucket, apply it first). Workloads stay provider-independent.infra/flux/— platform Helm layer (HelmRepositories/OCIRepositories + HelmReleases).infra/gateway/— Gateway API resources + Let's Encrypt TLS (chat./matrix./auth./id./grafana.<server_name>), with an enabled/disabled profile that addsadmin.only alongside Ketesa.infra/namespaces/— every platform Namespace + PSS labels, owned by one early dependency-free Kustomization (HelmReleases/Secrets cannot land in namespaces that don't exist; per-layer namespaces deadlock the DAG).infra/policies/— cluster-wide fail-closedValidatingAdmissionPolicyinvariants enforced at admission: approved kagent Agent references (one model gateway, reviewed pod identity, scoped MCP subset), no:latestimage tags, managed-namespace PSS-label retention, and Service exposure limits (only ClusterIP/ExternalName;gateway/traefikis the sole LoadBalancer exception). Validated offline bycheck:admission-policiesand exercised bytest:admission-policies.infra/postgres/— shared CloudNativePG cluster + core databases/roles (synapse,mas,bridge,kagent,keycloak) and the pgvector-backedknowledgedatabase with separate schema-owner/retrieval roles. The optional knowledge Component appends separate connector-state and DML-only ingestion roles, exact HBA rows, and immutable state-machine migrations; external-network Components keep one scoped role/database each. Workloads read namespace-local credential copies with the same password as the matching CNPGpostgresSecret, while the knowledge owner credential never leavespostgres.infra/knowledge/— disabled-by-default sovereign batch ingestion plus one bounded Git/Markdown reference connector over the existing Flux source: complete ACL-bearing inventories, durable desired/applied checkpoints, fail-closed preemption and artifact-rejection quarantine, content-addressed source blobs, tombstones, digest-pinned Docling parsing, DML-only idempotent writes, and a dedicated authenticated:8082embeddings listener. The shipped #340 embeddings profile (delivered via #540) supplies the opt-in in-cluster runtime and proxy-to-model NetworkPolicy edge; live readiness remains an operator acceptance gate before ingestion is unsuspended.infra/audit/— offline, fail-closed core for the opt-in regulated identity and admin-action audit streams: pinned Synapse/MAS source contracts, closed payload-free schemas, read-only grants, projectors, cursor/dedup reconciliation, and deterministic negative tests. Runtime adapters and the durable sink remain explicitly deferred; docs/audit.md and ADR 0018 own the evidence boundary and non-claims.infra/canary/— disabled-by-default synthetic Matrix delegation CronJob forlocal/gcp. It sends a content-free nonce to the configureddocs-qaAgent through the normal admission path, including the cluster's selected model and its egress/cost boundary, then exposes failure/staleness through kube-state-metrics;demoand federation profiles render no canary resources (docs/observability.md §9.11).infra/alert-delivery/— disabled-by-default, self-hosted Alertmanager-to-Matrix receiver forlocal/gcp. It posts bounded content-free alert summaries as a non-agent@alertbot, cannot invoke the bridge, and leaves Alertmanager's null posture intact when disabled (docs/observability.md §9.12).infra/moderation/— opt-in Draupnir policy-list enforcement in a restricted standalone namespace, with an optional Synapse antispam Component. It stays outside every default Flux DAG until an operator provides its Matrix identity/secret and composes the cluster Component (docs/moderation.md).infra/observability/— kube-prometheus-stack (+monitors/as a dependent layer: Pod/ServiceMonitors plus the LLM token-burn and runtime image-vulnerability drift PrometheusRules); Grafana atgrafana.<server_name>through the shared Gateway.infra/trivy-operator/— vulnerability-only Trivy Operator layer forlocal/gcp, pinned by release commit and image digests in isolatedtrivy-system; namespaced RBAC, default-deny networking, andcount/jobs.batch: 1admission back the ownership-guardedcheck:trivy-operator/test:trivy-operatorcontract.demo/federationremove the layer and namespace budget structurally.infra/matrix/— ESS Communitymatrix-stack(Synapse + MAS + Element Web + well-known delegation), pinned + values validated against the chart schema (it changes between CalVer releases). Its opt-inretention/Component gives the production-shapedlocal/gcpprofiles finite message/media policy fromplatform-settings; demo/federation stay unchanged.infra/admin/— disabled-by-default Ketesa static SPA atadmin.<server_name>, digest-pinned and locked to the local homeserver. Its profile owns a restricted namespace, quota, HTTPRoute, and egress-denied NetworkPolicy; Synapse/MAS remain the authorization boundary (docs/admin-console.md).infra/federation/— the disposable M8 lab's four Synapse-only ESS releases (three admitted participants plus one denied control), dedicated database roles, closed-federation and room-v12 policy, well-known delegation, Gateway routes, and local-CA trust. Its opt-indelegation/Component adds org B's client-credentials issuer, exact public docs-qa routes, a signed card, per-azpreservations, and the isolated single-writer usage-receipt signer/archive without exposing kagent. Itsconstrained/Component right-sizes the retained workloads and Git-managed controllers and serializes expensive first installs only whenclusters/federation-constrainedcomposes it. Bootstrap Flux controllers and k3s metrics-server are not rendered by that Component; their reversible profile lifecycle belongs toscripts/lib/demo-cluster.sh. It is not part of the normal local or production DAG.infra/keycloak/— sovereign reference IdP (KeycloakX chart + official Keycloak image), isolated in a restricted-PSS namespace with its own CNPG database/role, Gateway route atid.<server_name>, and bootstrap realm/client/demo users. MAS consumes its SOPS-backed OIDC client through ESS; an external provider can replace that fragment (docs/identity.md).infra/agentgateway/— AI-native data plane: LLM egress chokepoint + A2A route to kagent.infra/mcp-catalog/— git-native catalog of vetted MCPserver.jsonentries. Each entry binds source/license approval, an immutable image, allowed tools, and the complete surface pin to every rendered agentgateway backend and kagentRemoteMCPServer;check:mcp-governancerejects uncataloged resources.infra/models/— optional self-hosted model runtimes selected with their agentgateway provider inventory; vLLM uses a pinned CPU image, an immutable Qwen snapshot, and a default-denymodelsnamespace.infra/kagent/— kagent platform + sample Agents (a2aConfig, served as A2A on:8083).infra/production-ha/— opt-in Components for the production replica, placement, disruption-budget, and workload-resource posture.clusters/gcpcomposes the profile;local,demo, andfederationintentionally do not.mise run check:production-haproves the effective renders and pinned chart selectors. The bridge stays one ready intake consumer and is exercised separately withmise run test:availability; do not describe that graceful SIGTERM drill as hard-crash recovery or distributed exactly-once delivery.infra/bridges/— reusable restricted Helm runtime plus self-contained, opt-in, digest-pinned mautrix Slack/Telegram profiles. Each network directory composes its runtime, Matrix registration, Postgres/HBA role, Matrix-to-A2A origin, andNOLOGINteardown into canonical Flux layers; it stays disabled until its cluster component, SOPS set, and provider acceptance are explicit (docs/interop.md).infra/secrets/—*.sops.yaml.exampletemplates only; REAL encrypted secrets are per-cluster inclusters/<env>/secrets/(generated byscripts/gen-secrets.sh <server_name> <env>, committed — Flux applies from git).scripts/federation.sh+scripts/seed-federation.sh— ownership-guarded lifecycle and acceptance proof for thefgentic-fedlab. The A2A half verifies the signed public card, JWT negative cases, one authorized provider-free docs-qa reply, aggregate token growth, per-client reservation exhaustion, and one task-bound ES256/JCS receipt whose tampered copy fails while denied requests leave the archive unchanged. The Matrix half exchanges A/B messages under room-v12 and server-ACL policy while denied server C cannot join or submit a signed transaction, and A drops B's disallowed callback probe with content-free evidence.fed:policy-reloadproves a Flux policy update without Synapse replacement and restores deny.fed:upremains canonical;fed:up:constrainedexplicitly composes the same proof through the serialized, right-sized laptop overlay and a labelled k3s Go-runtime budget.scripts/lib/demo-cluster.showns the bootstrap-only Flux-controller and metrics-server switches, restores their canonical values on standard capacity, refuses an in-place standard/constrained node switch, and atomically records exact Docker identities before destructive teardown sodemo:down/fed:downcan resume safely after interruption. Pending receipt state is inspect-only instatusand blocksup; malformed or identity-conflicting state fails closed.fed:stopreleases CPU/RAM while preserving the exact cluster/image volume for same-mode reuse, andfed:downverifies zero lab-owned cluster/image artifacts.FGENTIC_FED_TRACE=yesadds only the allowlisted, content-free resource trace under.agents/tmp.scripts/fed-check.shis a bounded, credential-free public discovery preflight for the bilateral partner onboarding runbook; it never claims reachability proves governance.scripts/fed-onboard.shstages that connectivity probe plus an opt-in A2A/AgentCard conformance check (fetch the partner's public Signed AgentCard, verify its ES256/JCS JWS with the bridge's own verifier under an out-of-band-pinned JWK, and require the JSONRPC A2A v1.0 interface plus the token-budget extension); it emitseligible_for_registry_reviewevidence only when both stages pass and never grants trust, since registry admission stays an explicit reviewed step.scripts/dev.sh— fast bridge lifecycle over the ownedfgentic-democluster: create/reuse, bridge-only image reload, status, state-preserving stop, and ownership-guarded teardown. It always writes a temporary kubeconfig and never reads or switches the user's default context.demo:upremains the full reconcile + seed + acceptance path.docs/adr/— Architecture Decision Records.AGENTS.md— root discovery symlink to this canonical file for Codex and other Agent Skills-compatible tools..agents/skills/remains the single shared skill source;.claude/skillsis its tracked Claude compatibility symlink. Read the matching skill before working an area:matrix-agents(operator runbooks: bootstrap, add-agent, add-bridge, verify),github-flow(backlog, issue/epic authoring, PRs, CI/CD),flux-gitops(infra changes, reconciliation debugging, version pins),bridge-dev(Go bridge inner loop),local-cluster(k3d lifecycle + troubleshooting),sops-secrets(generate/edit/rotate secrets),terraform-gke(cloud reference, spend-gated),observability(metrics, cost alert, monitors),docs-spec(topic specs §N, ADRs, README/AGENTS sync)..claude/settings.jsonand.codex/environments/environment.toml— project-native checkout setup for Claude Code and the Codex App; both delegate dependency preparation tomise run agent:setup. Hosted environments use the same task after installingmise.mise.toml— root task vocabulary + pinned toolchain.docs/(OKF-frontmattered topic specs +index.mdlistings — mapping below, conventions in thedocs-specskill) /README.md(humans).- Community meta:
CHARTER.md(TSC authority, voting, conflicts, licensing, and LF activation),CONTRIBUTING.md(workflow, labels, DCO),CODEOWNERS,GOVERNANCE.md,MAINTAINERS.md,RELEASE.md,SECURITY.md(private vulnerability reporting — never file security bugs as public issues), rootSUPPORT.md+ canonical.github/SUPPORT.md,ADOPTERS.md,.github/ISSUE_TEMPLATE/+ PR template.
matrix-a2a-bridge packages¶
The behavioral detail behind the one-line map in apps/matrix-a2a-bridge/AGENTS.md. It lives here rather than in that nested instruction file because the root + bridge instruction chain is bounded by Codex's 28 KiB budget; see hosted-agents.md. The binding rules stay in the instruction file — this section is orientation, not authority.
cmd/bridge/main.go— entry point: config load, slog, state layer (Postgres or memory), appservice + event-processor wiring, intake-first bounded shutdown. Also-generate-registration.cmd/usage-receipt/+internal/usagereceipt/— federation-only ES256/JCS seller receipt tool and Envoy external processor. It runs only after agentgateway JWT authorization and quota reservation, attaches the signed receipt to terminal A2A Task metadata, rejects direct Messages that cannot prove a terminal state, replays one task-unique archive assertion, and never receives a model credential or prompt-derived signing decision.internal/config/— typed, env-parsed config (caarlos0/env);Config,Load(fail-fast validation, incl. timeouts/rates).internal/matrixapp/— builds the mautrixAppService(CreateFull, optional SQL StateStore), loads/generates the registration (rate_limited: false), and wraps the transaction route with exact-body, bounded, authenticated pre-ACK durable intake.internal/a2aclient/— thin wrapper over thea2a-goSDK: resolve local AgentCards for profile refresh; bind local v1 JSON-RPC transport to the operator-configured base plus exact agent route; verify and periodically revalidate remote A2A v1.0 Signed AgentCards against pinned ES256 identities; cache delegation clients; apply per-remote timeouts and token-budget metadata; send with a caller-persisted message ID; classify any send whose HTTP transport may have started but produced no acknowledgement as ambiguous; resume an acknowledged task throughGetTask; map theTask | Messagesum type to aResult(text, contextId, taskId, terminal); authenticate local agentgateway calls as the bridge workload; and stamp the Matrix sender plus W3Ctraceparentvia a context-aware RoundTripper. Never send the localA2A_API_KEYto a remote URL.internal/evaluation/+cmd/eval/— fixed, typed A2A quality suite for the three sample agents: deterministic rubrics, direct agentgateway Prometheus deltas, optional operator pricing, and mergeable JSON/Markdown reports under.agents/tmp/. An opt-in sovereign LLM-as-judge lane (--judge, #355) scores theoptional_llm_judgescenarios for groundedness/task-success via a self-hosted judge Agent over A2A — guarded toresidency: self-hostedmodels, parsed fail-closed at the boundary, and recorded payload-free.cmd/eval-golden/adds the zero-egress checked-in demo-stub gate; this is deterministic source-contract evidence, not live-model quality.internal/agentschema/+cmd/validate-agents/— strictagents.schema.jsonvalidation reused byagent:newand the effective agent-zoo render check before a generated mapping reaches GitOps.internal/mcppin/+cmd/pin-mcp/— versioned, typed MCP surface collection through the official SDK; complete-object JCS hashes; deterministic drift reports; and atomic pin updates. The repository wrapper isscripts/pin-mcp.sh.internal/bridge/— the orchestration: resolve@mentiontargets (typedm.mentions+ body fallback; own-homeserver check + exact room/current ghost-membership/per-agent sender policy), atomically plan durable per-target jobs and inbound controls before HTTP 200, claim them through fenced database leases under a global concurrency cap and cross-restart per-room FIFO, revalidate/rate-limit, call A2A, poll known or input-paused tasks, persist reply/control outboxes, reply/edit as ghost with deterministic Matrix transaction IDs, reconcile optional pin state, and emit content-free terminal records through thelog_stream=auditlogger.agents.goloads and atomically replaces the routing/allowlist map: each agent has exactly one localnamespace/nametarget or one remoteurl/timeout/tokenBudget/cardIdentitytarget, plus exactallowedRooms,bridgedOrigins,allowedServers, andallowedSendersglobs. Local aliases inallowedRoomsare bootstrap locators resolved on every admission; direct room IDs are preferred. Bridge origins are anchored full-MXID namespaces and always require an explicit per-agent sender match.profiles.goderives Matrix ghost profiles from AgentCards while retaining the last-known display data on refresh failures;directory.goserves the local, policy-filtered!agentscommand without A2A or LLM work;welcome.goposts the bot's one sender-filtered onboarding notice under the shared notice budget.durable_controls.goowns crash-safe cancel/continuation/question/progress/pin behavior;reaction.go,inflight.go, andprogress.goretain the development-only in-memory path.internal/state/— the versionedStore/Ledgerinterfaces: Postgres (shared dbutil pool,bridge_appservice_transactions,bridge_delegations, boundedbridge_delegation_controls,bridge_contexts, permanentbridge_room_welcomes, and legacybridge_processed_events) plus a behavior-matching in-memory development implementation.internal/telemetry/— env-gated OTLP/HTTP exporter setup; unsetOTEL_EXPORTER_OTLP_ENDPOINTkeeps standalone development tracing-free.chart/— Helm chart (Deployment, Service, ConfigMap for the agent map, NetworkPolicy, ServiceAccount;database.secretNamefeedsDATABASE_URL).deploy/— the Flux unit (Namespace + HelmRelease) reconciled by thebridgeKustomization; CD pins the image digest here.registration.example.yaml/agents.example.yaml— templates for the two config files;agents.schema.jsonis the Stable v1 routing/allowlist contract validated against every in-repo fixture.
Platform protocols (how agents participate at runtime)¶
- Identity. Every platform agent is a Matrix ghost
@agent-<name>:<server>owned by the bridge (exclusive appservice namespace@agent-.*). The explicitagents.yamlallowlist maps each ghost to exactly one local kagentnamespace/nameor one remoteurlwith timeout, partner-enforced token-budget metadata, and a pinned ES256 Signed AgentCard identity; unmapped or untrusted targets are rejected. - Delegation. An
@mention(typedm.mentions, plaintext fallback) becomes an A2ASendMessageto eitherhttp://kagent-controller.kagent:8083/api/a2a/<namespace>/<name>through local agentgateway or the exact verified remote URL. Non-streaming by design; long tasks useGetTaskpolling + Matrixm.replaceedits. Remote timeouts are additional ceilings over global request/task deadlines, and remote calls never receive the localA2A_API_KEY(docs/bridge.md §6). - Threading. The bridge threads ordinary conversations with a per-
(room, ghost)A2AcontextId; kagent mapscontextId1:1 to a persistent session. Do not reuse a contextId across different agents. D18's retrieval-capable Agent is the security exception: every initial delegation omits callercontextId,taskId, andreferenceTaskIdsso the server creates a fresh session that cannot retain or reference previously grounded content. - Attribution. The Matrix sender is forwarded as the asserted A2A user identity (
X-User-Id). Configured external-appservice MXID namespaces add bounded origin kind/network audit fields but never replace the full MXID; the exact evidence chain and downstream-authentication limits are in docs/audit.md. D18's permission-aware retrieval path uses a separate gateway-projected identity-and-room-audience contract; it does not makeX-User-Idauthentication globally. - Trust boundaries. Room content is untrusted input to agents (prompt injection is the #1 threat — docs/security.md): sender allowlists gate who may invoke which agent; agents from other homeservers are never resolved as local targets; explicitly configured remote agents require a currently verified A2A v1.0 Signed AgentCard; agent replies are
m.noticeand other automation must not act on them. Local LLM egress goes only through agentgateway — no agent holds a model credential.
Principles¶
- Open standards only. Every layer is an open protocol or OSS component (Matrix, A2A, MCP, Kubernetes, Gateway API, OIDC). No proprietary SaaS in the critical path; every component is swappable. This is the whole point of the showcase.
- Federation-ready decisions. The endgame is cross-organization collaboration (docs/federation.md): federated rooms use room v12+, closed-federation allowlists, and server ACLs; nothing merged may assume "single homeserver forever" (e.g., never match users by localpart without checking the homeserver).
- Independent apps, bridging infra. Each app under
apps/is self-contained and deployable on its own.infra/exposes shared components (Postgres, gateway, agentgateway) without coupling apps together. The homeserver, MAS, Matrix-to-A2A bridge, kagent, optional Keycloak IdP, and every enabled external bridge each get their own database + scoped role in the shared CloudNativePG cluster (Synapse's DB usesCcollation). The composed knowledge store follows the same boundary with a dedicated database, a non-workload schema-owner role, and a read-only retrieval role. - Flux GitOps delivery. Production CD is Flux v2, pull-based, for both
infra/andapps/. Neverkubectl apply/helm upgradeprod by hand — commit to git and let Flux reconcile. GitHub Actions is CI-only (build/test/scan/sign the bridge image, commit the digest).HelmRelease.dependsOnreferences HelmReleases only — wrap apps in Flux Kustomizations to depend on Kustomizations. - Helm-first reusable manifests. Package Kubernetes manifests as parameterised Helm (the bridge's
chart/) or FluxHelmReleases with inlinespec.values; per-directorykustomization.yamllists resources;base+ overlays where environments differ. Kustomize is Flux's thin resource-lister, never a Helm replacement. - Gateway API, not Ingress. Web/UI traffic (Element, Synapse client API, MAS, Keycloak) routes through the Gateway API (Traefik); cert-manager terminates Let's Encrypt TLS per host. Local A2A/LLM/MCP traffic egresses through agentgateway on
ClusterIP— the local model-credential chokepoint. The opt-in federation profile is the sole public A2A exception: one exact docs-qa route reaches a dedicated agentgateway listener under strict org-B JWT and quota policy; kagent itself remains ClusterIP-only behind NetworkPolicy. An explicitly configured remote A2A URL is a separate outbound trust boundary, receives no local gateway credential, and requires a pinned Signed AgentCard plus independently reviewed transport authorization. kagent's A2A endpoint is unauthenticated by default, so the NetworkPolicies on both the agentgateway and kagent namespaces are load-bearing security controls. Ordinary A2A may retain its direct-route debugging toggle, but a D18 retrieval-capable Agent must use the gateway projection path and fail closed if it is unavailable. - A2A for delegation, non-streaming. Agents are invoked over A2A
SendMessage(request/response;GetTaskpolling for long tasks). Streaming is deliberately unused. Reuse the officiala2a-goSDK (the same one kagent uses) — do not hand-roll JSON-RPC. - Homeserver strategy is decided — don't relitigate it per-PR. Reference profile: ESS Community (Synapse + MAS + Element; best appservice API, MAS/Element X, Postgres). Pure-permissive fallback profile: Tuwunel/continuwuity (Apache-2.0, RocksDB) for AGPL-averse deployments. The bridge only uses stable-spec appservice endpoints, so it stays homeserver-portable; switching is governed by the explicit triggers in docs/licensing.md §10.3, not preference.
- Encryption is selected per room. ADR 0026 supersedes ADR 0008's blanket prohibition. The current crypto-free bridge refuses encrypted events until the per-ghost identity and crypto stages land, so ADR 0015's classification-bounded plaintext contract continues to govern current v1 partner rooms.
- Cost and resource efficiency are correctness constraints. Every mention is an LLM invocation: keep the bridge's per-sender/per-room rate limits and agentgateway's token metering intact; cross-org
maxTokensis a per-azpadmission reservation, while actual model metrics remain aggregate. During development, choose the smallest sufficient model and resource profile, reuse existing local infrastructure, and avoid unnecessary CPU, RAM, storage, network, or paid-service consumption. Scale only from measured need. Never merge a change that lets automation invoke agents unboundedly or presents reservations as consumption (docs/design-decisions.md D7/D8). - SOPS + age secrets. Never commit plaintext secrets — only
*.sops.yaml, decrypted in-cluster by Flux. A gitleaks pre-commit hook enforces it. Secrets are per-cluster (clusters/<env>/secrets/). - Version pins that bind each other. Gateway API CRDs are pinned to v1.5.1 experimental, the supported overlap between agentgateway v1.3.1 (officially Gateway API 1.3–1.5; still watches TCPRoute v1alpha2) and Traefik chart 41.0.2 (proxy v3.7.6; supports Gateway API v1.5.1). Gateway API v1.6 deprecates rather than removes TCPRoute v1alpha2, but remains outside agentgateway 1.3.x's supported range; advance it with agentgateway v1.4 after that controller's TCPRoute v1 transition. Bump these pins together, deliberately. kagent charts come via an OCI-type HelmRepository (never chartRef→OCIRepository: Flux appends digest build-metadata that kagent stamps into a label).
- GHCR images. The bridge image is multi-arch distroless, published to
ghcr.io/fmind-ai/matrix-a2a-bridge, deployed by immutable digest (neverlatest). - License hygiene. Project code is Apache-2.0 (docs/licensing.md). Never add an AGPL dependency to the bridge (mautrix/go is MPL-2.0 — keep the
NOTICEfiles current); never mirror AGPL images (Synapse, Grafana) into project registries — reference upstream. Contributions use DCO sign-off, no CLA.
Conventions¶
- Server name / domain:
fgentic.fmind.ai(GKE reference) /fgentic.localhost(local k3d, loopback 80/443, local-CA TLS —scripts/local-ca.sh). Normal public hosts are the apex pluschat.,matrix.,auth.,id., andgrafana.;admin.exists only when the Ketesa profile is enabled, and the federation profile alone addsa2a.<org-A>for its exact exported route. Bot user@a2a-bridge:<server_name>; agent ghosts@agent-<name>:<server_name>(appservice exclusive namespace@agent-.*). ESS release name is fixed toess(Servicesess-synapse:8008,ess-matrix-authentication-service:8080,ess-well-known:8010; MSC3861 compat paths route to MAS in the HTTPRoutes). The tracked local model default remains Vertex AIgoogle/gemini-2.5-flashthrough agentgateway with a cluster-only ADC secret. The GKE reference selects self-hosted vLLMQwen/Qwen2.5-7B-Instructas its documented 24 GiB GPU policy default; issue #10's funded live acceptance remains open. - kagent A2A endpoint:
http://kagent-controller.kagent:8083/api/a2a/<namespace>/<name>(AgentCard at…/.well-known/agent-card.json). A remote mapping uses an exact HTTP(S) A2A URL whose card at the same suffix verifies under its pinned P-256 public JWK; production config enables none by default. agentgateway LLM:http://agentgateway-proxy.agentgateway-system:8080. - Go (default) and Python are the core languages; Go for the bridge. Type-safe, small composable units, errors wrapped with
%w, no ignorederr, no tech debt. miseis the single source of truth for tasks (install,format,check,test,build,watch); lefthook + CI reuse it. Conventional Commits; no attribution. Most tasks live in the rootmise.toml(run from the repo root), but each app also has its ownapps/*/mise.toml— a few tasks (e.g. the ActivityPub gateway'sinterop) exist only there and must be run from that app directory (mise --cd apps/<app> run <task>), so a baremise run <task>at the root can report "task not found".- A fresh agent clone uses
mise run agent:setup. It installs pinned tools and locked dependencies without Git hooks, dependency-manifest rewrites, cluster creation, credentials, or provider access. Claude runs it on session startup; the Codex App runs it when its checked-in local environment is selected. Do not replace it with mutation-capablemise run installin disposable agent setup. Follow it withmise truston the root and everyapps/*/mise.toml: an untrusted app config aborts the aggregate gates with a trust error that looks like a test failure. - Use the smallest sufficient development boundary. Focused unit tests first;
test:integrationfor the isolated Matrix↔A2A kind boundary;dev:uponce plusdev:reload/watchfor interactive bridge work on the lightweight owned demo;demo:upafter manifest/profile changes and for final seeded end-to-end proof. Use canonicalfed:upfor federation acceptance and opt intofed:up:constrainedonly on a constrained host; both prove the same topology and behavior. Use the fullcluster:up+ Flux bootstrap only for SSO, observability/tracing, Trivy, SOPS, or other controls intentionally omitted from demo.dev:stopandfed:stoppreserve their owned state while releasing active CPU/RAM. Repo development never depends on a global cluster or the user's default Kubernetes context. - One local session owns the repository and the runtime. Local development is a single sequential agent in one checkout. Git worktrees and extra clones isolate source and Git state but not Docker, k3d, kind, fixed image tags, loopback ports, or cluster names, so a second local path buys no isolation where it matters and multiplies the load that actually constrains the host. The lane model that once split work across
agent/blue,agent/green, andagent/redcheckouts was retired on 2026-07-28: it froze a 14 GiB no-swap workstation and produced review debt that landed back on the same machine. Parallelism belongs in cloud/hosted sessions, which cost the workstation nothing — see hosted-agents.md. - The installed commit/push hooks and hookless fallback
mise run agent:gateshare one portable host-local mutex, which now guards against a second concurrent gate in the same checkout rather than against a sibling worktree. Hooks invoke its scopedcheckandtestmodes; the fallback runs both in order. Do not launch either aggregate gate manually while a hook may already be running it. CI runs them sequentially on isolated runners. - The aggregate gates are Docker-backed and collapse under a running cluster.
test:kagent-trace-privacy,test:mautrix-bridges, andtest:integrationstart containers. Measured on the reference workstation on 2026-07-28: with two k3d clusters up,checktook 208 s andtestran 984 s beforetest:kagent-trace-privacyfailed on a dead container; with every cluster stopped, the same commit rancheckin 108 s andtestin 77 s, both green. Stop clusters before running the gates, and when a gate fails, check host load before assuming the change caused it. - After any local cluster recreate (
mise run cluster:up+ Flux bootstrap), runmise run cluster:overridesto re-apply the gitignoredplatform-settings-overridesConfigMap (holds the realgcp_projectand any value kept out of git). It is untracked by design, so a recreate loses it and Vertex falls back to theyour-gcp-projectplaceholder until re-applied (docs/forking.md §5). Idempotent; a no-op when the file is absent. mainis protected by a repo ruleset (protect-main, active). It blocks force-pushes, branch deletion, and non-linear history, somain's history cannot be rewritten or lost by accident — for a deliberate rewrite (e.g. the v0.1.0 squash) temporarily disable the ruleset, then re-enable it. It intentionally omits repo-level pull-request/status-check rules: CD's digest-pin step pushes a fast-forward commit straight tomainasgithub-actions[bot], which a PR/check rule would block, and a repo ruleset cannot grant the built-in GitHub Actions app a bypass (GitHub requires an org-scoped ruleset,admin:org, to bypass Actions app id15368). Requiring PR review at repo level also adds no real gate here because coding agents push with the maintainer (admin) identity and would bypass it — agents still work issue→PR by convention (github-flow). Enforced review for outside contributors is the documented next step (org-scoped ruleset).