Workflow in workflow #9

Open
opened 2026-07-10 04:19:13 +08:00 by weiwen · 1 comment
Owner

I want a planner workflow that will read issues from the tracker, pick an issue, then run implement-issue or explore-issue on it, and repeat.

This will require being able to run workflows within workflows. Explore how this will affect the code, UX, and the fullscreen UI.

I want a planner workflow that will read issues from the tracker, pick an issue, then run `implement-issue` or `explore-issue` on it, and repeat. This will require being able to run workflows within workflows. Explore how this will affect the code, UX, and the fullscreen UI.
Author
Owner

Effort Estimate

Medium-large: ~2–4 weeks for one developer familiar with the codebase.

Breakdown:

  • Issue listing API (issues.list): ~1–2 days
  • Sub-workflow invocation mechanism (sdk.run or import convention): ~3–5 days
  • Nested reporter / TUI support: ~3–5 days
  • Planner workflow preset: ~2–3 days
  • Testing: ~2–3 days

Most Relevant Files

File Relevance
packages/niwa/index.ts Core types: NiwaSDK needs run() method; Issues needs list(). The RunView / reporter types may need nesting support.
src/run.ts The run spine — module resolution, SDK construction, invoke, cleanup. Must be refactored to expose a reusable resolveAndInvoke() for sub-workflow calls.
src/sdk.ts createRunSdk builds the NiwaSDK. Adding sdk.run() here. Also makeWorkspace — sub-workflows share the parent's runner/reporter.
src/issues.ts Host-side issue tracker. Needs list() method with provider-specific CLI args (gh issue list, tea issues).
src/run-reporter.ts RunReporter interface. May need a subworkflow event for nesting.
src/tui/reporter.ts FullscreenReporter — single RunView, one agent pane. Needs to handle nested agent sequences from sub-workflows.
src/tui/view.ts RunView layout — single-level agents list. Needs hierarchical or stacked view model.
src/presets/content/implement-issue.ts Model for how workflows compose sub-modules. The planner workflow will follow this pattern.
src/presets/content/explore-issue.ts Same as above; read-only workflow for triage.
src/cli.ts CLI dispatch — minor changes if sdk.run needs process-level isolation.
src/config.ts Config types — possible additions for planner configuration.
docs/design/architecture.md Architecture doc — would need updates to describe nested workflow composition.

Claims Analysis

Claim Status Detail
"read issues from the tracker" Partial niwa.issues.get(id) exists for a single issue by ID. Missing: No list() or search API to enumerate open issues — a blocker for "read issues from the tracker."
"pick an issue" Not implemented No selection logic exists. A planner workflow would need either an LLM decision step or a deterministic strategy (oldest-first, priority).
"run implement-issue or explore-issue on it" Partial Both workflows exist as presets. Missing: No mechanism to invoke a workflow from within a workflow — only Roles are composed this way (via direct module import + calling .run()).
"repeat" Not implemented No looping construct or state persistence in the SDK. A planner would need its own loop + state tracking.
"run workflows within workflows" Not implemented Architecture doc describes Workflows composing Roles, but no nested Workflow-in-Workflow mechanism exists.
"affect code, UX, and fullscreen UI" Accurate The task correctly identifies the three impact areas. Changes are needed in all three.

Open Questions

  1. Issue listing API shape: Should issues.list() return all open issues, or accept filters (state, label, milestone)? Pagination needed? Minimum viable for an LLM-based planner?
  2. Selection strategy: How does the planner "pick" an issue? LLM decision (explore each then decide)? Oldest-first? Priority-tagged? The strategy drives the planner workflow's structure.
  3. Invocation mechanism: Import-as-module (import impl from './implement-issue'; impl.run(sdk, id)) or sdk.run('implement-issue', id)? The import approach follows existing patterns (Roles are composed this way in implement-issue.ts) but limits flexibility. An SDK-level run() enables runtime-dynamic dispatch but requires significant refactoring of run.ts.
  4. Reporter architecture for nesting: How should the TUI handle sub-workflow agents?
    • (a) Flatten all agents into one sequential view — simplest, loses nesting context
    • (b) Stack/tab approach — child workflow gets its own view context, parent minimized
    • (c) Drill-down — view switches to child workflow, parent shown as breadcrumb
  5. Reporter ownership: Does the sub-workflow share the parent's RunReporter or create a sub-reporter? The plain/rail reporters are stateless and share fine; the FullscreenReporter owns the terminal and can only have one active.
  6. SDK lifecycle: Does sdk.run() share the parent's SDK (same workspace tracker, same disposal) or create a child SDK? If shared, workspace releases from both parent and child merge correctly. If separate, wiring the reporter and runner through is complex.
  7. Error handling semantics: If explore-issue fails on one issue, does the planner abort, skip to the next, or retry? What about partial failures in implement-issue (e.g., implement passes but review fails)?
  8. Termination condition: When does the planner stop? When no more open issues? After N successful runs? LLM decides? Based on available workspaces in the tatami pool?
  9. State persistence: Should the planner record processed issues between runs (e.g., in .niwa/planner-state.json)? Without this, every run would re-process old issues.
  10. Loop detection: Should there be protection against infinite recursion (workflow calling itself directly or transitively)?
  11. Parallelism: Should the planner run multiple sub-workflows concurrently on different workspaces? The tatami pool size constrains this.
  12. Config surface: Does the planner need configuration (which workflows to run, issue filters, concurrency, order)? Would live in .niwa/config.json or a dedicated .niwa/planner.json?
  13. Preset vs user-authored: Is planner a shipped preset (embedded in binary) or only a demonstration pattern users implement themselves?

