Skip to content

A2A Agent Mode

cliq can operate as an A2A (Agent-to-Agent) agent. When enabled, other AI systems — planners, orchestrators, mesh routers — can discover what cliq can do, send it a requirement in natural language, and receive structured results when the pipeline completes. cliq assembles the right team, orchestrates the full pipeline, and returns the outcome. No human in the loop (unless a HUG gate is involved).

This turns cliq into a callable capability in a larger agent ecosystem.


A2A is off by default. Pass --a2a when starting the server:

Terminal window
cliq server start --a2a

This does three things:

  1. Mounts the agent card at GET /.well-known/agent-card.json — a standard discovery endpoint that describes cliq’s capabilities
  2. Mounts the JSON-RPC endpoint at POST /a2a/jsonrpc — where calling agents send tasks
  3. Joins configured meshes — registers with any mesh networks marked active: true in settings

Without --a2a, none of these exist. The server runs as a plain local API.

To join a specific mesh:

Terminal window
cliq server start --a2a --mesh prod
cliq server start --a2a --mesh prod --mesh staging

The --mesh flag implies --a2a. You can repeat it to join multiple meshes.


Add a server.a2a section to ~/.cliqrc/settings.json:

{
"server": {
"port": 4100,
"a2a": {
"agent_type": "cliq_pipeline",
"instance_id": "cliq-macbook-01",
"workspace_root": "/tmp/cliq-workspaces",
"meshes": [
{
"name": "prod",
"type": "savant",
"active": true,
"url": "https://savant.example.com",
"client_id": "cliq-agent",
"client_secret": "your-secret-here"
}
]
}
}
}
FieldTypeDescription
server.portnumberHTTP port for the cliq server. Default: 4100.
server.a2a.agent_typestringLogical agent type for mesh routing. Shared across all your cliq instances.
server.a2a.instance_idstringUnique identifier for this cliq instance. Must be globally unique.
server.a2a.workspace_rootstringDirectory where temporary task workspaces are created. Cleaned up after each task.
server.a2a.meshesarrayMesh networks to join. See below.
server.a2a.advertisestring[]Which teams to include in the agent card. Default: ["@cliq"]. See Advertising Teams.
server.a2a.enable_builderbooleanExpose builder capabilities (build_team, improve_role, validate_team). Default: false.
FieldTypeDescription
namestringHuman-readable name (used with --mesh flag)
typestringMesh protocol. Currently "savant" is supported.
activebooleanJoin this mesh when --a2a is used without --mesh
urlstringMesh server URL
client_idstringAuthentication client ID
client_secretstringAuthentication client secret

When A2A is enabled, GET /.well-known/agent-card.json returns a standard A2A agent card. Calling agents use this to discover what cliq can do before sending tasks.

The card is built dynamically from your installed teams. Each team’s top-level metadata — description, use_when, not_for, tags, and inputs — is used to build a skill entry in the agent card.

The skill description is composed from the team’s description, use_when/not_for hints, and inputs. For example, given a team like:

name: "@local/feature-pipeline"
description: "Feature development with git lifecycle for JS/TS projects"
tags: [code, git, tdd]
use_when:
- The task requires code changes delivered as a pull request
inputs:
- name: target_branch
description: "Branch name for the feature"

the agent card would include a skill description like:

Feature development with git lifecycle for JS/TS projects. Use when: The task requires code changes delivered as a pull request. Required parameters (provide as a JSON data part): target_branch — Branch name for the feature.

See Build from Scratch — A2A Metadata for a walkthrough.

The advertise setting controls which teams appear in the agent card. Teams should include description, use_when, and not_for fields for effective A2A discovery.

PatternMatches
"@"All teams in all scopes
"@cliq"All @cliq teams
"@local"All @local teams
"@cliq/hello-world"A specific team

Prefix with ! to exclude:

PatternExcludes
"!@local"All @local teams
"!@cliq/hello-world"A specific team

Evaluation: union all include patterns, subtract all exclude patterns. If no include patterns are present, all teams are included (as if "@" were specified).

{ "advertise": ["@cliq"] }
{ "advertise": ["@local", "@cliq/feature-dev-js"] }
{ "advertise": ["@cliq", "!@cliq/hello-world"] }
{ "advertise": [] }

System capabilities (builder) are controlled separately via enable_builder, not advertise.


A calling agent sends a task via JSON-RPC to POST /a2a/jsonrpc. The message follows a convention of text + data parts:

  • Message text — the requirement spec / task description. This is what the team works on. It becomes the requirement.md that drives the pipeline.
  • Data part (JSON) — contains team, optional repos, and team inputs. Only the team field is required; inputs is only needed when the team declares inputs.

