Skip to content

feat(workflows): flow directives + sub-workflow invocation (lobster.run)#27

Open
rendrag-git wants to merge 9 commits intoopenclaw:mainfrom
rendrag-git:main
Open

feat(workflows): flow directives + sub-workflow invocation (lobster.run)#27
rendrag-git wants to merge 9 commits intoopenclaw:mainfrom
rendrag-git:main

Conversation

@rendrag-git
Copy link

What

Two features that transform Lobster's workflow file runtime from a linear step executor into a control-flow engine:

1. Flow Directives — branching, loops, skip-ahead

steps:
  - id: check
    command: exec --json --shell "cat /tmp/status.json"
    flow:
      - when: $check.json.has_questions == true
        goto: route-questions
      - when: $check.json.design_complete == true
        goto: approval
      - default: brainstorm
  • goto forward (skip-ahead) or backward (loops)
  • default unconditional fallback
  • max_iterations per step (default 10) — hard error on breach
  • Extended condition evaluator: deep property access ($step.json.field.sub), comparison operators (==, !=, >, <, >=, <=), truthiness checks
  • Works with existing approval/halt/resume mechanism
  • Visit counters persist across halt/resume

2. Sub-workflow Invocation (lobster.run)

steps:
  - id: brainstorm
    command: lobster.run --name brainstorm-loop --args-json '{"task": "${task}"}'
  • In-process recursive runWorkflowFile() — not a subprocess
  • File resolution: parent dir → $LOBSTER_WORKFLOW_PATH → cwd
  • Args passing via --args-json (template-resolved)
  • Child halt bubbles to parent via linked state keys
  • Max nesting depth: 10 ($LOBSTER_MAX_WORKFLOW_DEPTH)
  • Supports --name (search) and --file (explicit path)

Stats

  • ~360 lines new code in src/workflows/file.ts
  • ~920 lines of tests across 5 new test files
  • 100% backward compatible — no flow field = linear execution as before
  • All existing tests pass

Test files

  • test/condition-evaluator.test.ts — extended condition evaluator
  • test/workflow-flow-validation.test.ts — load-time flow validation
  • test/workflow-flow.test.ts — flow execution (loops, branches, max_iterations)
  • test/workflow-subworkflow-parsing.test.ts — lobster.run command parsing + file resolution
  • test/workflow-subworkflow.test.ts — sub-workflow execution + halt propagation
  • test/workflow-integration.test.ts — combined feature tests

Ubuntu and others added 9 commits March 5, 2026 17:08
- New src/query.ts with listRuns, getRunDetail, cancelRun functions
- New test/query.test.ts with 11 unit tests covering all query operations
- CLI subcommands: list, status, cancel added to src/cli.ts
- Updated help text with new commands
- All 58 tests passing
…isons

Adds deep property access ($step.json.field.sub), comparison operators
(==, !=, >, <, >=, <=), and truthiness checks to evaluateCondition().
Exports evaluateCondition and resolveDeepRef for testing. Fully backward
compatible with existing $step.approved/$step.skipped patterns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds FlowRule type, flow/max_iterations fields to WorkflowStep.
loadWorkflowFile() now validates flow rule shapes, goto targets exist,
default is last, and max_iterations is a positive integer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…anching

Replaces the for-loop executor with a while-loop that honors flow rules.
Adds stepIndexMap for O(1) goto resolution, visitCounts (persisted across
halt/resume), and evaluateFlowRules(). Supports flowPending on resume
(skip re-execution when only flow evaluation is needed after approval).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
parseLobsterRunCommand() extracts --name/--file/--args-json from a
command string. resolveWorkflowByName() searches parent dir, then
LOBSTER_WORKFLOW_PATH dirs, then cwd, trying .lobster/.yaml/.yml/.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…opagation

executeChildWorkflow() runs child workflows in-process via recursive
runWorkflowFile(). Child halt bubbles to parent via childStateKey in
resume state. Depth limit enforced via LOBSTER_MAX_WORKFLOW_DEPTH (default 10).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests cover: sub-workflow in a flow loop, three-level nesting,
nested halt inside a loop with resume, flow routing, and combined
flow + approval + lobster.run scenarios.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e1242bce44

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +93 to +97
resumeToken: encodeToken({
filePath: state.filePath,
resumeAtIndex: state.resumeAtIndex,
steps: state.steps,
args: state.args,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include protocol/version fields in generated status resume tokens

getRunDetail() builds resumeToken without protocolVersion, v, or kind, but resume decoding requires those fields (src/resume.ts checks protocolVersion and v before accepting any token). In practice, the token returned by lobster status cannot be passed to lobster resume because it is rejected as an unsupported token format, so this new status/detail path exposes a non-functional resume token.

Useful? React with 👍 / 👎.

Comment on lines +260 to +263
const visits = (visitCounts.get(step.id) ?? 0) + 1;
const maxIter = step.max_iterations ?? 10;
if (visits > maxIter) {
throw new Error(`Step '${step.id}' exceeded max_iterations (${maxIter})`);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Skip visit-count increment when resuming halted sub-workflow

The step visit counter is incremented before resuming a halted child workflow, so any resume with childStateKey consumes an extra iteration even though the parent step is just continuing prior work. This causes false max_iterations failures for lobster.run steps that halt for approval (for example, max_iterations: 1 will fail on the first resume), which breaks resumable sub-workflows under tighter iteration limits.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant