Skip to content
This repository was archived by the owner on Jul 13, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 116 additions & 5 deletions docs/agents-and-monitoring.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
# Agents And Monitoring

This doc explains how Story Automator chooses child agents, builds child-session commands, and decides whether a tmux session is active, completed, stuck, or incomplete.
This doc explains how Story Automator chooses child agents, builds child-session commands, and decides whether a session is active, completed, stuck, or incomplete.

## Agent Model

There are two distinct agent layers:
There are three distinct agent layers:

- the orchestrator itself, which runs from a supported top-level agent session
- child sessions, which can run Claude or Codex depending on the agent plan
- child sessions, which run Claude or Codex via tmux (for those harnesses) or OpenCode via native task dispatch
- OpenCode harness uses native task tool dispatch — no tmux, no heartbeat polling

Agent selection is driven by:

- default primary and fallback values
- per-task overrides
- complexity-based overrides
- retro-specific rule: retrospective uses the configured retro agent
- harness type: determines whether child sessions are spawned via tmux or native task tool

## Harness Detection

The automator detects the active harness by checking the project root for harness-specific directories and markers:

| Harness | Detection Method |
|---------|-----------------|
| Claude | `.claude/` directory exists |
| Codex | `.codex/` directory exists |
| OpenCode | `.opencode/` directory exists |

When multiple harnesses are detected, the priority order is: OpenCode > Codex > Claude. The `BMAD_RUNTIME_PROVIDER` or `STORY_AUTOMATOR_RUNTIME_PROVIDER` environment variable can override this (set to `claude`, `codex`, or `opencode`).

## Agent Resolution

Expand All @@ -24,14 +38,17 @@ flowchart TD
B --> C["Generate deterministic agents file"]
C --> D["Resolve agent for story + task"]
D --> E{"Task type"}
E -->|create/dev/auto/review| F["Claude or Codex"]
E -->|create/dev/auto/review| F["Claude or Codex via tmux"]
E -->|retro| G["Configured retro agent"]
E -->|OpenCode harness| H["Native task tool dispatch"]
```

The generated agents file is a runtime artifact, not just display text.

## Child-Session Command Build

### Claude and Codex (tmux-based)

The helper CLI generates step-specific commands with `tmux-wrapper build-cmd`.

Examples:
Expand All @@ -49,7 +66,25 @@ Important behavior:
- long commands are written to `/tmp/sa-cmd-<session>.sh`
- review and retro prompts are assembled from resolved sibling skill/workflow files

## tmux Lifecycle
### OpenCode (native task tool)

When OpenCode is detected as the harness, the automator generates a JSON dispatch payload instead of a tmux command.

The orchestrating OpenCode agent reads this payload and calls its native `task` tool with the rendered prompt.

```

Key differences from tmux-based harnesses:

- no tmux session is spawned
- no heartbeat polling — tasks are fire-and-forget
- no output capture files — progress is visible in the task tool's streaming output
- completion is detected via the task tool's return value
- stop hooks are not installed — OpenCode uses native lifecycle (session.idle / process teardown)

## tmux Lifecycle (Claude and Codex)

This lifecycle applies only to Claude and Codex child sessions. OpenCode uses native dispatch (see above).

```mermaid
sequenceDiagram
Expand Down Expand Up @@ -77,6 +112,58 @@ Environment details:
- `AI_AGENT=<claude|codex>`
- Codex child sessions use isolated `CODEX_HOME` under `/tmp`

## OpenCode Native Lifecycle

OpenCode tasks are fire-and-forget. The automator generates a dispatch payload, and the orchestrating agent passes it to the native task tool.

```mermaid
sequenceDiagram
autonumber
participant O as Orchestrator
participant A as story-automator CLI
participant T as OpenCode Task Tool

O->>A: opencode-dispatch <step> <story_id>
A->>A: Render step prompt from policy
A->>A: Resolve model from config.yaml
A-->>O: JSON dispatch payload
O->>T: task(prompt=payload.prompt, subagent_type=payload.subagent_type)
T-->>O: Task result (streaming output visible)
O->>O: Verify completion via task return value
```

Important characteristics:

- no tmux session is created
- no heartbeat polling — the task tool handles lifecycle
- no output capture files — output streams through the task tool
- no stop hooks are installed — OpenCode manages process teardown
- the automator cannot kill or signal an OpenCode task — it must complete or fail on its own

## OpenCode Model Configuration

OpenCode supports per-step model overrides via `_bmad/bmm/config.yaml`:

```yaml
opencode:
models:
orchestrator: "" # global default (empty = opencode default)
create: "" # model for create-story step
dev: "" # model for dev-story step
auto: "" # model for qa-generate-e2e-tests step
review: "" # model for code-review step
retro: "" # model for retrospective step
subagent_type: "coder" # default subagent type for task tool
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Model resolution order:

1. `--model` CLI flag (explicit override)
2. `opencode.models.<step>` from config.yaml
3. Empty string = OpenCode uses its default model

If the config block is absent or all values are empty, OpenCode uses whatever model is configured in its own settings.

## Claude vs Codex

Python Story Automator does support Codex child sessions.
Expand All @@ -93,6 +180,8 @@ Important Codex-specific behavior:

## Monitoring States

### Claude and Codex

`monitor-session` polls helper status and collapses it into a small set of orchestration outcomes.

