Repository navigation
Agent Mesh v0.2.0 enables Ingress: a router can now expose a2a, mcp and inference services to HTTP clients outside the mesh, under the same identity and policy checks that apply inside it. This release also adds sdk/meshnet, a Go SDK that embeds a mesh member in a Go program, publishes the agentmesh MCP server to the MCP registry, and includes fixes to the node, the control plane, the Helm chart and the documentation.
Upgrade notes
- v0.1.1 nodes, routers and control planes interoperate with v0.2.0. All API changes are additive.
- The control plane database schema is now version 21. The migration is automatic and keeps existing data. Do not downgrade a migrated database to v0.1.1: the old binary does not know the
ingressresource type and a policy save from it removes everyallowed_ingressgrant. Take a backup before upgrading. agentmesh-onebehind a proxy, including Cloud Run, requires--external-url. The URL is no longer taken from request headers (#755).- The Helm bootstrap Job seeds the mesh policy only when the control plane has none. A
helm upgradeno longer replaces a policy edited in the console or withPOST /policies(#763). - Ingress is off until configured. Roles have no
allowed_ingressby default, services have noingress:block, andagentmesh-routerhas no ingress listener unless--ingress-addris set.agentmesh-oneserves ingress on its existing port.
Ingress (#838, #839, #851)
Ingress exposes a service at a stable path on a router:
https://<router>/ingress/<peer_id>/<a2a|mcp|inference>/<name>[/<subpath>]
Two settings must both be present for a service to be reachable:
- The mesh policy grants it.
PolicyRole.allowed_ingresslists service patterns and the permitted auth modes for the serving node's role. An empty list denies ingress for that role. A grant withoutallowed_auth_modespermitsedgeonly. - The node exposes it. The service in
agentmesh-node.yamlhas aningress:block, withauth_mode(edgeorpassthrough) and an optionalallowlist inPolicyBinding.memberssyntax that narrows the callers.
The router checks the grant when the node announces the service and on every request. The node checks it again against its own credential before it hands the request to the backend.
edge mode (default). The router accepts a delegated Session Biscuit or a task Biscuit from POST /oauth/token. An unauthenticated request receives a 401 with WWW-Authenticate and RFC 9728 metadata at /.well-known/oauth-protected-resource. Before the router dials the provider it verifies the signature, expiry, revocation, bans, Block 0 policy, each tar_block and the service's allow list. Standing member and router transport Biscuits are refused as bearer credentials. The node verifies the caller's Biscuit again, removes caller-supplied X-Mesh-* headers and sets X-Mesh-Ingress, X-Mesh-Principal, X-Mesh-Role and X-Mesh-Task from the verified credential.
passthrough mode. The backend authenticates its own callers. The role must list INGRESS_AUTH_MODE_PASSTHROUGH explicitly. The control plane rejects passthrough on a service that also has target_auth_path or an inline credential.
A2A agent cards. GET /.well-known/agent-card.json on an ingress URL returns the backend's card with supportedInterfaces set to the public URL, signatures removed and, in edge mode, the mesh OAuth 2.1 security schemes.
Transport. Router and node communicate over /mesh/ingress/1.0.0. The node accepts this protocol only from a peer authenticated as mesh:role:router; other streams are reset before any data is read and counted in agentmesh_node_streams_refused_total{scope="not_router"}. Request bodies are limited to 32 MiB at the router and at the node. Authenticated callers are rate limited per Biscuit revocation ID. The router's revocation set is pruned by expiry instead of by size and is exported as agentmesh_router_revoked_tokens.
Router flags (all off by default):
| Flag | Purpose |
|---|---|
--ingress-addr |
Address of the ingress listener |
--ingress-url |
Public base URL, sent in the lease and published on /info as RouterInfo.ingress_url |
--ingress-tls-cert-path, --ingress-tls-key-path |
TLS termination on the router |
--public-control-plane |
Public control plane URL for RFC 9728 and A2A OAuth metadata |
--allow-insecure-ingress-http |
Plain HTTP on a non-loopback address, for use behind a TLS-terminating gateway only |
Deployment. The Helm chart and the .github/k8s manifests route /ingress/* on the Gateway HTTPRoute to the routers. Metrics: agentmesh_router_ingress_requests_total{mode,outcome} and agentmesh_node_external_ingress_requests_total{mode,outcome}. Documentation: External ingress.
Go SDK (#843)
github.com/google/agentmesh/sdk/meshnet embeds a mesh member in a Go program, in the style of tsnet.
Server.Upenrolls and attaches to routers.ListenA2Aserves an A2A agent to the mesh and, when policy allows, on router ingress.HTTPClientandDiscovercall providers over authenticated streams.WhoIsreturns the verified caller of a request.AttenuateTokenappends atar_block.MCPServerandInferenceHandlerexpose the node's tools and the/v1endpoint in process.
The SDK uses the same state directory format as agentmesh-node, sdk/js and sdk/python, and the same policy enforcement code as agentmesh-node. Documentation: Embedded Go SDK.
Control plane
- Operator-managed signing keys: when rotating with
--signing-key-path, keep the previous key in the file until every credential it signed has expired (24 hours by default). The flag help and the reference now state this (e8c06ff). --key-rotation-interval 0selects the default of 24 hours.--signing-key-pathis what disables rotation (b54604e).- An enrollment refused because the identity has no binding for the requested role is logged with the peer, the role, the issuer, the subject and the roles the identity did resolve to (#756).
- Border Crossing log lines at
/sts/tokenincludeclient_peer_idon allow and deny.act.subin the border JWT is the egress node (#750, 95300a5). --dht-replicationhelp describes replication accurately: the router a member announces to keeps the record and copies it to this many closest routers. Members do not hold sessions with the replicas (5d321c4).
Node
- Concurrent first calls to the same provider share one
/mesh/authhandshake. Previously each call opened its own stream, the provider's limiter rejected the excess and the caller received a 502. Shared waits are counted asagentmesh_node_provider_reach_total{result="shared"}; a rate-limited handshake is reported asresult="rate_limited"(#849). - A directory lookup that fails is retried on the next request. Previously the node cached its fallback (circuits through its own routers) for two minutes and did not ask the directory again in that time (#852).
- A router that disconnects the node is redialed. A race between recording the session and clearing the redial mark could leave the router attached without a session; with a single router the node then exited (#725, #793).
- A policy denial under the verifying key is final. The node no longer continues to other trusted keys after a denial. Initial key creation is serialized (#796).
- An authorizer run limit is reported as such, not as a denial. The time limit returns 504 and
CallMCPToolretries; the facts and iterations limits return 500 without retry. The authorizer error is preserved in the message (#842, 54ec3d3, #813). /oauth/revokeaccepts a Biscuit that verifies under a trusted key without a bearer token. Revocation records the last block's ID, so the parent token remains valid (45765f3).- The usage text for
agentmesh-node join <control-plane-url>marks the argument as required (2c44e1e).
Router
- Members hold no DHT or gossipsub streams on routers; only peer routers use
/mesh/kad/1.0.0. The metrics documentation and the help ofagentmesh_router_streams_refused_totalare corrected (46757a6).
agentmesh-one
- The advertised and signed public URL comes from
--external-urlor the local listener, never fromHost,X-Forwarded-HostorX-Forwarded-Proto. A proxied request without--external-urlis logged once and counted inagentmesh_one_proxied_requests_without_external_url_total(#755).
Helm chart
- The bootstrap Job reads
/admin/policyand seeds only when it holds no roles. A policy it cannot read fails the Job instead of being overwritten (#763). - PodDisruptionBudget for the control plane (#703).
SDKs and tools
@agentmesh-p2p/sdkincludes anagentmeshexecutable: an MCP stdio server with the node's caller tools, configured throughAGENTMESH_*variables. It is listed on the MCP registry asio.github.google/agentmeshand published by the release workflow (#837).mcp-clientpretty-prints JSON when stdout is a terminal (#835).- The console collapses the Revoked Nodes section like Expired Nodes (#834) and shows each router's ingress URL.
Documentation
The reference pages for authorization, enrollment, networking, metrics, APIs and CLI flags, and the getting-started guides were checked against the code and corrected (#760, #762, #764–#766, #782–#788). New reference pages: agentmesh-console and mcp-client. The site has search and heading anchors (#758).
New guides with runnable examples: Keycloak as identity provider (#753), agentgateway (#802) and kgateway (#812) in front of the mesh, Google APIs with a federated credential (#749), OpenShell sandboxes and Substrate actors on one mesh (#747, #754, #806), and a private DIY enterprise stack (#804).
Dependencies (#850)
go-libp2p v0.51.0, go-libp2p-kad-dht v0.43.0, go-libp2p-pubsub v0.19.0, quic-go v0.64.1, grpc v1.85.0, a2a-go v2.7.0, pgx v5.12.0, modernc.org/sqlite v1.61.1, golang.org/x/net v0.62.0, go-jose v4.1.6, envoy go-control-plane v1.40.0, prometheus/client_golang v1.25.0. Release images are built with Go 1.27.2.