Skip to content

RFC: workflow orchestration over the control plane (Connect as primary, openab-pty hosts as workers) #77

Open

Description

@chaodu-agent

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

  1. CP worker in openab-pty. A PromptRunner that runs one dedicated non-interactive CLI session per delegation in the host's workspace. Result = captured output, ANSI-stripped and capped below max_result_bytes. It does not type into the user's live PTY. The runtime client itself is not on openabdev/openab main yet (feat(cp): land Agent Control Plane v1 on main openab#1528).
  2. CP primary client for OpenAB Connect. Either a native Swift client of the wire protocol, or a local openab run primary driven through openab 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).
  3. Scheduler. CP only does single delegations. repeat, parallel, joinPolicy, stopCondition and onMaxIterations need a scheduler that issues delegations and evaluates conditions.
  4. Artifacts between steps on different hosts. Text results are capped. Files move through git (branch per workflow run), not through CP. stopCondition.fileCheck must 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 parallel fan-out gets SATURATED; 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-tools is 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 to namespace/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/event unless the namespace sets metadata_only = true.

Client-side requirements (OpenAB Connect)

  1. Create and manage a CP. Deploy openab-cp on a host the client manages, and mint and revoke per-worker auth_keys bound to namespace/name. Today there is no packaged image or release for openab-cp: docs/control-plane.md says "there is no packaged container image yet", and no openab-cp release 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.
  2. 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 classic openab container (FR: colocated classic openab alongside openab-pty (shared /workspace) #62) carrying a [control_plane] worker section: main has no [control_plane] config at all; the branch adds ControlPlaneConfig. 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).
  3. Inventory. List the workers on a CP with runtime labels and liveness. cp/list_agents exists on main (any registered client, own namespace) and returns labels and session counts; liveness = present in the registry (removed on disconnect or 45 s lease expiry), plus agent_registered / agent_deregistered events for observers. There is no last-heartbeat timestamp in the listing. openab agent list exists 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 run worker next to openab-pty on two hosts, plus a primary driven by openab 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 PromptRunner in 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 list from the primary shows both with labels.
  • openab agent spawn --label cli=kiro --prompt … --deadline-secs 300 returns completed with output from one host.
  • Two parallel spawns land on different hosts (least-loaded selection).
  • Stopping one host fails its in-flight delegation as target_disconnected within 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 serves cp/delegate with 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/cancel and deadline expiry kill the session's process group.
  • max_delegated_sessions is enforced; extra delegations are refused, not queued.
  • The section is off by default and is not rendered by default in the K8s chart.

Related

No existing issue proposes workflow orchestration or a peer agent type.

Activity

  1. chaodu-agent commented on Oct 29, 2026

    @chaodu-agent
    CollaboratorAuthor

    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 on main. Phase 0 therefore needs either a build from that branch or a new PR that lands the runtime half first. Tracking that dependency here.

  2. added
    p3Priority 3: RFC, roadmap, nice-to-have or deferred
    on Oct 30, 2026
  3. chaodu-agent commented on Oct 31, 2026

    @chaodu-agent
    CollaboratorAuthor

    Decisions recorded:

    1. Scheduler placement: option A, inside OpenAB Connect. No control-plane change. Trade-off accepted: scheduled work stops while the Mac sleeps.
    2. Phase 0: open a new PR that lands the runtime half first, rather than building from the closed feat(cp): land Agent Control Plane v1 on main openab#1528 branch.

    Next step: the new Phase 0 PR is not started yet and needs its own scope confirmation.

  4. chaodu-agent commented on Oct 31, 2026

    @chaodu-agent
    CollaboratorAuthor

    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| CP
    
    Loading

    Solid arrows are dispatch and results. Dashed arrows are worker registration and heartbeat. Connect is not part of Phase 0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestp3Priority 3: RFC, roadmap, nice-to-have or deferred

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions