Skip to content

feat(flows): add decision node type for constrained LLM branching#210

Open
JoshuaLelon wants to merge 2 commits intoopenclaw:mainfrom
JoshuaLelon:feat/decision-node-type
Open

feat(flows): add decision node type for constrained LLM branching#210
JoshuaLelon wants to merge 2 commits intoopenclaw:mainfrom
JoshuaLelon:feat/decision-node-type

Conversation

@JoshuaLelon
Copy link
Copy Markdown

@JoshuaLelon JoshuaLelon commented Apr 2, 2026

Summary

  • Adds a first-class decision node type to ACPX flows for single constrained LLM calls that return validated { choice, reasoning? } results
  • Adds resolveDecision callback on FlowRunnerOptions — keeps ACPX provider-agnostic while enabling callers to inject structured output implementations (OpenAI response_format, Anthropic tool_use, etc.)
  • Falls back to isolated ACP agent session with structured prompt + extractJsonObject parse when no resolver is provided
  • Validates choice ∈ options at runtime; invalid choices fail the node
  • reasoning is optional — resolvers that don't need to explain themselves can omit it
  • Extracts shared runIsolatedAcpPromptWithTrace helper to deduplicate the isolated session path between executeAcpNode and executeDecisionNode
  • Output feeds naturally into existing switch edges via $.choice

Usage

import { decision, defineFlow, compute } from "acpx/flows";

const flow = defineFlow({
  name: "tone-review",
  startAt: "check_tone",
  nodes: {
    check_tone: decision({
      prompt: (ctx) => `Is the tone right for ${ctx.input.audience}?`,
      options: {
        good: "Tone is appropriate",
        too_formal: "Too formal, needs loosening",
        too_casual: "Too casual, needs tightening",
      },
    }),
    publish: compute({ run: () => ({ status: "published" }) }),
    revise: compute({ run: () => ({ status: "needs_revision" }) }),
  },
  edges: [
    {
      from: "check_tone",
      switch: {
        on: "$.choice",
        cases: {
          good: "publish",
          too_formal: "revise",
          too_casual: "revise",
        },
      },
    },
  ],
});

Files changed

File Change
src/flows/types.ts DecisionNodeDefinition, DecisionResolverInput, DecisionResult types; resolveDecision on FlowRunnerOptions; reasoning optional
src/flows/definition.ts decision() builder helper
src/flows/runtime.ts executeDecisionNode, validateDecisionResult, ACP fallback, runIsolatedAcpPromptWithTrace shared helper
src/flows/store.ts Snapshot serialization for decision nodes
src/flows.ts Public exports
test/flows.test.ts 7 test cases (routing, invalid choice, context propagation, optional reasoning, bad reasoning type, ACP fallback)

Test plan

  • pnpm run typecheck — clean
  • pnpm run build — clean
  • pnpm run test — 0 failures (7 new decision tests + all existing tests pass)
  • Pre-commit hooks (oxfmt, oxlint, build) pass

🤖 AI-assisted (Claude Code) — fully tested

Generated with Claude Code

JoshuaLelon and others added 2 commits April 1, 2026 19:08
Add a first-class `decision` node type to ACPX flows that makes a single
constrained LLM call and returns a validated `{ choice, reasoning }` result
for use with switch edge routing.

Key design:
- `DecisionNodeDefinition` takes `prompt`, `options: Record<string, string>`,
  and optional `model`/`profile` fields
- `resolveDecision` callback on `FlowRunnerOptions` keeps ACPX provider-agnostic —
  callers inject their own structured output implementation (e.g. OpenAI
  response_format, Anthropic tool_use)
- Falls back to isolated ACP agent session with structured prompt + JSON parse
  when no resolver is provided
- `validateDecisionResult` enforces choice ∈ options at runtime
- Output feeds naturally into existing `switch` edges via `$.choice`

🤖 AI-assisted (Claude Code) — fully tested

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ptional

- Make DecisionResult.reasoning optional — resolveDecision callbacks no
  longer need to provide reasoning if the decision doesn't require it
- Extract runIsolatedAcpPromptWithTrace helper to deduplicate the isolated
  session path shared by executeAcpNode and executeDecisionNode (~70 lines)
- Add ACP fallback test for decision nodes (no resolveDecision provided)
- Add test for optional reasoning and non-string reasoning validation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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