Skip to content

Channels & Signals

cliq agents don’t share memory. Each agent runs in its own process with its own context window. There is no message bus, no shared database, no API layer between them. Instead, coordination happens through two filesystem-based mechanisms:

  • Channels — directories where agents write structured handoff documents for downstream phases
  • Signals — tiny files the orchestrator uses to track completion, verdicts, and pipeline state

This page covers both in detail.


Most multi-agent frameworks use message queues, shared memory, or API calls for inter-agent communication. cliq uses plain files. This is a deliberate choice, not a limitation.

Transparency. Every handoff between agents is a markdown file you can open and read. There’s no serialization format to decode, no message broker to query. When something goes wrong, you read the file.

Auditability. The .cliq/ directory is a complete record of what happened. Channels show what each agent told the next. Signals show when each phase completed. You can reconstruct the entire pipeline run from the filesystem alone.

Portability. Channels and signals work anywhere there’s a filesystem — your laptop, a CI runner, a container, a remote server. No daemon to install, no port to bind.

Debuggability. You can inspect, edit, or even pre-populate channel files mid-run. If an agent wrote a bad handoff, fix the file and re-run the downstream phase. The filesystem is your API.


A channel is a directory under .cliq/channels/ dedicated to one edge in the workflow DAG. When phase A depends on phase B, there is a channel directory where B writes handoff notes for A to read.

Channel directories follow the pattern:

{from_phase}--{to_phase}

The from phase is the writer; the to phase is the reader. The double-dash -- separator is used instead of a single dash because phase names themselves may contain hyphens (e.g., security-auditor).

Examples:

architect--developer # architect writes handoff for developer
architect--security-auditor # architect writes handoff for security-auditor
developer--reviewer # developer writes handoff for reviewer
reviewer--developer # reviewer writes feedback to developer (gate route)

The orchestrator creates channel directories at pipeline startup, before any agent runs. The ChannelManager.create_channels() method walks the workflow DAG’s dependency edges and creates one directory per edge:

for (const edge of edges) {
const channel_name = `${edge.from}--${edge.to}`;
fs.mkdirSync(path.join(channels_dir, channel_name), { recursive: true });
}

Gate phases get additional bidirectional channels created via ChannelManager.create_gate_channels() — more on that below.

The result: every possible handoff path has a directory ready before the first agent starts.

When the orchestrator activates a phase, the prompt generator builds a prompt that tells the agent exactly which channels to read and write.

Incoming channels. For each dependency (predecessor phase), the prompt includes:

Read your incoming channels:
- .cliq/channels/architect--developer/ (handoff from Architect)
- .cliq/channels/tester--developer/ (handoff from Tester)
Read any instructions.md files in those channels if present.

The agent reads these directories, finds handoff files written by predecessor phases, and uses that context to inform its work.

Outgoing channels. For each successor phase, the prompt includes:

When complete, write your handoff notes to these channels:
- .cliq/channels/developer--reviewer/handoff.md

The agent writes a handoff.md file (or other artifacts) into each outgoing channel directory before signaling completion.

When a phase has multiple successors in the DAG, it writes to multiple outgoing channels. Each handoff can — and should — be tailored to the downstream agent’s needs.

For example, an architect phase with both a developer and a security-auditor downstream:

.cliq/channels/
├── architect--developer/
│ └── handoff.md # Implementation plan, API contracts, file structure
├── architect--security-auditor/
│ └── handoff.md # Threat model, auth boundaries, data flow concerns

The architect writes different content to each channel. The developer gets an implementation plan; the security auditor gets a threat model. Same source phase, targeted handoffs.

Gate Channels: Bidirectional Communication

Section titled “Gate Channels: Bidirectional Communication”

Gate phases (like a reviewer) create bidirectional channels between the gate and every phase it can route work to. This is handled by ChannelManager.create_gate_channels(), which creates both the forward and reverse channel:

