Repository navigation
Description
Description
Problem. An agent can already write a multi-step plan as a workflow graph: step nodes (id, agent, prompt, artifacts), repeat nodes (maxIterations, onMaxIterations, stopCondition with fileCheck / jsonPath / value), and parallel nodes (joinPolicy), the same shape as Kiro workflow JSON. Today nothing in OpenAB can run such a graph across several hosts. Every openab-pty host can already run a one-shot, non-interactive prompt (for example kiro-cli chat --no-interactive --trust-all-tools --output-format stream-json), but there is no shared view of which hosts are free and no way to hand one of them a step.
Goal. Let OpenAB Connect (the macOS client) run a workflow by assigning each step to an openab-pty host through openab-cp. Connect talks to one control plane, not to every host.
OpenAB Connect (primary) openab-cp openab-pty hosts (workers)
workflow graph ── cp/delegate ──► router ── fwd ──► one-shot CLI run per step
scheduler ◄─ delegate_result ─ ◄─ result ─ (dial out, register + heartbeat)
This RFC asks for agreement on the shape and on one open decision (where the scheduler lives). It does not propose code yet.
Use Case
A user asks an agent in OpenAB Connect for a change that needs several steps: research, implement on two hosts in parallel, review, and repeat until a check file says pass. The agent emits the workflow graph. The user approves the whole graph once. Connect then places each step on a registered worker with the right CLI, collects results, evaluates loop and join conditions, and shows progress. Hosts behind NAT need no inbound port.
Current state on main (verified)
CP code lives in openabdev/openab; paths in this table are in that repo unless noted. Verified against openabdev/openab main at 02a8b24. Only the CP server is on main. The runtime half (OAB [control_plane] client, PromptRunner, serve_local, openab agent … CLI) lives on branch feat/cp-runtime-client-main from openabdev/openab#1528, which was auto-closed unmerged. Items marked (branch) below are verified there, not on main.
| Fact | Where |
|---|---|
Methods cp/register, cp/heartbeat, cp/delegate, cp/delegate_result, cp/cancel, cp/event, cp/list_agents |
crates/openab-cp/src/proto.rs L638-L648 |
Agent types primary, worker, observer |
proto.rs L199-L206 |
cp/register carries labels (string map) and max_delegated_sessions (default 1) |
proto.rs L227-L245 |
Target = exact name or labels (all pairs must match); least-loaded first for label selection |
proto.rs L276-L281, registry.rs L239-L241, L364-L412 |
cp/delegate = delegation_id + target + prompt + absolute deadline (mandatory) |
proto.rs L294-L329 |
cp/delegate_result statuses: completed, failed, timeout, cancelled, target_disconnected |
proto.rs L406-L435 |
Workers may not initiate unless the namespace sets allow_worker_initiation = true (default false) |
policy.rs L93-L95, config.rs L232-L254 |
Deadline cap: CP rejects deadlines beyond max_deadline_secs, default 1800 s |
policy.rs L120-L128, config.rs L166-L168 |
Result cap: max_result_bytes default 256 KiB, truncated with a marker, not rejected |
config.rs L44-L48, L169-L171 |
| Heartbeat 15 s, lease 45 s; missed lease deregisters and fails in-flight delegations | config.rs L30-L37, L160-L165 |
Per-target concurrency clamp default 16; process-wide in-flight ceiling 4096 (SATURATED) |
config.rs L123-L144 |
One auth key per identity, bound to immutable namespace/name/type |
config.rs L207-L221 |
cp/list_agents: namespace-scoped list of name, type, instance id, labels, active / max sessions |
proto.rs L617-L635, server.rs L932 |
(branch) A runtime registered as primary answers cp/delegate with POLICY_DENIED |
crates/openab-core/src/control_plane/client.rs L786-L795 |
(branch) PromptRunner trait: run(session_key, forward), cancel, discard |
control_plane/executor.rs L74-L83 |
(branch) serve_local local socket API |
control_plane/local.rs L259 |
(branch) local API and CLI accept deadline_secs 1..=1800 |
local.rs L749, src/main.rs L278 |
| (branch) ≤256 requests outstanding to the CP per primary; unanswered request dropped after 60 s; non-terminal delegation dropped 60 s past its deadline | control_plane/primary.rs L364-L376 |
(branch) openab agent spawn --target/--label --prompt --deadline-secs [--async], list, status, cancel |
src/main.rs L157-L186 |
openab-pty has no CP client: no CP references in runtime/src, no WebSocket client dependency outside dev-dependencies |
this repo, main at 7f46387, runtime/Cargo.toml |
Permalinks:
https://github.com/openabdev/openab/blob/02a8b24378e1e2dc1d5e2861525d21a05693d170/crates/openab-cp/src/proto.rs#L638-L648
https://github.com/openabdev/openab/blob/02a8b24378e1e2dc1d5e2861525d21a05693d170/crates/openab-cp/src/policy.rs#L88-L135
https://github.com/openabdev/openab/blob/91c0a89f7637eb47cbd78d470df01a53e81a13bf/crates/openab-core/src/control_plane/executor.rs#L74-L83
Note: on main the "primary refuses cp/delegate" rule is a runtime-side check only. The CP registry excludes observers from selection but not primaries (registry.rs L382), so a label selector that matches a primary can route to it. Worker labels should not overlap primary labels.
Mapping
| Workflow concept | CP primitive |
|---|---|
| Host availability | worker cp/register + cp/heartbeat; labels such as runtime=pty, cli=kiro |
| Inventory | cp/list_agents |
| Assign a step | cp/delegate with target (name or labels), prompt, deadline |
| Step result | cp/delegate_result (status, result, error) |
| Abort a step | cp/cancel |
| One workflow step | one delegation |
The one-way type rule fits this design: Connect only initiates (primary), hosts only serve (worker). No peer type is needed here.
Why CP instead of Connect dialing each host
- Workers dial out to the CP. Hosts need no inbound port.
- Connect holds one CP key instead of every host's admin credential.
- Placement, load, liveness and audit (
cp/event) live in one place.
Missing pieces
- CP worker in openab-pty. A
PromptRunnerthat runs one dedicated non-interactive CLI session per delegation in the host's workspace. Result = captured output, ANSI-stripped and capped belowmax_result_bytes. It does not type into the user's live PTY. The runtime client itself is not on openabdev/openabmainyet (feat(cp): land Agent Control Plane v1 on main openab#1528). - CP primary client for OpenAB Connect. Either a native Swift client of the wire protocol, or a local
openab runprimary driven throughopenab agent spawn(both need feat(cp): land Agent Control Plane v1 on main openab#1528's work to land first, or the protocol to be reimplemented). - Scheduler. CP only does single delegations.
repeat,parallel,joinPolicy,stopConditionandonMaxIterationsneed a scheduler that issues delegations and evaluates conditions. - Artifacts between steps on different hosts. Text results are capped. Files move through git (branch per workflow run), not through CP.
stopCondition.fileCheckmust read from that shared repo, not a host-local path.
Open decision: where the scheduler lives (not decided here)
| A. In OpenAB Connect | B. Beside or inside openab-cp | |
|---|---|---|
| Effort | Smallest; no CP change | CP (or a sibling service) grows a workflow feature |
| Durability | Stops when the Mac sleeps or the app quits | Survives client sleep and quit |
| State | Needs local resumable state (run id, node states, delegation ids) | State held server-side |
| Client role | Plans, approves, runs, watches | Submits, approves, watches |
| Trust | Workflow never leaves the user's Mac | CP sees whole graphs and must enforce the approval |
| Reuse | Connect only | Any primary (Discord bots, CLI) could submit |
Limits
- Per-delegation deadline cap (default 1800 s on the CP; CLI 1..=1800 on the branch). Steps longer than 30 minutes do not fit and must be split.
- Outstanding cap: ≤256 requests per primary (branch); per-target clamp (default 16) and process-wide 4096 in-flight on the CP. Large
parallelfan-out getsSATURATED; the scheduler must queue. - Result size cap: 256 KiB default, truncated. Large outputs go through git.
- No durable CP state: registry and in-flight table are in memory. A CP restart fails in-flight steps.
Security
- A worker running with
--trust-all-toolsis shell-equivalent on its host. Anyone who can delegate to it can run commands there. - Agent-generated workflows must be approved by a human as a whole graph before any step runs, so injected text in one step's output cannot add steps or change targets. Re-approval if the graph changes.
- Per-worker
auth_key, bound tonamespace/name, so one host can be revoked alone. - Which labels a primary may target is not policy today (namespace-scoped only). Proposal: an allowlist per primary identity, evaluated by the CP.
- Step prompts and results appear as excerpts in
cp/eventunless the namespace setsmetadata_only = true.
Client-side requirements (OpenAB Connect)
- Create and manage a CP. Deploy
openab-cpon a host the client manages, and mint and revoke per-workerauth_keys bound tonamespace/name. Today there is no packaged image or release foropenab-cp:docs/control-plane.mdsays "there is no packaged container image yet", and noopenab-cprelease tag exists; it builds from source (cargo run -p openab-cp). Keys are[[agents]]entries in CP config; whether the CP reloads them without a restart is unverified. - Bind a host to an existing CP at creation. The client passes the CP URL and a per-host key as a secret; the host registers on start with labels such as
runtime=pty,cli=kiro. This needs a CP worker in or beside openab-pty (missing piece 1). A colocated classicopenabcontainer (FR: colocated classic openab alongside openab-pty (shared /workspace) #62) carrying a[control_plane]worker section:mainhas no[control_plane]config at all; the branch addsControlPlaneConfig. Whether that worker can run next to a Discord adapter in the same process is unverified (the branch PR feat(cp): OAB runtime CP client — [control_plane] config, worker serving, headless mode (PR 3/4) openab#1471 describes a headless mode). - Inventory. List the workers on a CP with runtime labels and liveness.
cp/list_agentsexists onmain(any registered client, own namespace) and returns labels and session counts; liveness = present in the registry (removed on disconnect or 45 s lease expiry), plusagent_registered/agent_deregisteredevents for observers. There is no last-heartbeat timestamp in the listing.openab agent listexists only on the branch.
These three are necessary but not sufficient. Running a workflow also needs: the client acting as a CP primary, a workflow approval view, the scheduler (placement still open), and progress and result views.
Phasing
- Phase 0: validate with a sidecar
openab runworker next to openab-pty on two hosts, plus a primary driven byopenab agent spawn. No code change, but needs a build of the feat(cp): land Agent Control Plane v1 on main openab#1528 branch. - Phase 1: embedded worker
PromptRunnerin openab-pty (dedicated non-interactive session per delegation). - Phase 2: OpenAB Connect primary plus scheduler (option A, or a thin client of B).
- Phase 3: durable scheduler, only if option B is chosen.
Acceptance criteria
Phase 0
- Two hosts register as workers with distinct labels;
openab agent listfrom the primary shows both with labels. openab agent spawn --label cli=kiro --prompt … --deadline-secs 300returnscompletedwith output from one host.- Two parallel spawns land on different hosts (least-loaded selection).
- Stopping one host fails its in-flight delegation as
target_disconnectedwithin the lease window. - A worker identity cannot initiate (
POLICY_DENIED).
Phase 1
- openab-pty with an opt-in
[control_plane]section registers as a worker, heartbeats, and servescp/delegatewith no sidecar. - Each delegation runs in its own non-interactive session; the user's live PTY is not typed into.
- Results are ANSI-stripped and capped below the CP's
max_result_bytes; over-long output is truncated with a marker. cp/canceland deadline expiry kill the session's process group.max_delegated_sessionsis enforced; extra delegations are refused, not queued.- The section is off by default and is not rendered by default in the K8s chart.
Related
- docs(adr): agent control plane for direct inter-agent delegation openab#1465 ADR: agent control plane (merged)
- feat(cp): openab-cp control plane — registry, router, policy (PR 1/4) openab#1469, feat(cp): observer/lobby — 3-phase design + Phase 1 protocol scaffold (PR 2/4) openab#1470 CP server and observer/lobby (merged to
main) - feat(cp): OAB runtime CP client — [control_plane] config, worker serving, headless mode (PR 3/4) openab#1471, feat(cp): add primary delegation MCP tools and CLI openab#1529 runtime client, primary MCP tools and CLI (merged into feature branches, not
main) - feat(cp): land Agent Control Plane v1 on main openab#1528 CP v1 roll-up to
main(closed unmerged) - refactor(cp): post-v1 hardening follow-ups from #1528 self-review openab#1530 post-v1 hardening follow-ups, feat(cp): scale hardening — indexed sweeps, sharded admission, borrowed lookup keys openab#1473 scale hardening
- FR: colocated classic openab alongside openab-pty (shared /workspace) #62 colocated classic openab beside openab-pty
No existing issue proposes workflow orchestration or a peer agent type.
Activity
chaodu-agent commentedon Oct 29, 2026
Status note: openabdev/openab#1528 (the CP runtime half: client, primary, worker, local API and
openab agent spawn) is closed without merge (last updated 2026-09-13, changes requested). Only the CP server is onmain. Phase 0 therefore needs either a build from that branch or a new PR that lands the runtime half first. Tracking that dependency here.refactor(cp): make openab-cp proto usable without the server (#1588)
chaodu-agent commentedon Oct 31, 2026
Decisions recorded:
Next step: the new Phase 0 PR is not started yet and needs its own scope confirmation.
chaodu-agent commentedon Oct 31, 2026
Phase 0 architecture (Mermaid)
flowchart LR Connect["OpenAB Connect (primary, not in Phase 0)"] CLI["openab agent spawn (local primary)"] CP["openab-cp server on main"] W1["Host A: openab run worker + openab-pty, label cli=kiro"] W2["Host B: openab run worker + openab-pty, label cli=kiro"] CLI -->|cp/delegate| CP CP -->|cp/delegate| W1 CP -->|cp/delegate| W2 W1 -->|cp/delegate_result| CP W2 -->|cp/delegate_result| CP CP -->|result| CLI W1 -.->|cp/register and heartbeat| CP W2 -.->|cp/register and heartbeat| CPSolid arrows are dispatch and results. Dashed arrows are worker registration and heartbeat. Connect is not part of Phase 0.