Two-agent PTY orchestrator for CLI models.
Let a Designer and a Critic debate until they agree.
GitHub | Getting Started | Troubleshooting
CriticLoop runs two CLI agents in separate PTYs, assigns them roles, and loops their feedback until both agree on a shared proposal.
Perfect for:
- Designer vs Reviewer (same model, different roles)
- Planner vs Critic (two different CLIs)
- Researcher vs Skeptic (deep review loops)
+-------------------- Designer --------------------+ +-------------------- Reviewer --------------------+
| AGREE: no | | AGREE: no |
| PROPOSAL: | | PROPOSAL: |
| 1) Draft API surface | | 1) Reduce scope |
| 2) Add tests | | 2) Add edge cases |
| NOTES: | | NOTES: |
| - Missing error paths | | - Consider timeouts |
+---------------------------------------------------+ +---------------------------------------------------+
+------------------------- Status -------------------------+
| Round 2 - awaiting Alpha review |
+-----------------------------------------------------------+
- Model-agnostic: Works with any CLI model, including open-source tools.
- Role-driven: Enforce Designer vs Critic behavior with a strict protocol.
- Deterministic stopping: Sentinel + idle detection keeps outputs clean.
- Traceable: Full transcripts + raw logs per agent.
npm installnode ./bin/criticloop.js setupThen run a task:
node ./bin/criticloop.js --task "Design a minimal API and test plan"Use --interactive to input roles (and optionally names/commands) at runtime:
node ./bin/criticloop.js --interactive --task "Draft a plan and review it"- Same model, different roles:
examples/criticloop.config.same-model.json - Two different CLIs:
examples/criticloop.config.two-clis.json
sequenceDiagram
participant A as Agent A (Designer)
participant B as Agent B (Reviewer)
A->>B: Proposal
B->>A: Review (agree or revise)
A->>B: Review (agree or revise)
loop until both agree
B-->>A: Agreement check
end
node ./bin/criticloop.js [options]
Commands:
init Write a template config
setup Interactive wizard (recommended)
Options:
-c, --config <path> Config path (default: criticloop.config.json)
-t, --task <text> Task text
--task-file <path> Read task from file
--no-tui Disable split TUI
--interactive Prompt for roles/names/commands at runtime
--save-config Save config when using setup/interactive
--print-config Print resolved config and exit
--session-name <name> Label session log folder
--name <text> Name for both agents
--name-a <text> Name for agent A
--name-b <text> Name for agent B
--role <text> Role for both agents
--role-a <text> Role for agent A
--role-b <text> Role for agent B
--command <text> Command for both agents
--command-a <text> Command for agent A
--command-b <text> Command for agent B
--cmd-a <text> Executable for agent A (use with --args-a)
--args-a <text> Args for agent A (quoted string)
--cmd-b <text> Executable for agent B (use with --args-b)
--args-b <text> Args for agent B (quoted string)
--max-rounds <n> Override max rounds
--timeout-ms <n> Override message hard timeout
--idle-ms <n> Override idle timeout
--sentinel <string> Override end-of-message sentinelCRITICLOOP_TASKCRITICLOOP_AGENT_A_NAME,CRITICLOOP_AGENT_B_NAMECRITICLOOP_AGENT_A_ROLE,CRITICLOOP_AGENT_B_ROLECRITICLOOP_AGENT_A_COMMAND,CRITICLOOP_AGENT_B_COMMANDCRITICLOOP_SESSION_NAME
The init command writes criticloop.config.json. Example:
{
"session": {
"maxRounds": 6,
"timeoutMs": 120000,
"idleMs": 1500
},
"tui": {
"enabled": true
},
"agents": {
"alpha": {
"name": "Alpha",
"role": "Planner",
"command": "your-cli-a --your-args"
},
"beta": {
"name": "Beta",
"role": "Critic",
"command": "your-cli-b --your-args"
}
}
}You can also use cmd + args instead of command if you prefer exact argument splitting.
CriticLoop sends a short bootstrap message that tells each agent to:
- Always answer with:
AGREE: yes|noPROPOSAL:(the plan)- optional
NOTES:
- End every response with the sentinel shown in the prompt
If a model fails to print the sentinel, CriticLoop can fall back to idle detection.
Each session writes to sessions/<timestamp>/:
agentA.logandagentB.log(raw PTY output)transcript.jsonl(structured messages)summary.json(final agreement)
Open two terminals to tail logs if you prefer separate windows:
tail -f sessions/<timestamp>/agentA.logGet-Content -Wait sessions/<timestamp>/agentB.logdocs/GETTING_STARTED.mddocs/TROUBLESHOOTING.mddocs/PROTOCOL.md
- Some CLIs display prompts or extra text; the sentinel helps delimit responses.
- If you see premature cutoffs, increase
timeoutMsoridleMsin config.