```mermaid
Expand All @@ -116,6 +205,16 @@ Important distinctions:
- `stuck` means no valid progress signal within the allowed window
- `incomplete` is a review-specific result, not a generic session state

### OpenCode

OpenCode tasks do not use tmux monitoring. The orchestrating agent tracks completion via:

- the task tool's return value (success/failure)
- the task tool's streaming output (visible in real time)
- verification of sprint status or story file after task completion

There is no heartbeat, no pane capture, and no crash recovery with auto-retry. If an OpenCode task fails, the orchestrating agent must decide whether to retry or escalate.

## Review Verification

Review sessions add extra verification:
Expand All @@ -125,8 +224,12 @@ Review sessions add extra verification:

This is what prevents false positives where a review session exits but the story was never marked done.

This applies to both tmux-based (Claude/Codex) and native (OpenCode) review tasks. The verification step is the same — only the dispatch mechanism differs.

## Output Files And Scratch Data

### Claude and Codex

During monitoring, the runtime may write:

- `/tmp/sa-<hash>-output-<session>.txt`
Expand All @@ -135,6 +238,10 @@ During monitoring, the runtime may write:

These are runtime scratch files. They are cleaned on normal session kill.

### OpenCode

OpenCode tasks do not produce tmux scratch files. Output is captured via the task tool's return value. The dispatch payload is ephemeral — it is read by the orchestrating agent and not persisted.

## Retry And Escalation

```mermaid
Expand All @@ -149,11 +256,15 @@ flowchart TD

Escalation is intentionally the last step, not the first response.

For OpenCode, retry logic is simpler — there is no tmux session to kill and respawn. The orchestrating agent re-dispatches via the task tool with the same or modified prompt.

## Practical Operator Notes

- if a child session looks done but review verification fails, treat it as incomplete, not complete
- if a long command is involved, the child may be running through a temp shell script rather than directly
- if monitor output is suspicious, re-check tmux and sprint-status directly
- OpenCode tasks are fire-and-forget — if a task appears stuck, check the task tool output, not tmux
- OpenCode cannot be killed by the automator — if a task hangs, the user must intervene at the OpenCode level

## Read Next

Expand Down
9 changes: 5 additions & 4 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Supported skill roots:
.agents/skills
.claude/skills
.codex/skills
.opencode/skills

If more than one supported root is complete, all complete roots are updated.

Expand Down Expand Up @@ -75,7 +76,7 @@ backup_legacy_story_automator_installs() {

wrapper_points_to_skill_tree() {
local shim="$1"
grep -Eq '\.(claude|agents|codex)/skills/' "$shim"
grep -Eq '\.(claude|agents|codex|opencode)/skills/' "$shim"
}

wrapper_points_to_legacy_target() {
Expand Down Expand Up @@ -175,7 +176,7 @@ skill_root_has_any_required_asset() {

collect_target_skills_roots() {
local candidate
local candidates=(".agents/skills" ".claude/skills" ".codex/skills")
local candidates=(".agents/skills" ".claude/skills" ".codex/skills" ".opencode/skills")

for candidate in "${candidates[@]}"; do
if skill_root_has_required_entrypoints "$candidate"; then
Expand All @@ -187,7 +188,7 @@ collect_target_skills_roots() {
select_single_incomplete_diagnostic_root() {
local candidate
local found=""
local candidates=(".agents/skills" ".claude/skills" ".codex/skills")
local candidates=(".agents/skills" ".claude/skills" ".codex/skills" ".opencode/skills")

for candidate in "${candidates[@]}"; do
if skill_root_has_any_required_asset "$candidate"; then
Expand Down Expand Up @@ -282,7 +283,7 @@ if [ "${#TARGET_SKILLS_RELS[@]}" -eq 0 ]; then
resolve_required_skill "bmad-dev-story" >/dev/null
resolve_required_skill "bmad-retrospective" >/dev/null
fi
err "Required dependency skills not found under any supported skill root (.agents/skills, .claude/skills, .codex/skills). Install bmad-create-story, bmad-dev-story, and bmad-retrospective under at least one supported root before running this installer."
err "Required dependency skills not found under any supported skill root (.agents/skills, .claude/skills, .codex/skills, .opencode/skills). Install bmad-create-story, bmad-dev-story, and bmad-retrospective under at least one supported root before running this installer."
fi

backup_legacy_story_automator_installs
Expand Down
3 changes: 3 additions & 0 deletions skills/bmad-story-automator/src/story_automator/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
cmd_stop_hook,
)
from .commands.orchestrator import cmd_orchestrator_helper
from .commands.opencode_dispatch import cmd_opencode_dispatch
from .commands.state import cmd_build_state_doc, cmd_sprint_compare, cmd_state_metrics, cmd_validate_state
from .commands.tmux import cmd_codex_status_check, cmd_heartbeat_check, cmd_monitor_session, cmd_tmux_status_check, cmd_tmux_wrapper
from .commands.validate_story_creation import cmd_validate_story_creation
Expand Down Expand Up @@ -55,6 +56,7 @@ def main(argv: list[str] | None = None) -> int:
"tmux-status-check": cmd_tmux_status_check,
"monitor-session": cmd_monitor_session,
"orchestrator-helper": cmd_orchestrator_helper,
"opencode-dispatch": cmd_opencode_dispatch,
"agent-config": cmd_agent_config,
}
handler = commands.get(command)
Expand Down Expand Up @@ -91,6 +93,7 @@ def _usage(stream: object) -> None:
"tmux-status-check",
"monitor-session",
"orchestrator-helper",
"opencode-dispatch",
"agent-config",
):
print(f" {name}", file=stream)
Expand Down
Loading