Unnamed Skill

DBOS durable execution patterns and CRITICAL constraints for ChainGraph executor. Use when working on workflows, steps, execution, or any DBOS-related code. Contains MUST-FOLLOW constraints about what can be called from workflows vs steps. Triggers: dbos, workflow, step, durable, execution, startWorkflow, writeStream, recv, send, runStep, atomic, checkpoint, WorkflowQueue, queue, cancelWorkflow, Promise.allSettled. (project)

$ Instalar

git clone https://github.com/chaingraphlabs/chaingraph /tmp/chaingraph && cp -r /tmp/chaingraph/.claude/skills/dbos-patterns ~/.claude/skills/chaingraph

// tip: Run this command in your terminal to install the skill


name: dbos-patterns description: DBOS durable execution patterns and CRITICAL constraints for ChainGraph executor. Use when working on workflows, steps, execution, or any DBOS-related code. Contains MUST-FOLLOW constraints about what can be called from workflows vs steps. Triggers: dbos, workflow, step, durable, execution, startWorkflow, writeStream, recv, send, runStep, atomic, checkpoint, WorkflowQueue, queue, cancelWorkflow, Promise.allSettled. (project)

DBOS Patterns for ChainGraph

This skill covers DBOS (Database-Oriented Operating System) patterns used in the ChainGraph executor. CRITICAL: Contains constraints that agents MUST follow to avoid runtime errors.

CRITICAL Constraints

The Most Important Rule

DBOS context methods have strict calling restrictions based on WHERE you are:

// ============================================================
// WORKFLOW FUNCTIONS: All DBOS methods allowed
// ============================================================
async function myWorkflow(task: Task): Promise<Result> {
  await DBOS.send(...)           // โœ… Allowed
  await DBOS.recv(...)           // โœ… Allowed
  await DBOS.startWorkflow(...)  // โœ… Allowed
  await DBOS.writeStream(...)    // โœ… Allowed
  await DBOS.setEvent(...)       // โœ… Allowed
  await DBOS.sleep(...)          // โœ… Allowed

  const result = await DBOS.runStep(() => myStep(task))  // โœ… Allowed
  return result
}

// ============================================================
// STEP FUNCTIONS: ONLY writeStream() allowed!
// ============================================================
async function myStep(task: Task): Promise<StepResult> {
  await DBOS.writeStream(...)    // โœ… ONLY THIS ONE!

  // โŒ NOT ALLOWED - Will throw runtime error:
  // await DBOS.send(...)        // โŒ Error!
  // await DBOS.recv(...)        // โŒ Error!
  // await DBOS.startWorkflow(...) // โŒ Error!
  // await DBOS.setEvent(...)    // โŒ Error!
  // await DBOS.sleep(...)       // โŒ Error!

  return { data: ... }
}

Constraint Reference Table

DBOS MethodFrom WorkflowFrom Step
DBOS.send()โœ…โŒ
DBOS.recv()โœ…โŒ
DBOS.startWorkflow()โœ…โŒ
DBOS.setEvent() / getEvent()โœ…โŒ
DBOS.sleep()โœ…โŒ
DBOS.cancelWorkflow()โœ…โŒ
DBOS.runStep()โœ…โŒ
DBOS.writeStream()โœ…โœ…
DBOS.readStream()โœ…โŒ

Promise Handling

NEVER use Promise.all() - it fails fast and leaves promises unresolved, risking unhandled rejections.

// โŒ BAD: Promise.all() fails fast, other promises left dangling
const results = await Promise.all([step1(), step2(), step3()])

// โœ… GOOD: Promise.allSettled() waits for all, reports outcomes
const results = await Promise.allSettled([step1(), step2(), step3()])

Memory Isolation

Workflows and steps should NOT have side effects outside their own scope:

  • โœ… Can READ global variables
  • โŒ Must NOT create or update global variables
  • โŒ Must NOT modify shared state outside return values

Queue Initialization Order

CRITICAL: WorkflowQueue MUST be created before DBOS.launch() is called!

// File: server/dbos/queue.ts:17-35
// Queue is created at module level BEFORE DBOS.launch()
export const executionQueue = new WorkflowQueue(QUEUE_NAME, {
  workerConcurrency: config.dbos.workerConcurrency ?? 5,
  concurrency: config.dbos.queueConcurrency ?? 100,
})

// If created AFTER DBOS.launch(), queue will NOT dequeue tasks!

Design Patterns

Pattern 1: Signal Pattern (Race Condition Fix)

Problem: Client subscribes to events before the stream exists.

Solution: Workflow writes initialization event BEFORE waiting for start signal.

File: packages/chaingraph-executor/server/dbos/workflows/ExecutionWorkflows.ts

Timeline:
1. create execution (tRPC)
   โ””โ”€ Workflow starts โ†’ writes EXECUTION_CREATED โ†’ stream exists! โœ…
   โ””โ”€ Workflow waits for START_SIGNAL... โธ๏ธ

2. subscribe events (tRPC)
   โ””โ”€ Stream already exists โ†’ immediately receives EXECUTION_CREATED โœ…

3. start execution (tRPC)
   โ””โ”€ Sends START_SIGNAL โ†’ workflow continues โ–ถ๏ธ

Implementation Pattern:

async function executionWorkflow(task: ExecutionTask): Promise<ExecutionResult> {
  // Write event BEFORE waiting - stream now exists!
  await DBOS.writeStream('events', {
    executionId: task.executionId,
    event: 'EXECUTION_CREATED',
    timestamp: Date.now(),
  })

  // Now safe to wait - clients can subscribe
  const signal = await DBOS.recv<string>('START_SIGNAL', 300)
  if (!signal) {
    throw new Error('Execution start timeout')
  }

  // Continue with execution...
}

Pattern 2: Shared State Pattern (Command System)

Problem: Cannot call DBOS.recv() from steps, but need to check for commands.

Solution: Workflow polls messages, updates shared state object that step reads.

Files:

  • Workflow: server/dbos/workflows/ExecutionWorkflows.ts
  • Step: server/dbos/steps/ExecuteFlowAtomicStep.ts
// Shared state object (passed from workflow to step)
interface CommandController {
  currentCommand: 'PAUSE' | 'RESUME' | 'STEP' | null
}

// WORKFLOW LEVEL: Poll DBOS.recv() every 500ms
async function executionWorkflow(task: ExecutionTask) {
  const commandController: CommandController = { currentCommand: null }
  const abortController = new AbortController()

  // Start polling loop (runs concurrently with step)
  const pollCommands = async () => {
    while (!abortController.signal.aborted) {
      const cmd = await DBOS.recv<{ command: string }>('COMMAND', 0.5)
      if (cmd) {
        if (cmd.command === 'STOP') {
          abortController.abort()
        } else {
          commandController.currentCommand = cmd.command
        }
      }
    }
  }

  // Run step with shared state
  const result = await DBOS.runStep(() =>
    executeFlowAtomic(task, abortController, commandController)
  )

  return result
}

// STEP LEVEL: Check shared state every 100ms (no DBOS calls!)
async function executeFlowAtomic(
  task: ExecutionTask,
  abortController: AbortController,
  commandController: CommandController
) {
  const checkCommands = setInterval(() => {
    if (commandController.currentCommand === 'PAUSE') {
      debugger.pause()
    } else if (commandController.currentCommand === 'RESUME') {
      debugger.continue()
    }
    commandController.currentCommand = null
  }, 100)

  // Execute flow...
  // Step reads shared state, never calls DBOS.recv()
}

Pattern 3: Collect & Spawn Pattern (Child Executions)

Problem: Cannot call DBOS.startWorkflow() from steps, but Event Emitter nodes need to spawn children.

Solution: Step collects child tasks and returns them, workflow spawns them.

Files:

  • Step: server/dbos/steps/ExecuteFlowAtomicStep.ts:346-401
  • Workflow: server/dbos/workflows/ExecutionWorkflows.ts
// STEP: Collect child tasks (don't spawn!)
async function executeFlowAtomic(task: ExecutionTask): Promise<ExecutionResult> {
  const collectedChildTasks: ExecutionTask[] = []

  // Execute flow, capture emitted events
  await engine.execute()

  // After execution, collect child tasks from emitted events
  for (const event of context.emittedEvents.filter(e => !e.processed)) {
    event.processed = true

    // Create child execution row in DB (allowed in step)
    const childTask = await createChildTask(instance, event, store)
    collectedChildTasks.push(childTask)
  }

  // Return child tasks for workflow-level spawning
  return {
    status: 'completed',
    childTasks: collectedChildTasks,  // โ† Workflow will spawn these
  }
}

// WORKFLOW: Spawn collected children (DBOS.startWorkflow allowed here!)
async function executionWorkflow(task: ExecutionTask) {
  const result = await DBOS.runStep(() => executeFlowAtomic(task))

  // Spawn children at workflow level
  if (result.childTasks?.length > 0) {
    for (const childTask of result.childTasks) {
      await DBOS.startWorkflow(executionWorkflow, {
        workflowID: childTask.executionId
      })(childTask)
    }
  }

  return result
}

Pattern 4: Auto-Start Pattern (Child Execution Lifecycle)

Problem: Children need manual start call, slowing down execution tree.

Solution: Children skip the signal wait entirely and start immediately.

File: server/dbos/workflows/ExecutionWorkflows.ts:192-214

async function executionWorkflow(task: ExecutionTask) {
  const executionRow = await store.get(task.executionId)
  const isChildExecution = !!executionRow.parentExecutionId

  // Write EXECUTION_CREATED first (Signal Pattern)
  await DBOS.writeStream('events', { event: 'EXECUTION_CREATED', ... })

  // Auto-start for children!
  if (!isChildExecution) {
    // Parents: wait for signal from tRPC (timeout: 5 minutes)
    const startSignal = await DBOS.recv<string>('START_SIGNAL', 300)
    if (!startSignal) {
      throw new Error('Execution start timeout')
    }
  } else {
    // Children: skip waiting, start immediately
    DBOS.logger.info(`Child execution auto-start, beginning execution`)
  }

  // Continue execution...
}

Child Execution Lifecycle:

Parent spawns child via DBOS.startWorkflow()
  โ””โ”€ Child workflow starts
      โ”œโ”€ Writes EXECUTION_CREATED event
      โ”œโ”€ Detects parentExecutionId
      โ”œโ”€ Skips signal wait (auto-start)
      โ””โ”€ Executes flow immediately

Pattern 5: WorkflowQueue Pattern (Managed Concurrency)

Problem: Need to manage concurrency and ensure idempotent workflow spawning.

Solution: Use WorkflowQueue with concurrency limits and deduplication.

File: server/dbos/queue.ts

import { WorkflowQueue } from '@dbos-inc/dbos-sdk'

// Create at module level BEFORE DBOS.launch()
export const executionQueue = new WorkflowQueue('chaingraph-executions', {
  workerConcurrency: 5,   // Max concurrent per worker process
  concurrency: 100,       // Max concurrent globally
})

// Use with deduplication to prevent duplicate workflows
await DBOS.startWorkflow(ExecutionWorkflows, {
  queueName: executionQueue.name,
  workflowID: childTask.executionId,  // Unique ID
  enqueueOptions: {
    deduplicationID: childTask.executionId,  // Idempotency key
  },
}).executeChainGraph(childTask)

Pattern 6: Parent Monitoring Pattern (Child Stops if Parent Dies)

Problem: Child executions should stop if their parent completes or fails.

Solution: Background checker monitors parent workflow status.

File: server/dbos/workflows/ExecutionWorkflows.ts

async function monitorParentWorkflow(
  parentExecutionId: string,
  abortController: AbortController
) {
  while (!abortController.signal.aborted) {
    const parentStatus = await DBOS.getWorkflowStatus(parentExecutionId)

    if (parentStatus?.status === 'COMPLETED' ||
        parentStatus?.status === 'ERROR' ||
        parentStatus?.status === 'CANCELLED') {
      abortController.abort('Parent workflow has ended')
      break
    }

    await DBOS.sleep(5)  // Check every 5 seconds
  }
}

Three-Phase Workflow Structure

ChainGraph executions follow a three-phase structure:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ PHASE 1: Stream Initialization (Lines 148-214)               โ”‚
โ”‚   โ”œโ”€ Create CommandController                                โ”‚
โ”‚   โ”œโ”€ Write EXECUTION_CREATED event (stream exists!)          โ”‚
โ”‚   โ”œโ”€ Auto-start children (send START_SIGNAL to self)         โ”‚
โ”‚   โ””โ”€ Wait for START_SIGNAL                                   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ PHASE 2: Execution (Lines 216-374)                           โ”‚
โ”‚   โ”œโ”€ Step 1: updateToRunning()                               โ”‚
โ”‚   โ”œโ”€ Step 2: executeFlowAtomic() โ† Core execution            โ”‚
โ”‚   โ””โ”€ Spawn children via DBOS.startWorkflow()                 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ PHASE 3: Cleanup (Lines 376-423)                             โ”‚
โ”‚   โ”œโ”€ Step 3: updateToCompleted()                             โ”‚
โ”‚   โ”œโ”€ Stop command polling                                    โ”‚
โ”‚   โ””โ”€ DBOS auto-closes event stream                           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Key Files

FilePurposeCritical?
server/dbos/workflows/ExecutionWorkflows.tsMain orchestration workflowโญโญโญ
server/dbos/steps/ExecuteFlowAtomicStep.tsCore execution stepโญโญโญ
server/dbos/queue.ts:17-35Queue initialization (MUST be before DBOS.launch)โญโญโญ
server/dbos/config.tsDBOS initializationโญโญ
server/dbos/DBOSExecutionWorker.tsWorker lifecycleโญโญ
server/dbos/steps/UpdateStatusStep.tsStatus updatesโญ
server/implementations/dbos/DBOSEventBus.tsEvent streaming via DBOS.writeStream()โญโญ
server/utils/config.ts:70-139Environment configโญโญ

Environment Variables

# Enable DBOS mode (default: false)
ENABLE_DBOS_EXECUTION=true

# DBOS Admin UI
DBOS_ADMIN_ENABLED=true
DBOS_ADMIN_PORT=3022              # Access at http://localhost:3022

# Concurrency Limits
DBOS_QUEUE_CONCURRENCY=100        # Global across all workers
DBOS_WORKER_CONCURRENCY=5         # Per worker process

# DBOS Conductor (optional, for production monitoring)
DBOS_CONDUCTOR_URL=https://conductor.dbos.dev
DBOS_APPLICATION_NAME=chaingraph-executor
DBOS_CONDUCTOR_KEY=your-api-key-here

Anti-Patterns

Anti-Pattern #1: Calling DBOS methods from steps

// โŒ BAD: Will throw runtime error
async function myStep(data: string) {
  await DBOS.send('other-workflow', 'message', 'TOPIC')  // โŒ Error!
}

// โœ… GOOD: Return data, let workflow send
async function myStep(data: string): Promise<{ toSend: Message }> {
  return { toSend: { target: 'other-workflow', message: 'hello' } }
}

async function myWorkflow() {
  const result = await DBOS.runStep(() => myStep(data))
  await DBOS.send(result.toSend.target, result.toSend.message, 'TOPIC')  // โœ…
}

Anti-Pattern #2: Splitting atomic execution

// โŒ BAD: State lost between steps
await DBOS.runStep(() => loadFlow())
await DBOS.runStep(() => executeFlow())  // โŒ Flow state lost!

// โœ… GOOD: Single atomic step
await DBOS.runStep(() => executeFlowAtomic(task))  // โœ… All in one step

Anti-Pattern #3: Making children wait for START_SIGNAL

// โŒ BAD: Children timeout waiting for signal that never comes
async function executionWorkflow(task: ExecutionTask) {
  const isChild = !!executionRow.parentExecutionId
  // Always waiting - children have no one to send them the signal!
  await DBOS.recv('START_SIGNAL', 300)  // โŒ Times out for children
}

// โœ… GOOD: Children skip signal wait
async function executionWorkflow(task: ExecutionTask) {
  const isChild = !!executionRow.parentExecutionId
  if (!isChild) {
    // Only parents wait for signal (from tRPC start() call)
    await DBOS.recv('START_SIGNAL', 300)
  }
  // Children start immediately - no signal wait!
}