.cliq/channels/
├── developer--reviewer/ # developer → reviewer (normal DAG flow)
└── reviewer--developer/ # reviewer → developer (gate feedback)

When the reviewer gate routes work back to the developer, it writes specific feedback into reviewer--developer/handoff.md — what failed, what needs fixing, and any relevant context. The developer reads this feedback on its next activation, fixes the issues, and writes back to developer--reviewer/ so the gate can re-evaluate.

This feedback loop can repeat up to the gate’s max_iterations budget (typically 3).

Any channel directory can contain an instructions.md file with role-specific guidance for that handoff. These are included in the agent’s prompt context and read before the agent produces its handoff.

.cliq/channels/architect--developer/
├── instructions.md # "Include file paths, API signatures, and test expectations"
└── handoff.md # The actual handoff (written by the architect at runtime)

Channel instructions are optional. When present, they shape the handoff’s format and content. You can use them to enforce structure — requiring specific sections, file path references, or acceptance criteria.

If a channel directory exists but contains no handoff files, the downstream agent simply has no predecessor context for that edge. The agent proceeds with whatever other context it has (role instructions, task board, design docs, other incoming channels).

If a channel directory doesn’t exist at all, it won’t appear in the agent’s prompt — the prompt generator only includes channels for edges that exist in the DAG.

Neither case is an error. Phases without predecessors (like the first phase in a pipeline) naturally have no incoming channels.


Signals are small files in .cliq/signals/ that the orchestrator uses to coordinate the pipeline. They answer questions like: Has this phase finished? What did the gate decide? Is the pipeline still running?

Agents don’t read signals (with one exception: gate agents write verdict signals). Signals are primarily an orchestrator-to-orchestrator mechanism.

Signal fileWritten byContentsPurpose
{phase}_doneCliq Agent SDKEmpty filePhase completed its work
{gate}_verdict_rawGate agentVERDICT: PASS\nREASON: ...Raw verdict text from the gate agent
{gate}_verdictOrchestratorJSON GateVerdict objectParsed, structured verdict
{gate}_gate_iterationOrchestratorInteger (e.g., 2)Current gate loop iteration
{phase}_routedOrchestratorEmpty fileSupport phase was activated by a gate route
_pipeline_statusOrchestratorCOMPLETED, ESCALATED, FAILED, or CANCELLEDFinal pipeline outcome

The lifecycle of a standard phase follows this sequence:

  1. Clear — The orchestrator calls SignalManager.clear_done() to remove any stale {phase}_done signal from a previous run or iteration.

  2. Activate — The orchestrator writes a dispatch file and spawns the agent process in a tmux pane.

  3. Agent works — The cliq agent SDK reads the dispatch, assembles context (role, channels, requirements, task board), calls the implementation, and captures the result. The implementation does the creative work (running a CLI, calling an API, waiting for human review).

  4. SDK signals done — The SDK writes .cliq/signals/{phase}_done after successfully processing the result. This creates an empty file.

  5. Orchestrator detects — The orchestrator polls for the _done file every 3 seconds (the POLL_INTERVAL). When it finds the file, it knows the agent is finished.

  6. Advance — The agent process exits cleanly. The orchestrator checks which phases are now ready (all dependencies met) and activates the next batch.

