bus integration task

Overview

bus integration task connects bus task streams to worker execution. The worker claims bus.task.created, bus.task.approved, bus.task.assigned, and bus.task.reopened events for one recipient, publishes task progress, and closes or fails the task from the selected backend result.

The default local backend is codex-appserver, which runs Codex through the shared bus agent App Server integration. While the child Codex session runs, the task stream is the parent control plane: bus task say steers the active turn, approval requests are emitted as bus.task.approval.requested, and bus task approve sends decisions back to the pending app-server request. The container backend is still available for provider-neutral one-shot execution through bus containers. Completed or blocked App Server work can be reopened with bus task reopen; the worker resumes the stored Codex App Server thread when the task stream contains app_server_thread_id. Local Docker App Server workers default to Codex danger-full-access mode because the local image does not include Codex’s Linux workspace sandbox helper; the bridge still promotes only the recipient task worktree and marks the task blocked instead of done when the turn produces no diff or reports incomplete evidence.

Usage

Before starting this worker, run a Bus Events API and provide BUS_API_TOKEN with events:send, events:listen, task:read, task:send, task:reply, and task:claim. For the default --agent-backend codex-appserver, codex app-server and Codex authentication/configuration must be available in the worker environment. For --agent-backend container, also start a container integration that accepts the requested profile and grant container:run; local testing can use bus-integration-docker for the codex profile.

When bus task resolves a worker profile from bus-remote, the task stream carries non-secret labels such as worker_profile, requested model, reasoning effort, auth mode, and credential source kind. This bridge propagates those labels into worker-start requests, attempt evidence, lifecycle metrics, and status output while continuing to run Codex through bus-agent/appserver. API keys, ChatGPT auth files, and credential source refs stay in the configured worker or deployment secret source and are not serialized through task Events.

export BUS_API_TOKEN="<token-with-task-and-container-scopes>"
bus-integration-task \
  --events-url http://127.0.0.1:8081 \
  --recipient bus-dev \
  --agent-backend codex-appserver \
  --container-profile codex

--workspace-root makes the worker run tasks from the addressed module repository. With --workspace-root /workspace, a task addressed to @bus-dev runs from /workspace/bus-dev, while @bus-integration-docker runs from /workspace/bus-integration-docker.

Stack-managed workers keep claiming additional matching tasks for their recipient after each task completes, so parallel local work does not require manual restarts. The containers are still disposable: remove and recreate them freely because durable coordination is in Bus Events and task file changes are kept in the recipient-owned Git worktree and promoted commit path, not in container-local storage. Use --once and --idle-timeout only for temporary one-shot workers.

--workspace-recipient names the recipient that maps to --workspace-root itself instead of a child repository. In the BusDK local task stack this is configured as busdk, so tasks addressed to @busdk intentionally edit the superproject root while normal module tasks keep their own recipient repository.

--agent-backend codex-appserver keeps a running session addressable by task reference. Use bus task say <ref> <message...> while the turn is active to steer it directly. Use bus task reopen <ref> <message...> after a terminal state to publish a new open task event that resumes stored App Server thread state when present. Use bus task watch <ref> to see approval request ids, then answer with the task ref and approval request id printed by that watch output:

bus task approve <ref> <approval-id> accept_for_session

The approval decision must be accept, accept_for_session, decline, or cancel.

--worktree runs each task in an isolated Git worktree. The container receives the workspace as a read-only dependency view and receives write access only to the task worktree. When the workspace is a Git superproject with .gitmodules, the worker creates task-local dependency links so relative module dependencies resolve through the read-only workspace instead of becoming writable. After a successful task container run, the trusted bridge can stage and commit worktree changes with --commit, then promote the task branch back to the primary checkout with a conservative fast-forward merge. Dirty or non-fast-forward primary checkouts fail safely. The generated checkout leaf is unique per task, which avoids Git worktree metadata collisions for repeated or concurrent tasks addressed to the same module. For Codex App Server tasks, a completed LLM turn is not enough to mark work done: no worktree changes, no tests run, an unclosed PLAN item, or explicit blocked evidence is recorded as a blocked task stream. When a Codex worker uses auth_mode=openai-api-key, the worker can resolve a local deployment secret by reading /run/secrets/<ref> or $BUS_TASK_DEPLOYMENT_SECRETS_DIR/<ref> and setting OPENAI_API_KEY inside the worker process. The key value is never written to task Events or repo config, and an already-set OPENAI_API_KEY takes precedence. Workers also publish concise non-secret hourly engineering notes through bus notes add, so the notes become Bus ecosystem data rather than hidden local files. The logical agent_id is the recipient module/AGENTS.md identity, while task work_ref, worker/container metadata, and App Server ids identify a concrete run. Terminal app_server_closeout evidence includes the agent_id, AGENTS.md path, and Bus Notes IDs or query metadata so later review can group work by agent and inspect the notes for a specific attempt.