Anti-Pattern #4: Using Promise.all() for parallel steps

// โŒ BAD: Promise.all() fails fast, leaving other promises dangling
const results = await Promise.all([
  DBOS.runStep(() => step1()),
  DBOS.runStep(() => step2()),
  DBOS.runStep(() => step3()),
])

// โœ… GOOD: Promise.allSettled() waits for all, handles all outcomes
const results = await Promise.allSettled([
  DBOS.runStep(() => step1()),
  DBOS.runStep(() => step2()),
  DBOS.runStep(() => step3()),
])

Anti-Pattern #5: Memory side effects in workflows/steps

// โŒ BAD: Modifying global state
let globalCounter = 0
async function myWorkflow() {
  globalCounter++  // โŒ Side effect outside scope!
}

// โœ… GOOD: Return values instead of mutating globals
async function myWorkflow(): Promise<{ count: number }> {
  const count = calculateCount()
  return { count }  // โœ… Pure function, no side effects
}

Anti-Pattern #6: Creating queue after DBOS.launch()

// โŒ BAD: Queue created after DBOS is initialized
await DBOS.launch()
const queue = new WorkflowQueue('my-queue')  // โŒ Won't dequeue!

// โœ… GOOD: Queue created at module level BEFORE DBOS.launch()
const queue = new WorkflowQueue('my-queue')  // โœ… Module level
// ... later in main()
await DBOS.launch()

Quick Reference

NeedPatternWhere
Stream exists before subscribeSignal PatternWrite event before recv()
Commands during step executionShared StateWorkflow polls, step reads object
Spawn child workflowsCollect & SpawnStep collects, workflow spawns
Children start immediatelyAuto-StartSkip signal wait
Real-time events from stepDBOS.writeStream()Only stream method allowed in steps
Managed concurrencyWorkflowQueueQueue with workerConcurrency/concurrency
Child stops if parent diesParent MonitoringBackground status checker
Parallel steps safelyPromise.allSettled()Never use Promise.all()

DBOS Workflow Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ WORKFLOW (can call ALL DBOS methods)                        โ”‚
โ”‚                                                             โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”        โ”‚
โ”‚  โ”‚ DBOS.send() โ”‚  โ”‚ DBOS.recv() โ”‚  โ”‚startWorkflowโ”‚        โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜        โ”‚
โ”‚                                                             โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚ DBOS.runStep(() => ...)                              โ”‚   โ”‚
โ”‚  โ”‚                                                       โ”‚   โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚   โ”‚
โ”‚  โ”‚  โ”‚ STEP (ONLY writeStream allowed)               โ”‚    โ”‚   โ”‚
โ”‚  โ”‚  โ”‚                                                โ”‚    โ”‚   โ”‚
โ”‚  โ”‚  โ”‚  โœ… DBOS.writeStream()                         โ”‚    โ”‚   โ”‚
โ”‚  โ”‚  โ”‚  โŒ DBOS.send/recv/startWorkflow/sleep/...    โ”‚    โ”‚   โ”‚
โ”‚  โ”‚  โ”‚                                                โ”‚    โ”‚   โ”‚
โ”‚  โ”‚  โ”‚  return { childTasks: [...] }                  โ”‚    โ”‚   โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                                                             โ”‚
โ”‚  // After step completes:                                   โ”‚
โ”‚  for (child of result.childTasks) {                        โ”‚
โ”‚    await DBOS.startWorkflow(...)(child)  // โœ… Allowed hereโ”‚
โ”‚  }                                                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Advanced DBOS Features

For advanced DBOS features not currently used in ChainGraph (Debouncer, forkWorkflow, versioning, rate limiting, partitioned queues), see dbos-advanced.md in this skill directory.


Related Skills

  • executor-architecture - Package overview
  • chaingraph-concepts - Core domain concepts
  • subscription-sync - Event streaming patterns
  • trpc-execution - Execution tRPC procedures
  • trpc-patterns - General tRPC framework patterns