PABLO
Docs
Documentation home

Getting started

  • What is PABLO?
  • Quickstart
  • Installation
  • Your first project
  • Your first task

Guides

  • Managing tasks
  • Dashboard
  • Analytics
  • Backups & restore
  • Troubleshooting

Concepts

  • How it works
  • Task states
  • Agents
  • Sync & rebase

Reference

  • Commands
  • Configuration
  • Providers
  • Storage

Contributing

  • Development

Concepts

How it works

PABLO has two layers: the commands you run, and a background job that runs every 5 minutes, checks each project and does whatever is due. Agents run through your agent backend, and everything else is plain, deterministic code.

Two layers#

Interactive
What you run: the pablo commands, the /pablo-commit-and-pr command (OpenCode, Claude Code or Copilot), and the agent sessions you open in Orca or OpenChamber.
Background
A scheduler (systemd user timer on Linux, launchd agent on macOS) runs pablo system:dispatch every 5 minutes.

The background job#

On each run, the dispatcher:

  1. takes a global lock, so two runs never overlap (a second one exits immediately);
  2. checks the tools it needs. A problem with gh, with the agent runtime's command (opencode, claude or copilot), or an impossible agent setup (OpenChamber with Claude Code or Copilot) stops the run; a problem with a tracker tool or the agent backend only produces a warning, and only the projects using that tool are affected;
  3. for each project, runs the jobs whose interval has elapsed since their last success.
JobWhat it doesInterval settingDefault
pollchecks every task's pull request and moves it through the state machinestate_polling.interval_minutes10 min
syncrebases or merges task branches onto the primary branch (details)sync.interval_minutes30 min (the installed config.yaml sets 720, every 12 h)
mainfast-forwards the project's primary checkout (details)main_update.interval_minutes60 min

A job's “last run” time is only saved when it succeeds, so a failure is retried on the next run. One project failing never blocks the others. Adding or removing a project changes nothing in the scheduler: each project's cadence lives in its YAML file.

Because the dispatcher wakes up every 5 minutes, a job runs at the first wake-up after its interval has elapsed, not at the exact minute.

Agent backends#

PABLO doesn't run agents itself. It hands them to an agent backend, exactly the way you would, and asks it which agents are busy.

The backend decides where an agent runs; the agent runtime (agent_runtime) decides what runs there: opencode run --agent <agent> '<prompt>' by default, or claude -p --agents ~/.pablo/claude/agents.json --agent <agent> --session-id <id> … -- '<prompt>' with Claude Code, or copilot --agent pablo-<agent> --session-id <uuid> … -p '<prompt>' with Copilot. Orca and the headless fallback run either; OpenChamber only runs OpenCode.

Orca (default)#

bash
# launch
orca terminal create --worktree path:<worktree> --title "pablo:<agent>" \
     --command "cd <worktree>; opencode run --agent <agent> '<prompt>'" --json
# wait for the end of a run
orca terminal wait --terminal <handle> --for exit --timeout-ms 3600000 --json
# who is working
orca worktree ps --limit 200 --json

An agent that is working or running counts as 🏃 busy; anything else (idle, waiting for input) counts as 💭 waiting for you. Orca only knows repositories registered with orca repo add. Under Claude Code the terminal runs claude -p instead (copilot -p under Copilot), and Orca follows it through the hooks it installs in your Claude Code settings, as it does for your own sessions.

OpenChamber#

Select it with agent_backend: openchamber in ~/.pablo/config.yaml or PABLO_AGENT_BACKEND=openchamber.

bash
openchamber session create --dir <worktree> --title "pablo:<agent>"
openchamber session send --session <id> --dir <worktree> --prompt "<prompt>" --agent <agent>
openchamber session messages --session <id> --dir <worktree> --wait
openchamber session list --dir <worktree> --with-status

OpenChamber works from directories: no repository to register, no waiting for a workspace to appear. busy and retry count as 🏃, idle as 💭. If sending the prompt fails, PABLO logs the reason to ~/.pablo/agents/logs/openchamber-fallback.log and falls back to a headless run with the same model.

Headless fallback#

When Orca is unreachable or doesn't know the repository, PABLO runs the runtime's command (opencode run --agent <agent> claude -p … --agent <agent> or copilot --agent pablo-<agent> … -p) in the background from the worktree, tracked with pid files in ~/.pablo/agents/. Headless runs always count as busy (🏃) while they run, since they can't report being idle. Project startup scripts and the rebase-conflict resolver always use this path.

What PABLO may change#

Agents propose; you decide. PABLO's own code only performs these operations, each under strict rules:

OperationWhen
Create a branch and a worktreepablo task:start
Commit, push, open a draft pull requestonly through /pablo-commit-and-pr, which you run
Switch a pull request between draft and readyCI turns green (ready); changes requested or QA failure (back to draft); task:retrigger-ci (draft)
Rebase or merge, then push with --force-with-leasesync, only with auto_apply or --apply; never on a dirty worktree; never plain --force
Resolve a rebase conflictnever in code: the conflict resolver agent does it
Delete a worktreewhen the pull request is merged, or pablo task:close
checkout + reset --hard the primary checkoutonly when it is clean and has no local commits (rules)

It never merges or closes a pull request, never comments or reviews, and never changes a ticket.

Design decisions#

Command-line tools, no tokens#

PABLO talks to every service through its own command-line tool, already logged in on your machine. There's no API key in its configuration and none should be added. When a tool isn't ready, PABLO shows that tool's own error. See Providers.

One timer for all projects#

A single 5-minute dispatcher reads each project's intervals from its YAML file, instead of one scheduler unit per project. Adding a project is just adding a file.

A state machine, not an agent framework#

The states are the ones a pull request goes through in a real team. Each state, its display and what runs on entering it live in one table in StateMachine.php; what the poller checks for each state lives in one table in Poller.php. The same entry handler is used by the poller, the commands and manual overrides, so the behaviour never diverges.

Why not OpenClaw?#

OpenClaw's scheduled “heartbeat” would map well onto the background job, but it was ruled out: it's much heavier than needed (a persistent self-hosted gateway with messaging integrations versus a timer calling a few tools); its skills run with full privileges and no sandbox; and skills from its community registry have no integrity verification, a known supply-chain risk. On top of a working OpenCode setup, that wasn't worth it.

PreviousTroubleshooting NextTask states

Edit this page on GitHub

On this page

  • Two layers
  • The background job
  • Agent backends
  • Orca (default)
  • OpenChamber
  • Headless fallback
  • What PABLO may change
  • Design decisions
  • Command-line tools, no tokens
  • One timer for all projects
  • A state machine, not an agent framework
  • Why not OpenClaw?
PABLO

MIT licensed · made by Baptiste Leduc · GitHub · Docs