Lifecycle behavior is configured through a JSON policy file rather than hidden process-global modes. --policy-file / BUS_TASK_POLICY_FILE load the module-owned policy schema for worker count, one-worker-per-recipient behavior, disposable versus persistent workers, idle timeout, safe App Server crash retry, backend selection, isolated worktrees, read-only dependency worktrees, task-scoped App Server state, evidence requirements, cleanup retention, and progress log level. Explicit CLI flags and environment variables still override matching policy fields for one invocation. The local BusDK dev-task Compose stack mounts config/dev-task-lifecycle-policy.json, starts a local Notes API, and issues worker tokens with the dev-task, events, container, and Notes scopes needed for workers to publish bus notes evidence.

--command-json sets the command sent to the container as a JSON array. The worker expands {prompt}, {text}, {body}, {work_ref}, {recipient}, {module}, {main_repo_path}, {repo_path}, {worktree_path}, {branch}, {worktree_branch}, {base_branch}, and {create_branch} placeholders. {repo_path} points at the isolated worktree when --worktree is enabled. {branch} comes from task metadata and is defaulted by bus task start to the current Git branch when possible.

bus-integration-task \
  --events-url http://127.0.0.1:8081 \
  --recipient bus-dev \
  --agent-backend container \
  --container-profile codex \
  --workspace-root /workspace \
  --command-json '["codex","exec","--skip-git-repo-check","{prompt}"]'

Use --pre-command-json and --post-command-json for repository-local hooks around the main command. Use --commit for the normal local isolated-worktree path so task containers edit files only and the bridge handles Git metadata, staging, and commit creation:

bus-integration-task \
  --events-url http://127.0.0.1:8081 \
  --recipient bus-dev \
  --agent-backend container \
  --container-profile codex \
  --workspace-root /workspace \
  --worktree \
  --command-json '["codex","exec","--skip-git-repo-check","{prompt}"]' \
  --commit

Remote Git push is intentionally not part of the default isolated-worktree task container path because the task container does not receive writable Git metadata for the shared workspace. The default local compose setup commits locally in the bridge and does not push.

Local Compose

The BusDK superproject includes compose.yaml for local testing with Docker Desktop:

The default compose command for real local use runs the Codex App Server backend in an isolated worktree for the addressed module repository, then the bridge stages and commits successful changes before fast-forward promotion. The workspace remains the read-only dependency view, while the addressed repository worktree is the only writable mount for the task container. The same stack starts Bus Events, provider-neutral container routing, PostgreSQL, and the local Notes API so worker bus notes add calls are stored as Bus Notes data. This consumes ChatGPT-backed Codex quota. Smoke tests can override BUS_TASK_AGENT_BACKEND and BUS_TASK_COMMAND_JSON to use a deterministic command backend when the goal is plumbing verification rather than real LLM work.

BUS_TASK_AGENT_BACKEND=container \
BUS_TASK_COMMAND_JSON='["codex","--version"]' \
BUS_TASK_COMMIT=false \
docker compose -f compose.yaml --profile dev-task up --build -d
docker compose -f compose.yaml --profile dev-task exec testing-agent sh

Inside the testing shell, create a task and watch the task reference printed by the task new command:

bus task new --new-branch work/codex-version @bus-dev "Show the Codex CLI version."
bus task watch <printed-task-ref> --timeout 5m

Successful smoke output includes the container stdout from codex --version and a completed task status. A timeout or failed container run leaves the task with a failed status and prints the failure message in task watch.