Generated: 2026-02-11
Source: Research for issue conductorbot-6cr
Focus: How OpenClaw keeps coding agents working autonomously until task completion with minimal human intervention.
OpenClaw achieves autonomy through prompt engineering, heartbeat polling, and sub-agent delegation - NOT through complex supervisor logic or confidence thresholds.
The pattern: "Just do it, don't ask" combined with periodic heartbeat checks for new work.
File: /Users/ajsharp/code/github/openclaw/src/agents/system-prompt.ts (~650 lines)
Key sections that drive autonomy:
"## Tool Call Style",
"Default: do not narrate routine, low-risk tool calls (just call the tool).",
"Narrate only when it helps: multi-step work, complex/challenging problems,
sensitive actions (e.g., deletions), or when the user explicitly asks.",
"Keep narration brief and value-dense; avoid repeating obvious steps.",Pattern: Explicitly tells agents to act without asking permission for routine operations.
File: /Users/ajsharp/code/github/openclaw/src/auto-reply/heartbeat.ts
export const HEARTBEAT_PROMPT =
"Read HEARTBEAT.md if it exists (workspace context). Follow it strictly.
Do not infer or repeat old tasks from prior chats.
If nothing needs attention, reply HEARTBEAT_OK.";How it works:
- Poll agent every 30 minutes (configurable)
- Check
HEARTBEAT.mdfor pending tasks - If agent responds "HEARTBEAT_OK" → discard, no user notification
- If agent has content → deliver to user
Result: Creates continuous background loop where agents autonomously pick up work.
OpenClaw shapes agent behavior through prompting rather than filtering questions:
- No "should I ask the user?" confidence thresholds
- No question interception middleware
- Instead: prompt engineering tells agents when to narrate vs act
File: /Users/ajsharp/code/github/openclaw/src/agents/tools/sessions-spawn-tool.ts
"Spawn a background sub-agent run in an isolated session
and announce the result back to the requester chat."Flow:
- Main agent spawns sub-agent with task description
- Sub-agent works in isolated session
- Sub-agent announces completion back to main session
- Main agent sees result and decides next steps
Why this works:
- Complex work delegated to isolated sub-agents
- Main agent stays responsive
- Sub-agents can fail/retry without blocking main agent
File: /Users/ajsharp/code/github/openclaw/src/agents/pi-embedded-runner/run.ts
return {
payloads: [...],
meta: {
durationMs,
agentMeta: { sessionId, provider, model, usage },
aborted,
stopReason: attempt.clientToolCall ? "tool_calls" : undefined,
},
};Stop reasons:
"end_turn"- Agent finished response"tool_calls"- Agent made tool call (waiting for result)"error"- Agent encountered error"aborted"- User/system aborted
File: /Users/ajsharp/code/github/openclaw/src/auto-reply/tokens.ts
export const SILENT_REPLY_TOKEN = "SILENT_REPLY";
export const HEARTBEAT_TOKEN = "HEARTBEAT_OK";
// Agent can signal "nothing to say" without user notification
if (reply === "HEARTBEAT_OK" || reply === SILENT_REPLY_TOKEN) {
// Don't deliver to user
}Pattern: Agent explicitly signals when it has nothing to report.
Completion is inferred from:
- Agent stops generating (
end_turn) - Agent returns heartbeat ack (
HEARTBEAT_OK) - Agent returns silent token (
SILENT_REPLY_TOKEN) - Sub-agent announces result back to parent
Sub-agent Registry (/Users/ajsharp/code/github/openclaw/src/agents/subagent-registry.ts):
registerSubagentRun({
runId: childRunId,
childSessionKey,
requesterSessionKey: requesterInternalKey,
requesterOrigin,
task,
cleanup,
label,
runTimeoutSeconds,
});Pattern:
- Main agent delegates work via
sessions_spawntool - Child session runs in background with isolated state
- Child announces result back to parent via delivery config
- Parent sees announcement and continues
Sub-agent System Prompt:
export function buildSubagentSystemPrompt(params: {
requesterSessionKey?: string;
task: string;
}): string {
return `## Subagent Context
You are running as a sub-agent spawned by ${params.requesterSessionKey}.
Your task: ${params.task}
When complete, results will be announced back to the requester.`;
}Key: No centralized supervisor - coordination is peer-to-peer via sessions and announcements.
File: /Users/ajsharp/code/github/openclaw/src/agents/pi-embedded-runner/run.ts
while (true) {
attemptedThinking.add(thinkLevel);
const attempt = await runEmbeddedAttempt({...});
// Handle context overflow - auto-compact and retry
if (contextOverflowError && overflowCompactionAttempts < MAX_ATTEMPTS) {
overflowCompactionAttempts++;
const compactResult = await compactEmbeddedPiSessionDirect({...});
if (compactResult.compacted) {
continue; // Retry after compaction
}
}
// Handle auth profile rotation - try next account
if (shouldRotate && await advanceAuthProfile()) {
continue; // Retry with new auth
}
// Handle thinking level fallback
const fallbackThinking = pickFallbackThinkingLevel({...});
if (fallbackThinking) {
thinkLevel = fallbackThinking;
continue; // Retry with different thinking level
}
// Success or unrecoverable error
return {...};
}- Success: Agent completes (
stop_reason = "end_turn") - Error escalation: After retry budget exhausted, throw FailoverError
- Timeout:
timeoutMskills the run - Abort signal: Caller can abort via AbortSignal
- Context overflow: Auto-compact session history (up to 3 attempts)
- Tool result truncation: Truncate oversized tool outputs
- Auth rotation: Rotate to next API key on rate limits
- Thinking level fallback: Try lower thinking levels if unsupported
File: /Users/ajsharp/code/github/openclaw/src/agents/tools/agent-step.ts
export async function runAgentStep(params: {
sessionKey: string;
message: string;
extraSystemPrompt: string;
timeoutMs: number;
}): Promise<string | undefined> {
// Submit message
const response = await callGateway({ method: "agent", params: {...} });
// Wait for completion
const wait = await callGateway({
method: "agent.wait",
params: { runId, timeoutMs: stepWaitMs }
});
// Fetch result
return await readLatestAssistantReply({ sessionKey });
}Pattern: Allows multi-turn agent loops where each step builds on the previous.
File: /Users/ajsharp/code/github/openclaw/src/agents/tools/cron-tool.ts
// Schedule types
{ "kind": "at", "at": "<ISO-8601 timestamp>" } // One-shot
{ "kind": "every", "everyMs": <ms> } // Recurring
{ "kind": "cron", "expr": "<cron-expr>" } // Cron expression
// Payload types
{ "kind": "systemEvent", "text": "<message>" } // Inject system event
{ "kind": "agentTurn", "message": "<prompt>" } // Run agent with message
// Delivery modes
{ "mode": "announce", "channel": "...", "to": "..." } // Deliver resultFile: /Users/ajsharp/code/github/openclaw/.agents/skills/PR_WORKFLOW.md
1. `review-pr` — review only, produce findings
2. `prepare-pr` — rebase, fix, gate, push to PR head branch
3. `merge-pr` — squash-merge, verify MERGED state, clean up
Skills execute workflow, maintainers provide judgment.
Always pause between skills to evaluate technical direction.System Prompt Integration:
Before replying: scan <available_skills>.
If exactly one skill clearly applies: read its SKILL.md, then follow it.
File: /Users/ajsharp/code/github/openclaw/skills/coding-agent/SKILL.md
# 1. Spawn Codex in background
bash pty:true workdir:~/project background:true \
command:"codex --yolo 'Build feature X. When done, run:
openclaw system event --text \"Done: Feature X\" --mode now'"
# 2. Agent works autonomously
# 3. On completion, triggers wake event
# 4. Main agent resumes and sees result- Cron job: Schedule next step
- Sub-agent spawn: Delegate to specialist
- System event: Inject task into session
- Delivery config: Route result to specific channel/user
What OpenClaw does:
- System prompt explicitly says "do not narrate routine, low-risk tool calls"
- Encourages sub-agent delegation for complex work
- Uses heartbeat prompts to check for pending work
For ConductorBot:
- Shape agent behavior through system prompts rather than question intercept logic
- Add role-specific prompts (PM, EM, Coder, QA) that guide autonomous behavior
- Include "when to escalate" guidelines in prompts
Implementation:
// claude-conductor/src/roles/pm-agent.yaml
systemPrompt: |
You are a Product Manager agent.
## Autonomy Guidelines
- DO NOT ask for clarification on standard PM tasks (writing specs, user stories)
- DO ask when technical feasibility is unclear
- DO ask when user preferences matter (UX decisions, priorities)
- Keep specs concise and actionable
## Tool Usage
- Call linear.createComment() without narration
- Use supervisor.escalate() only for critical decisionsWhat OpenClaw does:
// Every 30min:
1. Send heartbeat prompt to agent
2. If agent responds "HEARTBEAT_OK" → discard, no user notification
3. If agent has content → deliver to user
4. Check HEARTBEAT.md for pending tasksFor ConductorBot:
- Implement periodic polling with task queue
- Use Linear issue status changes as triggers
- Add HEARTBEAT.md pattern to workspace
Implementation:
// claude-conductor/src/daemon/heartbeat-poller.ts
export class HeartbeatPoller {
async poll() {
const tasks = await this.loadTaskQueue(); // From HEARTBEAT.md
for (const task of tasks) {
const result = await this.workflowEngine.execute({
workflow: task.workflow,
input: task.description,
});
if (result.status === "HEARTBEAT_OK") {
// Silent completion, no notification
continue;
}
// Deliver result to Linear/Slack
await this.commsAdapter.notify(result);
}
}
}What OpenClaw does:
Main Agent:
- Delegates work via sessions_spawn(task, label, model, timeout)
- Continues other work
- Receives announcement when sub-agent completes
Sub-agent:
- Isolated session with focused task
- Works autonomously
- Announces result back to parent
- Optionally auto-deleted after completionFor ConductorBot:
- Use for PM→EM→Coder→QA pipeline
- Each agent is a "sub-workflow" that announces completion
- Parent workflow waits for all sub-agents before proceeding
Decision: This maps well to ConductorBot's workflow engine. Instead of spawning sub-agents, use nested workflows with parallel execution.
What OpenClaw does:
// Agent can signal "nothing to say" without user notification
if (reply === "HEARTBEAT_OK" || reply === SILENT_REPLY_TOKEN) {
// Don't deliver to user
}For ConductorBot:
- Add
HEARTBEAT_OKtoken to agent responses - Supervisor checks for token before escalating to comms adapters
- Reduces noise in Linear comments and Slack channels
Implementation:
// claude-conductor/src/core/supervisor.ts
const SILENT_TOKENS = ["HEARTBEAT_OK", "SILENT_REPLY", "NO_ACTION_NEEDED"];
async intercept(toolCall: AskUserQuestionCall) {
const response = await this.aiProvider.complete({
prompt: `Should we escalate this question or can you answer it?
Question: ${toolCall.question}
If you can answer, provide answer.
If no action needed, respond with: HEARTBEAT_OK`,
});
if (SILENT_TOKENS.some(token => response.includes(token))) {
return { escalate: false, silentCompletion: true };
}
// ... continue with confidence check
}What OpenClaw does:
- SKILL.md files provide step-by-step instructions
- Agent reads relevant skill before starting work
- Skills encode best practices
For ConductorBot:
- Create SKILL.md files for each agent role
- Store in
claude-conductor/skills/directory - Include in role config
Implementation:
# claude-conductor/roles/pm-agent.yaml
role: pm-agent
skillFile: skills/pm-agent/SKILL.md
systemPrompt: |
Read ${skillFile} before starting work.
Follow the PM workflow template exactly.# skills/pm-agent/SKILL.md
## PM Agent Workflow
### Phase 1: Spec Writing
1. Read Linear issue description
2. Write detailed spec with:
- User stories
- Acceptance criteria
- Technical considerations
3. Post spec as Linear comment
### Phase 2: Clarifications
1. If spec is complete → respond with "HEARTBEAT_OK"
2. If you need technical input → escalate to EM agent
3. If you need user preference → escalate to human
### Phase 3: Handoff
1. Label issue "spec-complete"
2. Assign to EM agent
3. Respond with "HEARTBEAT_OK"What OpenClaw does:
// Schedule agent run
cron.add({
schedule: { kind: "cron", expr: "0 9 * * *" },
payload: { kind: "agentTurn", message: "Daily standup" },
delivery: { mode: "announce", channel: "slack", to: "#team" }
});For ConductorBot:
- Already implemented in daemon.ts!
- Use for polling Linear/GitHub triggers
- Add heartbeat polling to existing poll loop
Enhancement:
// claude-conductor/src/daemon/daemon.ts
async poll() {
// Existing: poll Linear for trigger events
const triggerEvents = await this.linearAdapter.pollTriggers();
// NEW: poll for heartbeat tasks
const heartbeatTasks = await this.loadHeartbeatTasks();
for (const task of [...triggerEvents, ...heartbeatTasks]) {
await this.workflowEngine.execute({
workflow: task.workflow,
input: task.description,
});
}
}┌─────────────────┐
│ Main Agent │
│ (in session) │
└────────┬────────┘
│
├─ Heartbeat poll (every 30min)
│ └─ HEARTBEAT.md task queue
│
├─ Sub-agent spawn
│ ├─ Sub-agent 1 (isolated session)
│ ├─ Sub-agent 2 (isolated session)
│ └─ Announcements back to main
│
└─ Cron jobs
└─ Scheduled agent turns
Key: Peer-to-peer coordination, no central orchestrator.
┌─────────────────┐
│ Daemon │
│ (polls Linear) │
└────────┬────────┘
│
├─ WorkflowEngine
│ ├─ Step 1 (sequential)
│ ├─ Step 2 (sequential)
│ └─ Step 3 (sequential)
│
└─ Supervisor
└─ Intercepts AskUserQuestion
└─ Escalates to comms adapter
Key: Centralized orchestrator with sequential steps.
┌─────────────────┐
│ Daemon │
│ + Heartbeat │
│ Poller │
└────────┬────────┘
│
├─ Poll Linear triggers
├─ Poll HEARTBEAT.md tasks
│
└─ WorkflowEngine
├─ Parallel steps (PM + EM)
│ ├─ Sub-workflow 1
│ └─ Sub-workflow 2
│
├─ Sequential steps (Coder → QA)
│
└─ Supervisor
├─ Check HEARTBEAT_OK token
├─ Answer with AI if confident
└─ Escalate to human if needed
Key: Hybrid approach - centralized orchestration with heartbeat polling and parallel execution.
Goal: Reduce noise, enable autonomous loops
-
Add silent token detection to Supervisor
// src/core/supervisor.ts const SILENT_TOKENS = ["HEARTBEAT_OK", "SILENT_REPLY"]; if (SILENT_TOKENS.some(token => response.includes(token))) { return { escalate: false, silentCompletion: true }; }
-
Add heartbeat poller to Daemon
// src/daemon/heartbeat-poller.ts export class HeartbeatPoller { async poll() { const tasks = await this.loadTaskQueue(); // HEARTBEAT.md for (const task of tasks) { await this.workflowEngine.execute(task); } } }
-
Create HEARTBEAT.md task queue format
# HEARTBEAT.md ## Pending Tasks - [ ] workflow: pm-review, input: "Review Linear issue XYZ-123" - [ ] workflow: qa-test, input: "Test PR #456"
Goal: Guide agents to work autonomously
-
Add systemPrompt to role configs
# roles/pm-agent.yaml systemPrompt: | You are a PM agent. DO NOT ask for clarification on standard PM tasks. DO ask when technical feasibility is unclear. Use HEARTBEAT_OK when no action needed.
-
Create skills directory
claude-conductor/skills/ ├── pm-agent/SKILL.md ├── em-agent/SKILL.md ├── coder-agent/SKILL.md └── qa-agent/SKILL.md -
Update ClaudeSdkProvider to inject system prompt
// src/providers/claude-sdk-provider.ts const systemPrompt = [ role.systemPrompt, role.skillFile ? await readFile(role.skillFile) : "", ].join("\n\n");
Goal: Enable concurrent agent work
-
Add parallel mode to workflow schema
steps: - id: review mode: parallel substeps: - id: pm-review agent: pm-agent - id: tech-review agent: em-agent
-
Update WorkflowEngine to handle parallel steps
// src/core/workflow-engine.ts if (step.mode === "parallel") { const results = await Promise.all( step.substeps.map(substep => this.executeStep(substep)) ); return this.aggregateResults(results); }
See openclaw-architecture-analysis.md for retry infrastructure plan.
System Prompts:
~/code/github/openclaw/src/agents/system-prompt.ts(main prompt builder, ~650 lines)~/code/github/openclaw/src/agents/pi-embedded-runner/system-prompt.ts(embedded runner)~/code/github/openclaw/src/agents/subagent-announce.ts(sub-agent prompt)
Agent Loop:
~/code/github/openclaw/src/agents/pi-embedded-runner/run.ts(main retry loop)~/code/github/openclaw/src/agents/cli-runner.ts(CLI agent runner)~/code/github/openclaw/src/agents/tools/agent-step.ts(multi-turn loops)
Autonomy Tools:
~/code/github/openclaw/src/agents/tools/sessions-spawn-tool.ts(sub-agent spawning)~/code/github/openclaw/src/agents/tools/cron-tool.ts(scheduled tasks)
Heartbeat System:
~/code/github/openclaw/src/auto-reply/heartbeat.ts(heartbeat prompts and token stripping)~/code/github/openclaw/src/auto-reply/tokens.ts(SILENT_REPLY_TOKEN, HEARTBEAT_TOKEN)
Workflows:
~/code/github/openclaw/.agents/skills/PR_WORKFLOW.md(multi-step PR workflow)~/code/github/openclaw/skills/coding-agent/SKILL.md(Codex/Claude Code orchestration)~/code/github/openclaw/skills/github/SKILL.md(GitHub operations)
Core:
claude-conductor/src/core/supervisor.ts(add silent token detection)claude-conductor/src/core/workflow-engine.ts(add parallel execution)claude-conductor/src/daemon/daemon.ts(add heartbeat polling)
Providers:
claude-conductor/src/providers/claude-sdk-provider.ts(inject system prompts)
New Files:
claude-conductor/src/daemon/heartbeat-poller.ts(heartbeat task queue)claude-conductor/skills/**/*.md(workflow templates)
OpenClaw achieves autonomous agent operation through:
- Prompt engineering → "Just do it, don't ask"
- Heartbeat polling → Check for work every 30min, silent acks
- Sub-agent delegation → Isolate complex work
- Skills as templates → Encode best practices
- Auto-retry loops → Self-heal errors
- Cron scheduling → Trigger work at intervals
No central supervisor needed - agents coordinate via sessions and delivery config, with heartbeat polls providing the "keep working" signal.
For ConductorBot: Adopt heartbeat polling, silent tokens, role-specific prompts, and parallel workflows. Skip sub-agent spawning (use nested workflows instead).