GraphQL API
Purpose
The GraphQL API is the exclusive read-surface interface for TUI clients and IDE extensions. It exposes push subscriptions over WebSocket — clients never poll. The control plane pushes projections as events occur (control_plane_api_and_state_model.md §3.2).
v1 deployment note In v1 local-embedded mode, the GraphQL API is served over an in-process transport (bufconn). No port is opened. TUI clients connect through the embedded control plane transparently. Team and enterprise deployments expose the GraphQL API over WebSocket on the
syndicate-serverendpoint.
Design constraint: single mutation
The GraphQL API is read-only with exactly one exception — the submitDecision mutation, which accepts operator approval/denial responses. This mutation is the only write surface exposed to the TUI. All other state mutations occur via the REST command API (control_plane_api_and_state_model.md CP-API-007).
The GraphQL API cannot mutate policy, issue permits, or modify specialist state.
Subscriptions
The following subscriptions are available:
| Subscription | Description |
|---|---|
sessionState | Current session lifecycle state, policy version, turn count |
activeEnvelope | Current execution envelope scope, permit expiry, approval mode |
trustSummary | Per-boundary trust state snapshot, updated on every trust event |
evidenceFeed | Live stream of audit events for the current session |
pendingDecisions | checkpoint_pending events awaiting operator response |
turnOutput | Streaming model output fragments for the current turn |
approvalMode | Current approval granularity (envelope vs per-step) and policy basis |
checkpoint_pending event
The control plane is responsible for assembling the checkpoint_pending payload completely before pushing it to subscribers. The TUI must be able to render the full checkpoint detail view from this payload without additional queries.
submitDecision mutation
The only write operation in the GraphQL API:
mutation SubmitDecision(
$proposalId: ID!
$decision: DecisionType! # APPROVE | DENY
$idempotencyKey: String!
$reason: String
) {
submitDecision(
proposalId: $proposalId
decision: $decision
idempotencyKey: $idempotencyKey
reason: $reason
) {
decisionId
outcome
}
}
The control plane validates the decision against the active checkpoint, records an approval_decision event, evaluates policy, and either issues a permit or records a denial. The mutation returns the recorded decision ID and outcome. It does not return a permit — permits are issued by internal control plane logic and are not client-visible objects.
Deployment topology
Local embedded (v1)
┌─────────────────────────────────────────────────┐
│ syndicate (single process, single binary) │
│ │
│ ┌────────────┐ gRPC/bufconn ┌──────────────┐ │
│ │ CLI / TUI │ ◄────────────► │ Control │ │
│ │ │ (in-process) │ Plane │ │
│ └────────────┘ └──────┬───────┘ │
│ │ │
│ ┌────▼─────┐ │
│ │ SQLite │ │
│ │ (local) │ │
│ └──────────┘ │
└─────────────────────────────────────────────────┘
No server process, no socket path, no daemon, no port. The operator runs syndicate and everything works.
Remote endpoint (team/enterprise)
The control plane is deployed as a standalone syndicate-server binary. The GraphQL API is served over WebSocket on the server endpoint.
┌────────────────────────────────┐
Developer A │ syndicate-server │
┌──────────┐ gRPC/TLS │ │
│ syndicate│ ──────────►│ ┌──────────┐ ┌────────────┐ │
│ (client) │ │ │ Control │ │ Event Store│ │
└──────────┘ │ │ Plane │ │ (shared) │ │
│ └──────────┘ └────────────┘ │
Developer B │ │
┌──────────┐ gRPC/TLS │ │
│ syndicate│ ──────────►│ │
│ (client) │ └────────────────────────────────┘
└──────────┘
Configuration seam in ~/.syndicate/config.toml:
[control_plane]
# Empty for local embedded mode (v1 default)
endpoint = ""
# Remote address for Team/Enterprise mode
# endpoint = "grpcs://cp.acme.com:443"
# tls_cert = "/path/to/client.crt"
# tls_key = "/path/to/client.key"
# tls_ca = "/path/to/ca.crt"
No code changes or recompilation are required to switch topologies.
Event-sourced state model
All state exposed via GraphQL subscriptions is derived from an append-only event log (control_plane_api_and_state_model.md §4). The event log is the single source of truth.
Derived state (materialized views) must be reconstructable and discardable.
State is derived from proposal events, approval events, permit events, execution events, specialist lifecycle events, and trust state transitions.
Idempotency
All mutating operations (including submitDecision) require an explicit idempotency key (control_plane_api_and_state_model.md §5). Duplicate requests return the original result without creating new events.
Concurrency
All state mutations use optimistic concurrency controls with expected_version. A version mismatch rejects the operation and requires retry with updated state.