External agents and outbound HTTP
External agents and outbound HTTP for Q integrations.
Q1.2 introduces tenant-owned A2A execution registrations and a common HTTP destination policy. This is a breaking security migration. The implementation and tests are local; production activation requires the steps below.
A2A API
| Operation | Required scope | Behaviour |
|---|---|---|
GET /v1/a2a/agents | a2a:read | Internal Q cards plus the authenticated tenant's external cards; optional capability filter |
POST /v1/a2a/agents | a2a:write | Atomic create/replace within the authenticated tenant; returns {"registered":"agent-id"} |
DELETE /v1/a2a/agents/{agent_id} | a2a:write | Removes an owned registration; returns 204 |
POST /v1/a2a/tasks | a2a:delegate | Known internal Q agent or an owned external registration; unknown/foreign IDs return 404 before delegation |
GET /v1/a2a/tasks/{task_id} | a2a:read | Existing tenant-scoped local Q task lookup |
GET /v1/a2a/.well-known/agent.json | Public | Q's static capabilities; no tenant registration or secret |
a2a:* and the existing administrative * cover these operations. ai:*, first_party and disabling Q_ENFORCE_AI_SCOPES do not bypass them. Developer API-key issuance accepts the new scopes. Tenant identity comes from authentication, never from a registration body. JWT/session callers also need these capabilities in their authenticated scope set.
Example registration body (synthetic values):
{
"agentId": "acme.research",
"name": "Acme research",
"endpoint": "https://agents.example.com/research",
"skills": [{"name": "research", "tags": ["research"]}],
"bearer_token": "SYNTHETIC-EXAMPLE-REPLACE-SECURELY"
}The optional bearer_token is write-only. Q encrypts it with A2A_CREDENTIAL_KEY using Fernet and binds the encrypted payload to tenant, agent ID and canonical origin. Missing/invalid encryption configuration fails closed. Replacing a registration without a token removes its previous credential; Q never silently carries it to a new endpoint. Credential-bearing endpoints require HTTPS. Discovery contains no credential field. Public validation and registration errors do not echo submitted secrets.
Agent IDs have 1–128 ASCII alphanumeric/underscore/dot/hyphen characters and start with an alphanumeric character. The q. prefix is reserved, case-insensitively. Cards are limited to 100,000 encoded bytes. Endpoints are base HTTP(S) URLs without userinfo, query credentials or fragments. Delegation appends /tasks; task metadata cannot replace the target or authentication headers. The existing legacy task envelope remains; this work does not establish complete A2A protocol conformance or remote task polling. Discovery currently returns at most 200 external cards in agent-ID order; general pagination remains Q2.2.
q_service.external_agents uses (tenant_id, agent_id) as its primary key. Every operation applies an explicit tenant predicate and transaction-local tenant context. Forced RLS remains active independently of the older Q_ENABLE_RLS switch. Tenant A and B can use the same agent ID without overwriting or discovering each other's registration.
Destination and transport policy
The shared factory is app.services.outbound.outbound_client(purpose, ...). Adapters select a fixed purpose; public request data cannot supply a transport, proxy, TLS override or private-network grant.
- HTTP(S) only; reject userinfo, fragments, control characters and ambiguous URL forms. Credentials belong in adapter-managed headers, not URLs.
- Resolve every destination and inspect all returned addresses. DNS failure, empty/mixed unsafe answers and more than 32 addresses fail closed. Connect to an approved literal IP; TLS certificate verification and SNI retain the original hostname. Recheck on every request and redirect, including pooled connections.
- Global addresses are the default. Private, loopback and non-global destinations need the server exception below. Metadata endpoints, link-local, multicast, unspecified/reserved addresses and IPv6 transition routes stay blocked. A mapped IPv6 address receives the underlying IPv4 policy.
- Credential/custom-header requests require HTTPS and bind that client to one origin. Continuation/download URLs cannot receive that client's credentials at a different origin. Cross-origin redirects are permitted only for credential-free GET/HEAD fetches; other purposes require same-origin redirects. Adapters retain their existing redirect opt-in.
- Environment proxies and environment TLS overrides are ignored. Connector
Q_HTTP_DISABLE_SSL_VERIFYno longer weakens these clients. Injecting a non-policy client intoA2AClientis rejected. - HTTPX request URL logs are redacted. Transport/status errors contain stable codes, not URLs. Webhook metrics use the bounded label
callback; retry destinations are retained privately in the outbox, including their original query, and are revalidated on delivery.
| Limit | Value |
|---|---|
| Request and response body | 8 MiB each, including streamed/chunked bodies |
| Whole HTTP request, body and redirect chain | 60 seconds maximum |
| DNS lookup | 5 seconds maximum |
| Connect/read/write/pool operation | 10 seconds maximum; smaller adapter limits retained |
| Redirects | 5 maximum; smaller adapter limits retained |
| Connections per client | 20 total, 10 retained |
| Encoding | Requests ask for identity; compressed responses are rejected |
Streams close on size failure, timeout and cancellation. These are per-request/client limits, not total tenant quotas. Existing adapter retries may start another bounded request; connector server-supplied retry delays are capped at 120 seconds. A long-lived MCP/context SSE response exceeding these limits must reconnect or fail; there is no unlimited streaming exception.
Deliberate private connectivity
An operator can set OUTBOUND_PRIVATE_RULES as a JSON array in server configuration. Each entry requires an exact canonical origin (scheme, host and non-default port), one purpose and explicit CIDRs:
[
{
"purpose": "mcp",
"origin": "https://tools.corp.example:8443",
"cidrs": ["10.20.0.12/32"]
}
]Valid purposes are a2a, fetch, mcp, hooks, connectors, callbacks and context. There are no host wildcards. A different purpose, host, scheme, port or resolved address does not inherit permission. Even an explicit metadata CIDR cannot permit metadata access. Loopback exceptions are supported, including IPv6 ::1/128; HTTPS and certificate verification still apply to credential-bearing requests. Use a certificate trusted by Q's verified HTTP transport. Client requests cannot add trust roots or disable verification.
These are deployment-wide network permissions for the chosen purpose, not tenant authorisation or endpoint authentication. Grant only reviewed destinations whose application authenticates authorised tenants. MCP registration additionally retains MCP_REQUIRE_HTTPS and MCP_BLOCKED_HOSTS; an outbound exception does not override an explicit MCP host block. Configure both deliberately when supporting local MCP development.
Adapter coverage and compatibility
| Purpose | Covered paths |
|---|---|
| A2A | Tenant registration endpoint validation, delegation, direct A2A client and remote card discovery (including advertised endpoint validation) |
| Fetch | Answer Engine fetch_url, including cache hits/revalidation and direct fetch/redirects |
| MCP | Client manager, third-party context MCP connector, streamable HTTP, server cards, ClickUp, Google Workspace and ClickUp OAuth HTTP |
| Hooks | Hook executor and handler HTTP POSTs |
| Connectors | Shared connector HTTP factory used by implemented GitHub, Google Drive and Jira connectors |
| Context | Pull, streaming, federated RAG and retrieval callback validation |
| Callbacks | Webhook delivery, HTTP notification channels and scheduled callback activities |
MCP client-manager sessions include tenant/global identity, server ID, URL and authentication configuration in their reuse key. Tools queries have an explicit tenant/global predicate even when legacy RLS is disabled. Changed credentials cannot reuse an old authenticated session; failed handshakes close clients.
Hook handlers no longer fall back to aiohttp/urllib. Fetch no longer sends URLs to a remote reader that can bypass Q's destination checks. It performs bounded direct HTTP and basic text extraction; JavaScript-rendered pages, remote-reader formatting and large/compressed downloads may now be unavailable. Provider-specific document download URLs on another origin need a separately reviewed credential-free download path; Q will not forward connector credentials automatically. The remaining connector scaffolds are still scaffolds; this policy does not implement them.
This transport is not an OS-wide egress firewall. Fixed provider/gateway SDK calls, browser engines, arbitrary worker code and other service-control clients are outside this adapter inventory. Q1.1's workspace execution boundary remains in force. Production network controls must independently restrict egress. The separate catalogue-only agent marketplace is not the A2A execution registry; its persistence/permission work remains separate. Retrieval/user ACL propagation remains Q1.3 and full A2A conformance remains Q6.2.
Deployment and rollback
- Apply Alembic revision
20261007_120000_external_agentsafter the workspace-resource revision. Grant the application DB role schema usage and SELECT/INSERT/UPDATE/DELETE on the new table. Use a role without superuser orBYPASSRLS; do not run the service as the migration administrator. - Provision one secret
A2A_CREDENTIAL_KEYconsistently across API/worker instances. Generate a Fernet key through the approved secret-management process. Do not put it in examples, source control or logs. Key rotation currently requires re-registering credentials; losing the key does not expose plaintext and makes those credentials unusable. - Re-register external agents for their authenticated owners. The old process-global dictionary had no trustworthy ownership or durability, so it is not auto-imported into all tenants. Issue the required A2A scopes and update clients for asynchronous tenant-aware service calls and DELETE support.
- Review private integrations, HTTPS certificates, headers, redirect/download hosts, response encoding and size/stream limits. Configure only the exact server grants required. Exercise the real integrations in a controlled staging environment.
- Run the required release checks and full hosted CI before production promotion. SDK regeneration, UI/client qualification and existing Q1.1 migration obligations remain release requirements.
Downgrading the migration drops the registry, including encrypted credentials. Export an authorised encrypted backup before a deliberate rollback; do not restore the previous globally shared execution registry as a compatibility bypass. This task has not migrated or deployed a production environment.