The agent card’s skill description tells callers what to send based on the team’s inputs fields.

These are separate concepts:

  • Task (message text) — the requirement the team works on. Sent as the message text in the JSON-RPC request.
  • Inputs (data part) — template parameters that configure the workflow (e.g. branch names, folder IDs). Declared in inputs and referenced via $(inputs.key).

When no message text is provided, the team runs in nospec mode — it executes autonomously with a generic task.

{
"jsonrpc": "2.0",
"method": "message/send",
"id": "req-1",
"params": {
"message": {
"role": "user",
"parts": [
{
"kind": "text",
"text": "Write a blog post about the future of AI coordination"
},
{
"kind": "data",
"data": {
"team": "@local/blog-pipeline",
"repos": "https://github.com/org/content.git"
}
}
]
}
}
}

When the team declares inputs (e.g. target_branch), pass them in the data part:

{
"jsonrpc": "2.0",
"method": "message/send",
"id": "req-2",
"params": {
"message": {
"role": "user",
"parts": [
{
"kind": "text",
"text": "Build a login page with email/password auth"
},
{
"kind": "data",
"data": {
"team": "@cliq/feature-dev-js",
"repos": "https://github.com/org/webapp.git",
"inputs": {
"target_branch": "feature/login"
}
}
}
]
}
}
}

If required inputs are missing, the executor responds with an error listing the missing parameters.

FieldRequiredDescription
teamYesTeam to invoke (@scope/name)
reposNoComma-separated git repo URLs to clone into the workspace
inputsNoJSON object of team input values (keys must match declared input names)

cliq resolves the team, creates an isolated workspace under workspace_root, runs cliq initcliq assemblecliq reqcliq run, and returns the result.


Every A2A task moves through these states:

  1. Submitted — task received, workspace being created
  2. Working — pipeline running
  3. Completed — pipeline finished successfully
  4. Failed — pipeline encountered an error or escalated
  5. Cancelled — cancelled by request

Each task gets an isolated workspace. The workspace is cleaned up after the task reaches a terminal state.

Use standard A2A methods — no special capabilities needed:

  • tasks/get — current state of any task, including phase progress and artifacts
  • tasks/cancel — cancels a running pipeline; the orchestrator terminates agents and exits cleanly

Pipelines dispatched via A2A can run on three backends, selected automatically from settings:

BackendSettingDescription
LocaldefaultRuns directly on the host
Dockerdocker.enabled: trueEach task runs in an isolated container with the workspace bind-mounted
Kuberneteskubernetes.enabled: trueTasks run as K8s Jobs with shared PVC storage (requires PostgreSQL)

Kubernetes takes highest precedence, then Docker, then local. See Docker Isolation and Kubernetes Deployment.


Use message/sendSubscribe instead of message/send to receive Server-Sent Events (SSE) as the pipeline runs:

data: {"kind":"status-update","status":{"state":"working","message":{"parts":[{"kind":"text","text":"Pipeline started — team: blog-pipeline"}]}}}
data: {"kind":"status-update","status":{"state":"working","message":{"parts":[{"kind":"text","text":"Running: researcher"}]}}}
data: {"kind":"status-update","status":{"state":"working","message":{"parts":[{"kind":"text","text":"Phase completed: researcher"},{"kind":"data","data":{"event":"on_phase_complete","phase":"researcher","phase_type":"standard"}}]}}}
data: {"kind":"status-update","status":{"state":"completed","message":{"parts":[{"kind":"text","text":"Pipeline completed"}]}}}
data: {"kind":"artifact-update","artifact":{"name":"pipeline_result","parts":[{"kind":"data","data":{"status":"completed","phases":[...]}}]}}

Every update has a human-readable TextPart. Most also include a machine-readable DataPart with structured context so calling agents can programmatically track progress.

EventWhenDataPart fields
Pipeline startedTask accepted
Workspace initializedAfter cliq initevent, instance_id, team, req_key
Phase startedPhase beginsevent, phase, phase_type, instance_id
Phase outputPeriodic agent output flushevent, phase, offset, lines
Phase completedPhase finishesevent, phase, phase_type, outcome
Gate verdictGate command completesevent, gate, iteration, max_iterations, commands, verdict
Pipeline escalatedHuman intervention neededevent, phase, reason
Pipeline completedAll phases finished— (terminal)
Pipeline cancelledCancelled by requestevent, reason

When server.workflow_streaming is enabled in settings, the orchestrator flushes each agent’s terminal output as phase_output events. Output is formatted into:

  • lines — human-readable text (assistant messages, tool calls as → Read path, completions as ✓ N lines)
  • events (optional) — raw structured JSON objects for programmatic analysis