Suggested Approach

Phase 1 — Core plumbing (prerequisites)

  1. Add issues.list() to packages/niwa/index.ts (type), src/issues.ts (implementation with gh issue list --json number,title,state,url,labels --state open and tea issues --output json), and wire through providers.
  2. Refactor src/run.ts to extract a reusable resolveAndInvoke(name: string, sdk: NiwaSDK, args: unknown[]): Promise<LoopOutput> that encapsulates module resolution + dynamic import + invocation. Have runModule call it.
  3. Add sdk.run(name, ...args) to the NiwaSDK interface and createRunSdk — delegates to the shared resolver. This is the clean public API.

Phase 2 — Reporter nesting

  1. Add subworkflowBegin / subworkflowEnd events to RunReporter interface. Plain/Pretty reporters treat them as no-ops or log them.
  2. Extend FullscreenReporter to maintain a stack of RunView frames. On subworkflowBegin, push a new frame and switch the active agent pane to it. On subworkflowEnd, pop the frame and restore the parent.
  3. Update src/tui/view.ts to render a breadcrumb header showing the nesting path and the sub-workflow's agents in the main pane.

Phase 3 — Planner workflow

  1. Write planner.ts (as a shipped preset):
    • issues.list() → get open issues
    • Optional: use an explorer agent to quickly triage which issue to tackle (or use a simple round-robin / oldest-first strategy)
    • sdk.run('explore-issue', issue.id) for complex issues (assessment first)
    • sdk.run('implement-issue', issue.id) for well-understood issues
    • Loop until no issues remain or a limit is reached
    • Track state in-memory (optional file persistence deferred)

Phase 4 — Polish

  1. Update docs/design/architecture.md to describe nested workflow composition and sdk.run().
  2. Add tests for issues.list(), sdk.run(), sub-workflow reporter events, and the planner workflow via RunModuleArgs / FakeCommandRunner.
  3. Add planner to the preset catalog and setup cascade.