Gate phases have a more complex signal flow than standard phases:

  1. Orchestrator runs automated checks — Before the gate agent even starts, the orchestrator runs any configured check commands (tests, linters, etc.) and records results.

  2. Orchestrator builds gate context — Check results, team manifest, and verdict history are written to .cliq/gate_context.md.

  3. Orchestrator clears previous verdictclear_verdict() removes the old {gate}_verdict and {gate}_verdict_raw files.

  4. Gate agent evaluates — The orchestrator injects the verdict protocol into the gate agent’s prompt automatically. The agent reads the gate context, reviews the work, and writes its verdict to {gate}_verdict_raw:

    VERDICT: PASS
    REASON: All checks pass and code meets requirements.

    Or to route work back:

    VERDICT: ROUTE:developer
    REASON: Tests fail due to incomplete error handling.

    Or to escalate to a human:

    VERDICT: ESCALATE
    REASON: Merge conflicts require human judgment.
  5. Orchestrator parses verdict — The orchestrator reads the raw verdict, parses it into a structured GateVerdict, and writes the parsed version to {gate}_verdict.

  6. Branch on outcome:

    • PASS — Gate is done. Pipeline continues to post-gate phases.
    • ROUTE — Orchestrator activates the target phase (e.g., developer), waits for it to complete, increments {gate}_gate_iteration, and loops back to step 1.
    • ESCALATE — Pipeline halts. Human intervention required.
  7. Budget check — If the gate has exhausted its max_iterations budget without passing, the orchestrator auto-escalates.

ParameterValueDescription
POLL_INTERVAL3 secondsHow often the orchestrator checks for signal files
PHASE_TIMEOUT20 minutesMax time a standard phase can run before escalation
GATE_AGENT_TIMEOUT15 minutesMax time a gate agent can take to produce a verdict

If a phase times out without writing its _done signal, the orchestrator escalates and halts the pipeline. The agent may be stuck, crashed, or simply taking too long.

The _pipeline_status signal doubles as a cancellation mechanism. When a user runs cliq cancel or sends SIGINT/SIGTERM to the orchestrator:

  1. The orchestrator writes CANCELLED to .cliq/signals/_pipeline_status
  2. Every poll loop checks for cancellation before checking for _done
  3. If cancelled, the orchestrator cleans up and exits

This means cancellation is detected within one POLL_INTERVAL (3 seconds) regardless of what the agents are doing.


Here’s the complete .cliq/ directory structure for a pipeline with four phases (architect → developer → reviewer gate → developer support loop):

.cliq/
├── channels/
│ ├── architect--developer/
│ │ ├── instructions.md # (optional) guidance for this handoff
│ │ └── handoff.md # architect's plan for the developer
│ ├── architect--security-auditor/
│ │ └── handoff.md # architect's threat model notes
│ ├── developer--reviewer/
│ │ └── handoff.md # developer's summary for the reviewer
│ └── reviewer--developer/
│ └── handoff.md # reviewer's feedback (gate route)
├── signals/
│ ├── architect_done # empty file — architect finished
│ ├── developer_done # empty file — developer finished
│ ├── security-auditor_done # empty file — security-auditor finished
│ ├── reviewer_verdict_raw # "VERDICT: PASS\nREASON: ..."
│ ├── reviewer_verdict # {"outcome":"PASS","reason":"..."}
│ ├── reviewer_gate_iteration # "2"
│ ├── developer_routed # empty — developer was re-activated by gate
│ └── _pipeline_status # "COMPLETED"
├── roles/
│ ├── architect.md
│ ├── developer.md
│ ├── security-auditor.md
│ └── reviewer.md
├── prompts/
│ ├── 1_architect.md
│ ├── 2_developer.md
│ ├── 3_security-auditor.md
│ └── 4_reviewer.md
├── design/ # shared design documents
├── task_board.md # shared task board
├── gate_context.md # built by orchestrator for gate agents
└── orchestrator.log # full orchestrator output

Channels are just directories with markdown files. You can read them at any time, even while the pipeline is running:

Terminal window
# See all channels
ls .cliq/channels/
# Read a specific handoff
cat .cliq/channels/architect--developer/handoff.md
# Check if a channel has content
ls -la .cliq/channels/developer--reviewer/

If a downstream agent is behaving unexpectedly, check its incoming channels first — the handoff it received may be incomplete or misleading.