The offset field tracks byte position for gap detection.

Before the pipeline starts, the orchestrator verifies required tools. These appear as structured events:

{"type":"cliq:preflight_started","tools":["git","node"]}
{"type":"cliq:tool_check","tool":"git","available":true}
{"type":"cliq:tool_check","tool":"node","available":true}
{"type":"cliq:preflight_completed","passed":2,"total":2,"verdict":"PASS"}

If preflight fails, the pipeline does not start.

During gate and exec phases, the orchestrator emits command results:

{"type":"cliq:command_started","command":"output-exists","phase":"quality-gate"}
{"type":"cliq:command_completed","command":"output-exists","pass":true,"duration_ms":50,"exit_code":0}
{"type":"cliq:commands_summary","phase":"quality-gate","passed":4,"total":4,"verdict":"PASS"}

The verdict field reflects the outcome: PASS (all passed), CONTINUE (some failed, iterations remain), or ESCALATE (failed on final iteration).

When the pipeline reaches a terminal state, cliq publishes a TaskArtifactUpdateEvent containing a post-mortem summary:

FieldAlways presentDescription
statusYescompleted, failed, or cancelled
instance_idYescliq instance ID
phasesWhen phases ranPhase-by-phase status array
gate_verdictsWhen gates ranAll gate verdicts with command results
orchestrator_logWhen log existsLast 10KB of orchestrator.log
escalationOn failurePhase and reason

The orchestrator runs in tmux and writes lifecycle events to .cliq/signals/_a2a_events as JSON Lines. The server process polls this file and relays events to the calling agent over the SSE stream. This is automatic for all A2A-initiated pipelines.


For fully asynchronous operation, calling agents can submit a task and receive updates via HTTP callbacks instead of holding an SSE connection open:

{
"jsonrpc": "2.0",
"method": "message/send",
"id": "req-1",
"params": {
"message": {
"role": "user",
"parts": [
{
"kind": "text",
"text": "Write a blog post about teamwork"
},
{
"kind": "data",
"data": {
"team": "@local/blog-pipeline"
}
}
]
},
"configuration": {
"pushNotificationConfig": {
"url": "https://caller.example.com/a2a/notify",
"token": "my-callback-token"
}
}
}
}

The server acknowledges immediately with submitted state, then pushes status updates to the callback URL as the pipeline progresses. Push configs survive server restarts (persisted in SQLite). The agent card advertises pushNotifications: true.


When using a Savant mesh, the lifecycle is:

  1. Authenticate — exchange client_id / client_secret for a JWT via POST /auth/token
  2. RegisterPOST /agents/register with the agent card URL and full agent card object
  3. Receive tasks — the mesh routes tasks to POST /a2a/jsonrpc based on capability matching
  4. DeregisterPOST /agents/deregister on graceful shutdown

The agent card includes enough detail for the mesh router to intelligently match tasks to cliq — team descriptions, use_when / not_for hints, tags, and skill definitions.


Beyond team pipelines, cliq can expose operational capabilities when enable_builder: true:

Generate a complete team from a natural language description. Returns roles, workflow DAG, gate commands, and A2A metadata.

ParameterRequiredDescription
intentYesNatural language description of the desired team
auto_installNoIf true, install to @local

Improve a single role prompt using AI analysis.

ParameterRequiredDescription
role_nameYesRole to improve
role_contentYesCurrent markdown content
team_nameYesOwning team name
instructionNoSpecific improvement direction

Validate a team definition for structural correctness (no LLM needed).

ParameterRequiredDescription
team_jsonYesJSON-encoded team definition

Response: { valid, errors, warnings }


All task state is persisted in ~/.cliqrc/cliq.db (SQLite, WAL mode). This provides:

  • Crash recovery — if the server crashes, in-flight tasks are marked as failed and workspaces are cleaned up on restart
  • Task retention — terminal tasks remain queryable for 7 days, pruned on server shutdown
  • Push notification durability — callback configs survive restarts

For multi-replica deployments, switch to PostgreSQL via database.url in settings.


IssueFix
”No A2A configuration found”Add a server.a2a section to ~/.cliqrc/settings.json
”Failed to register with mesh”Check mesh URL, credentials, and network connectivity
Agent card not servedEnsure --a2a was passed to cliq server start
Task stuck in “working”Check workspace for orchestrator logs. Cancel and retry.
Tasks lost after crashTasks are in SQLite. On restart, in-flight tasks are marked failed. Query tasks/get.
Push notifications not receivedEnsure callback URL is reachable. Check server logs for delivery errors.