## Effort Estimate **Medium-large: ~2–4 weeks** for one developer familiar with the codebase. Breakdown: - Issue listing API (`issues.list`): ~1–2 days - Sub-workflow invocation mechanism (`sdk.run` or import convention): ~3–5 days - Nested reporter / TUI support: ~3–5 days - Planner workflow preset: ~2–3 days - Testing: ~2–3 days --- ## Most Relevant Files | File | Relevance | |---|---| | `packages/niwa/index.ts` | Core types: `NiwaSDK` needs `run()` method; `Issues` needs `list()`. The `RunView` / reporter types may need nesting support. | | `src/run.ts` | The run spine — module resolution, SDK construction, invoke, cleanup. Must be refactored to expose a reusable `resolveAndInvoke()` for sub-workflow calls. | | `src/sdk.ts` | `createRunSdk` builds the `NiwaSDK`. Adding `sdk.run()` here. Also `makeWorkspace` — sub-workflows share the parent's runner/reporter. | | `src/issues.ts` | Host-side issue tracker. Needs `list()` method with provider-specific CLI args (`gh issue list`, `tea issues`). | | `src/run-reporter.ts` | `RunReporter` interface. May need a `subworkflow` event for nesting. | | `src/tui/reporter.ts` | `FullscreenReporter` — single `RunView`, one agent pane. Needs to handle nested agent sequences from sub-workflows. | | `src/tui/view.ts` | `RunView` layout — single-level agents list. Needs hierarchical or stacked view model. | | `src/presets/content/implement-issue.ts` | Model for how workflows compose sub-modules. The `planner` workflow will follow this pattern. | | `src/presets/content/explore-issue.ts` | Same as above; read-only workflow for triage. | | `src/cli.ts` | CLI dispatch — minor changes if `sdk.run` needs process-level isolation. | | `src/config.ts` | Config types — possible additions for planner configuration. | | `docs/design/architecture.md` | Architecture doc — would need updates to describe nested workflow composition. | --- ## Claims Analysis | Claim | Status | Detail | |---|---|---| | "read issues from the tracker" | **Partial** | `niwa.issues.get(id)` exists for a single issue by ID. **Missing:** No `list()` or search API to enumerate open issues — a blocker for "read issues from the tracker." | | "pick an issue" | **Not implemented** | No selection logic exists. A planner workflow would need either an LLM decision step or a deterministic strategy (oldest-first, priority). | | "run implement-issue or explore-issue on it" | **Partial** | Both workflows exist as presets. **Missing:** No mechanism to invoke a workflow from within a workflow — only Roles are composed this way (via direct module import + calling `.run()`). | | "repeat" | **Not implemented** | No looping construct or state persistence in the SDK. A planner would need its own loop + state tracking. | | "run workflows within workflows" | **Not implemented** | Architecture doc describes Workflows composing Roles, but no nested Workflow-in-Workflow mechanism exists. | | "affect code, UX, and fullscreen UI" | **Accurate** | The task correctly identifies the three impact areas. Changes are needed in all three. | --- ## Open Questions 1. **Issue listing API shape**: Should `issues.list()` return all open issues, or accept filters (state, label, milestone)? Pagination needed? Minimum viable for an LLM-based planner? 2. **Selection strategy**: How does the planner "pick" an issue? LLM decision (explore each then decide)? Oldest-first? Priority-tagged? The strategy drives the planner workflow's structure. 3. **Invocation mechanism**: Import-as-module (`import impl from './implement-issue'; impl.run(sdk, id)`) or `sdk.run('implement-issue', id)`? The import approach follows existing patterns (Roles are composed this way in `implement-issue.ts`) but limits flexibility. An SDK-level `run()` enables runtime-dynamic dispatch but requires significant refactoring of `run.ts`. 4. **Reporter architecture for nesting**: How should the TUI handle sub-workflow agents? - (a) Flatten all agents into one sequential view — simplest, loses nesting context - (b) Stack/tab approach — child workflow gets its own view context, parent minimized - (c) Drill-down — view switches to child workflow, parent shown as breadcrumb 5. **Reporter ownership**: Does the sub-workflow share the parent's `RunReporter` or create a sub-reporter? The plain/rail reporters are stateless and share fine; the FullscreenReporter owns the terminal and can only have one active. 6. **SDK lifecycle**: Does `sdk.run()` share the parent's SDK (same workspace tracker, same disposal) or create a child SDK? If shared, workspace releases from both parent and child merge correctly. If separate, wiring the reporter and runner through is complex. 7. **Error handling semantics**: If `explore-issue` fails on one issue, does the planner abort, skip to the next, or retry? What about partial failures in `implement-issue` (e.g., implement passes but review fails)? 8. **Termination condition**: When does the planner stop? When no more open issues? After N successful runs? LLM decides? Based on available workspaces in the tatami pool? 9. **State persistence**: Should the planner record processed issues between runs (e.g., in `.niwa/planner-state.json`)? Without this, every run would re-process old issues. 10. **Loop detection**: Should there be protection against infinite recursion (workflow calling itself directly or transitively)? 11. **Parallelism**: Should the planner run multiple sub-workflows concurrently on different workspaces? The tatami pool size constrains this. 12. **Config surface**: Does the planner need configuration (which workflows to run, issue filters, concurrency, order)? Would live in `.niwa/config.json` or a dedicated `.niwa/planner.json`? 13. **Preset vs user-authored**: Is `planner` a shipped preset (embedded in binary) or only a demonstration pattern users implement themselves? --- ## Suggested Approach ### Phase 1 — Core plumbing (prerequisites) 1. **Add `issues.list()`** to `packages/niwa/index.ts` (type), `src/issues.ts` (implementation with `gh issue list --json number,title,state,url,labels --state open` and `tea issues --output json`), and wire through providers. 2. **Refactor `src/run.ts`** to extract a reusable `resolveAndInvoke(name: string, sdk: NiwaSDK, args: unknown[]): Promise<LoopOutput>` that encapsulates module resolution + dynamic import + invocation. Have `runModule` call it. 3. **Add `sdk.run(name, ...args)`** to the `NiwaSDK` interface and `createRunSdk` — delegates to the shared resolver. This is the clean public API. ### Phase 2 — Reporter nesting 4. **Add `subworkflowBegin` / `subworkflowEnd` events** to `RunReporter` interface. Plain/Pretty reporters treat them as no-ops or log them. 5. **Extend `FullscreenReporter`** to maintain a stack of `RunView` frames. On `subworkflowBegin`, push a new frame and switch the active agent pane to it. On `subworkflowEnd`, pop the frame and restore the parent. 6. **Update `src/tui/view.ts`** to render a breadcrumb header showing the nesting path and the sub-workflow's agents in the main pane. ### Phase 3 — Planner workflow 7. **Write `planner.ts`** (as a shipped preset): - `issues.list()` → get open issues - Optional: use an explorer agent to quickly triage which issue to tackle (or use a simple round-robin / oldest-first strategy) - `sdk.run('explore-issue', issue.id)` for complex issues (assessment first) - `sdk.run('implement-issue', issue.id)` for well-understood issues - Loop until no issues remain or a limit is reached - Track state in-memory (optional file persistence deferred) ### Phase 4 — Polish 8. Update `docs/design/architecture.md` to describe nested workflow composition and `sdk.run()`. 9. Add tests for `issues.list()`, `sdk.run()`, sub-workflow reporter events, and the planner workflow via `RunModuleArgs` / `FakeCommandRunner`. 10. Add `planner` to the preset catalog and setup cascade.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
weiwen/niwa#9
No description provided.