Terminal window
# See all signals
ls -la .cliq/signals/
# Check if a specific phase is done
test -f .cliq/signals/developer_done && echo "done" || echo "not done"
# Read the current gate verdict
cat .cliq/signals/reviewer_verdict_raw
# Check the gate iteration count
cat .cliq/signals/reviewer_gate_iteration
# Check pipeline status
cat .cliq/signals/_pipeline_status

Phase never signals done. The agent process may have crashed or the implementation may have hung. Check the agent’s tmux pane output and .cliq/signals/{phase}_error for error details. The orchestrator will eventually time out (20 minutes) and escalate.

Gate stuck in a loop. The gate keeps routing to the same phase without progress. Check reviewer_gate_iteration — if it’s approaching max_iterations, the gate will auto-escalate. Read the verdict history in gate_context.md to see what’s happening across iterations.

Stale signals from a previous run. The orchestrator calls SignalManager.clear_done() before activating each phase, so stale _done files are not normally an issue. If you’re debugging manually, you can clear all signals:

Terminal window
rm .cliq/signals/*_done
rm .cliq/signals/*_verdict*
rm .cliq/signals/*_gate_iteration
rm .cliq/signals/*_routed

Channel handoff is empty or wrong. If a predecessor phase wrote a poor handoff, you can edit the file directly and re-run the downstream phase. The channel file is the input — fix the input, get better output.


Consider a simple pipeline: architect → developer → reviewer (gate).

phases:
- name: architect
type: standard
- name: developer
type: standard
depends_on: [architect]
- name: reviewer
type: gate
depends_on: [developer]
max_iterations: 3

Step 1: Orchestrator starts. Creates channels and clears signals.

.cliq/channels/architect--developer/ (empty)
.cliq/channels/developer--reviewer/ (empty)
.cliq/channels/reviewer--developer/ (empty, gate route channel)

Step 2: Architect runs. No incoming channels (first phase). Writes its plan:

.cliq/channels/architect--developer/handoff.md
# Architecture Handoff
## Approach
Implement the REST API with three endpoints: GET /items, POST /items, DELETE /items/:id.
## File Structure
- src/routes/items.ts — route handlers
- src/models/item.ts — data model
- src/db/connection.ts — database setup
## Key Decisions
- Use Express for routing
- SQLite for storage (portable, no daemon)
- Input validation with zod schemas

Architect touches architect_done. Orchestrator detects it, kills the architect process.

Step 3: Developer runs. Reads architect--developer/handoff.md, implements the plan, writes its summary:

.cliq/channels/developer--reviewer/handoff.md
# Developer Handoff
## What Was Built
- Implemented all three endpoints in src/routes/items.ts
- Added zod validation schemas in src/models/item.ts
- SQLite connection pool in src/db/connection.ts
- Added 12 unit tests in tests/items.test.ts
## Notes for Review
- Used transactions for DELETE to handle concurrent access
- Tests cover happy path and validation errors

Developer touches developer_done. Orchestrator detects it, moves to the gate.

Step 4: Reviewer gate (iteration 1). Orchestrator runs checks (tests, linter). Tests fail. Orchestrator builds gate_context.md, launches the gate agent. The reviewer writes:

VERDICT: ROUTE:developer
REASON: 2 of 12 tests fail — DELETE endpoint returns 500 instead of 404 for missing items.

Orchestrator parses the verdict, writes feedback to the gate channel:

.cliq/channels/reviewer--developer/handoff.md
# Gate Feedback (Iteration 1)
Fix the DELETE endpoint: it should return 404 when the item doesn't exist,
not 500. The missing-item test and the concurrent-delete test both fail.

Step 5: Developer runs again (routed). Reads reviewer--developer/handoff.md, fixes the bug, writes back to developer--reviewer/, touches developer_done.

Step 6: Reviewer gate (iteration 2). Orchestrator re-runs checks. All tests pass. Gate agent writes:

VERDICT: PASS
REASON: All 12 tests pass. Code is clean and well-structured.

Orchestrator writes COMPLETED to _pipeline_status. Pipeline done.