feat: first-class DAG workflow engine #311

Closed
opened 2026-07-13 19:38:38 +02:00 by dries · 1 comment
Owner

Problem Statement

Ocman can launch child sessions, isolate work in worktrees, and repeatedly trigger a single action through Agent Loops. It cannot yet coordinate a large, durable multi-agent workflow such as the Bun Zig-to-Rust migration described in the reference video: discover thousands of work items, fan them out, run implementers and adversarial reviewers in parallel, fan findings back into fixers, serialize commits, gate progress on builds/tests, and resume safely after interruption.

Today a lead agent can approximate parts of this by creating many sessions and loops, but correctness lives in prompts and agent memory. Ocman has no persisted graph, deterministic dependency scheduling, artifact contracts, workspace ownership, fan-in semantics, or workflow-level recovery. At high concurrency this leads to agents fighting over Git state, repeated expensive commands, unclear progress, and unsafe retries of uncertain side effects.

Solution

Add a first-class, versioned DAG workflow engine to ocman. Users and agents define workflows declaratively as YAML or JSON. A workflow version contains triggers, typed nodes, dependency edges, CEL conditions, artifacts, resource requirements, repeat policies, and workspace policies. Each trigger firing or manual start creates an immutable, separately auditable Workflow Run pinned to one workflow version.

The engine executes agent, command, approval, subworkflow, map, and join nodes. Dynamic map nodes expand a JSON work-item list into pinned subworkflow runs, while join nodes aggregate ordered results and statuses. Named resource pools and a required run concurrency cap bound machine load. Worktree shards and exclusive/path-scoped leases prevent concurrent writers from corrupting shared state. Immutable artifacts connect nodes without shared mutable scratch state.

Existing Agent Loops become compatibility views over triggered one-node workflows. Existing cron, schedule, PR, child-completion, and turn-completion behavior moves into the workflow trigger layer. Current loop APIs, MCP tools, and UI remain as wrappers for one release while persisted loops are migrated.

The first reusable template demonstrates the target capability: discover migration units, map each through implement -> two parallel adversarial reviews -> join -> fix -> validate -> serialized commit, then run subsequent compile/test campaigns from captured diagnostic artifacts.

User Stories

  1. As an ocman user, I want to define a workflow as YAML or JSON, so that orchestration is reviewable and reproducible.
  2. As an ocman user, I want workflow definitions to be immutable and versioned, so that edits cannot change active runs unexpectedly.
  3. As an ocman user, I want every run pinned to one workflow version, so that I can reconstruct exactly what executed.
  4. As an ocman user, I want to validate a workflow before activation, so that malformed graphs never begin execution.
  5. As an ocman user, I want cycle detection across nodes and subworkflows, so that invalid recursive workflows are rejected.
  6. As an ocman user, I want to start workflows manually, so that one-off migration campaigns do not require a trigger.
  7. As an agent, I want to start and inspect workflows through MCP, so that orchestration can be initiated from a coding session.
  8. As an API client, I want to start and control workflows through REST, so that the UI and integrations share one behavior.
  9. As an ocman user, I want cron, interval, PR, child-completion, and turn-completion triggers, so that existing loop use cases remain possible.
  10. As an ocman user, I want every trigger firing to create a separate run, so that recurring executions have independent histories and outcomes.
  11. As an ocman user, I want trigger overlap policies of skip, queue, and parallel, so that recurring runs behave predictably.
  12. As an ocman user, I want overlap to default to skip, so that a slow cron workflow does not create an accidental backlog.
  13. As an ocman user, I want an agent node to launch an OpenCode session, so that coding work is a native workflow operation.
  14. As an ocman user, I want agent attempts to use fresh sessions by default, so that unrelated work does not inherit stale context.
  15. As an ocman user, I want optional session affinity, so that retries or related nodes can preserve useful conversational context.
  16. As an ocman user, I want command nodes to run bounded shell commands, so that builds, tests, diagnostics, and Git operations can be deterministic gates.
  17. As an ocman user, I want command nodes to inherit scoped permission rules, so that workflows cannot bypass ocman security.
  18. As an ocman user, I want approval nodes, so that sensitive transitions such as merging can wait for a human.
  19. As an ocman user, I want reusable subworkflow nodes, so that common pipelines can be composed without duplication.
  20. As an ocman user, I want subworkflows pinned when a run starts, so that downstream edits cannot alter the active execution.
  21. As an ocman user, I want map nodes to consume JSON arrays, so that discovered files, errors, or tests become parallel work items.
  22. As an ocman user, I want each mapped item to have a stable key, so that retries and restart recovery do not duplicate completed work.
  23. As an ocman user, I want map to invoke a subworkflow per item, so that complex per-item pipelines remain reusable.
  24. As an ocman user, I want join nodes to aggregate results in input order, so that output remains deterministic despite parallel completion.
  25. As an ocman user, I want per-item statuses in joined output, so that partial failure can be inspected and handled.
  26. As an ocman user, I want join policies for all-success, always, and minimum-success, so that fan-in behavior matches the workflow intent.
  27. As an ocman user, I want independent branches to continue after an unrelated branch fails, so that one bad item does not waste all useful work.
  28. As an ocman user, I want optional global fail-fast, so that workflows with unsafe downstream effects can stop immediately.
  29. As an ocman user, I want CEL conditions on edges, so that branches can depend safely on node outcomes and JSON artifacts.
  30. As an ocman user, I want bounded repeat-until policies around nodes or subworkflows, so that review/fix cycles are expressible without graph back-edges.
  31. As an ocman user, I want every repeat policy to declare a hard attempt limit, so that an unmet predicate cannot repeat forever.
  32. As an ocman user, I want immutable named artifacts between nodes, so that concurrent branches do not communicate through mutable scratch state.
  33. As an ocman user, I want artifacts for JSON, text, file references, diffs, and diagnostics, so that common coding workflows have explicit contracts.
  34. As an ocman user, I want agent output collectors for final messages, Git diffs, files, and JSON files, so that agents do not need a special completion call.
  35. As an ocman user, I want output validation after a node completes, so that missing or malformed artifacts fail the node visibly.
  36. As an ocman user, I want large logs and payloads stored outside SQLite, so that the state database remains responsive.
  37. As an ocman user, I want large artifacts content-addressed, so that repeated payloads can be referenced without unnecessary copies.
  38. As an ocman user, I want run metadata retained after payload cleanup, so that old workflow outcomes remain auditable.
  39. As an ocman user, I want large payloads deleted after 30 days by default, so that unattended workflows do not fill the disk indefinitely.
  40. As an ocman user, I want to override artifact retention per workflow, so that important migration evidence can be preserved.
  41. As an ocman user, I want every run to require a concurrency cap, so that machine load is intentionally bounded.
  42. As an ocman user, I want cost and wall-clock duration to default to unlimited, so that long campaigns do not stop without an explicit user limit.
  43. As an ocman user, I want optional cost, token, and duration limits, so that expensive workflows can still be bounded.
  44. As an ocman user, I want named resource pools with capacities, so that agents, compilers, tests, and commits can have different limits.
  45. As an ocman user, I want nodes to declare resource requirements, so that the scheduler starts work only when required capacity is available.
  46. As an ocman user, I want a workflow to own a bounded pool of worktree shards, so that disk use and isolation are controlled.
  47. As an ocman user, I want exclusive workspace leases by default, so that mutating tasks cannot interfere accidentally.
  48. As an ocman user, I want path-scoped leases for declared non-overlapping paths, so that many agents can safely share a small number of large worktrees.
  49. As an ocman user, I want path overlap rejected before concurrent work starts, so that two writers cannot own the same file scope.
  50. As an ocman user, I want mutating Git commands denied for path-leased agents, so that a centralized coordinator owns staging, reset, stash, commit, and push.
  51. As an ocman user, I want commit operations serialized per shard, so that concurrent implementation does not corrupt Git state.
  52. As an ocman user, I want workflow permissions to restrict writable roots, commands, and explicit environment variables, so that unattended execution stays scoped.
  53. As an ocman user, I want workflow definitions to reference secret names rather than values, so that definitions are safe to store and share.
  54. As an ocman user, I want hosts to resolve secrets at execution time, so that secret material never enters the workflow definition.
  55. As an ocman user, I want known secret values redacted from logs and artifacts, so that observability does not leak credentials.
  56. As an ocman user, I want a durable record of every node attempt, so that I can understand retries, outputs, and failures.
  57. As an ocman user, I want still-running agent sessions reconnected after an ocman restart, so that useful work is not abandoned.
  58. As an ocman user, I want interrupted side-effecting attempts marked unknown, so that ocman never blindly repeats a commit or push.
  59. As an ocman user, I want retry-safe nodes to opt into automatic replay, so that deterministic reads and checks can recover unattended.
  60. As an ocman user, I want uncertain branches paused for inspection, so that recovery favors correctness over pretending exactly-once execution.
  61. As an ocman user, I want pause to stop new scheduling while active nodes finish, so that pausing does not destroy in-progress work.
  62. As an ocman user, I want cancel to request termination of active attempts, so that runaway execution can be interrupted.
  63. As an ocman user, I want canceled descendants marked skipped when unreachable, so that the final graph explains what did not run.
  64. As an ocman user, I want a read-only DAG view, so that I can inspect dependencies before activation without needing a visual editor.
  65. As an ocman user, I want a live run graph, so that I can see pending, ready, active, blocked, failed, skipped, unknown, and successful nodes.
  66. As an ocman user, I want map branches collapsible by item, so that large campaigns remain navigable.
  67. As an ocman user, I want node logs, artifacts, attempts, resource waits, and workspace leases visible, so that stalled work can be diagnosed.
  68. As an ocman user, I want SSE updates for run state, so that the graph updates without browser polling.
  69. As an ocman user, I want to filter runs by project, workflow, trigger, state, and time, so that active and historical work can be found quickly.
  70. As an ocman user, I want a reusable migration template with implement, dual review, fix, validate, and commit stages, so that the workflow from the reference video is immediately demonstrable.
  71. As an ocman user, I want reviewers to receive only declared artifacts such as the diff and guidance, so that adversarial review remains independent from implementer context.
  72. As an ocman user, I want compiler or test output captured once and partitioned into mapped work, so that dozens of agents do not repeat expensive discovery commands.
  73. As an ocman user, I want validation commands to gate commits, so that stubs, skipped tests, or broken output cannot be considered successful solely because an agent says it is done.
  74. As an existing loop user, I want my persisted loops migrated, so that upgrading ocman does not discard automations or history.
  75. As an existing loop user, I want current REST and MCP loop controls to keep working during migration, so that agents and integrations do not break immediately.
  76. As an existing loop user, I want the Loops UI to remain available as a compatibility view, so that one-node recurring workflows remain familiar.
  77. As an ocman maintainer, I want the new workflow UI capability-gated rather than platform-branched, so that frontend platform neutrality remains intact.
  78. As an ocman maintainer, I want workflow execution to remain host-local initially, so that multi-host scheduling does not block a useful first release.
  79. As an ocman maintainer, I want run, attempt, and lease records to allow an optional host identity, so that future multi-host scheduling does not require a schema redesign.
  80. As an ocman maintainer, I want one execution service shared by REST, MCP, triggers, and compatibility wrappers, so that behavior cannot drift by entry point.

Implementation Decisions

  1. The domain uses these canonical terms:
    • Workflow Definition: the named workflow and activation metadata.
    • Workflow Version: an immutable validated DAG plus trigger and policy configuration.
    • Workflow Run: one execution pinned to one Workflow Version.
    • Node Run: the run-scoped state of one static or dynamically expanded node.
    • Node Attempt: one executor invocation for a Node Run.
    • Artifact: immutable named data produced by one node and consumed by others.
    • Workspace Shard: a worktree from the run-owned bounded pool.
    • Lease: temporary ownership of resource capacity, a shard, or declared paths.
    • Loop: a compatibility name for a triggered one-node workflow.
  2. Workflow versions are declarative YAML or JSON. The server normalizes both to one canonical representation before validation and persistence.
  3. Workflow versions are immutable. Editing creates a new version. Runs and subworkflow references are pinned before execution begins.
  4. The graph remains acyclic. Repetition is represented by a bounded repeat-until policy on a node or subworkflow, never by a back-edge.
  5. CEL evaluates edge conditions and repeat predicates against a constrained environment containing node outcomes and declared JSON artifacts. Shell and template execution are not condition languages.
  6. Static validation rejects node cycles, recursive subworkflow cycles, duplicate IDs, missing references, invalid artifact contracts, invalid CEL, undeclared resource pools, invalid trigger configuration, and absent/non-positive concurrency caps.
  7. Workflow Runs are persisted separately from definitions and versions. Each trigger firing creates a new run with an immutable trigger snapshot and pinned dependency set.
  8. Trigger overlap supports skip, queue, and parallel; skip is the default. Trigger state and pending queued firings are durable.
  9. Existing cron, interval, PR, child-completion, and turn-completion implementations become workflow triggers rather than a separate loop execution model.
  10. Existing loops are migrated to workflow definitions containing one node and the corresponding trigger. Historical loop iterations map to historical run/attempt records as far as their available data permits.
  11. Existing loop REST/MCP/UI surfaces remain compatibility wrappers for one release and operate on one-node workflows. New code uses workflow APIs and terminology.
  12. The node types in the initial release are agent, command, approval, subworkflow, map, and join. Git, forge, build, and test behavior are composed from command or agent nodes rather than receiving bespoke node types.
  13. Agent nodes launch through the existing platform/session service and launcher seams. Fresh sessions are the default; an explicit session-affinity key permits reuse.
  14. Agent completion is inferred from the platform session reaching idle or a terminal state. Configured collectors capture final messages, Git diffs, files, or JSON files and validate declared outputs.
  15. Command nodes execute through one command-executor boundary that applies working-directory, permission, environment, cancellation, output, and resource policies. They return exit status, stdout/stderr references, and declared collectors.
  16. Approval nodes durably wait for an explicit user/API decision. They consume no executor capacity while waiting.
  17. Map consumes a JSON array artifact. Each item has a stable key and invokes a pinned subworkflow. Duplicate keys are invalid. Join returns input-ordered values plus per-item outcomes.
  18. Join policies are all-success, always, and minimum-success. Failed branches block only dependent nodes by default; unrelated branches continue. Global fail-fast is opt-in.
  19. Artifacts are immutable and named. Initial artifact kinds are JSON, text, file reference, diff, and diagnostics. Consumers receive only declared inputs.
  20. Artifact metadata and references live in state.db. Large payloads and logs live in a content-addressed store under ocman data storage. The default payload retention is 30 days; run metadata remains.
  21. Workflow definitions contain secret references only. Secret resolution happens at the owning host during attempt startup. Known resolved values are redacted from captured output.
  22. Every run requires a positive concurrency cap. Cost/token and duration limits are optional and default to unlimited. When configured, limits include descendant map items and subworkflows.
  23. Named resource pools provide independent capacities. Nodes atomically acquire all required capacities before starting and release them on terminal attempt state.
  24. A run owns a configurable bounded pool of worktree shards for one repository. The first release runs on one host and one repository.
  25. Workspace leases support exclusive and path modes. Exclusive is the default. Path leases require declared normalized path scopes and may coexist only when scopes do not overlap.
  26. Path-leased mutators cannot execute Git operations that mutate repository-wide state. Commit/push coordinator nodes use exclusive or serialized per-shard capacity.
  27. Run, attempt, artifact, resource, shard, and lease records include optional owning-host identity where needed, but the initial scheduler selects only the local host.
  28. Scheduler state is durable. Ready-node calculation, lease acquisition, and attempt creation are transactional enough that concurrent ticks cannot dispatch the same attempt twice.
  29. Exactly-once side effects are not claimed. After restart, live sessions are reconnected where possible. Interrupted attempts become unknown unless explicitly retry-safe. Unknown attempts pause dependent scheduling pending user resolution.
  30. Pause prevents new attempts while allowing active attempts to finish. Cancel requests executor termination; terminal cancellation releases leases and marks unreachable descendants skipped.
  31. The scheduler publishes run/node/attempt changes through the existing SSE infrastructure. The browser does not busy-poll for workflow progress.
  32. REST, MCP, trigger dispatch, and loop compatibility wrappers all delegate to one workflow service. The service owns definition validation, version activation, run lifecycle, and state transitions.
  33. The initial authoring UI accepts YAML/JSON and displays validation errors plus a read-only graph. Drag-and-drop graph editing is deferred.
  34. The run UI displays graph state, dynamic map branches, attempts, artifacts, logs, resource waits, shard/path ownership, trigger information, and controls.
  35. Frontend workflow affordances use capability gating and remain host/platform agnostic.
  36. A reusable migration workflow template ships as an example/preset. It discovers items once, maps a per-item subworkflow, runs two independent diff-only reviewers, joins findings, runs a fixer, validates, and serializes commits.
  37. Expensive diagnostics are modeled as command outputs and partitioned artifacts, so downstream agents consume captured slices rather than rerunning discovery.
  38. Architecture documentation is updated because this adds a new orchestration seam, persistence model, executor flow, and browser/backend workflow data flow.

Testing Decisions

  1. The primary seam is a workflow-run harness around the workflow service, backed by a real temporary state database, fake agent/command executors, and a controllable clock.
  2. Tests submit complete definitions, start or trigger runs, drive scheduler ticks/executor outcomes, and assert externally visible run states, node states, artifacts, leases, attempts, and final outcomes. Internal helper call order is not asserted.
  3. Definition tests cover YAML/JSON normalization, immutable versions, DAG validation, CEL compilation, subworkflow pinning, recursive-cycle rejection, artifact contracts, resource references, and required concurrency.
  4. Scheduler tests cover readiness, conditional branches, independent failure continuation, fail-fast, all join policies, repeat-until limits, deterministic map ordering, stable-key resume, and bounded concurrency.
  5. Resource/workspace tests cover atomic pool acquisition, capacity release, shard bounds, exclusive leases, normalized path overlap, non-overlapping path concurrency, Git-policy enforcement, and serialized commit capacity.
  6. Recovery tests persist each interruption point, recreate the service, and verify session reconnection, retry-safe replay, unknown side-effect state, paused dependents, and no duplicate attempt dispatch.
  7. Artifact tests use the public workflow-run seam to verify collection, validation, immutability, content addressing, redaction, missing outputs, and retention cleanup while preserving metadata.
  8. Trigger tests use a fake clock and existing fake forge/session-status seams to verify firing, overlap skip/queue/parallel, durable trigger state, and one immutable run per firing.
  9. Migration tests start from representative pre-workflow databases and verify active, paused, completed, and errored loops become usable one-node workflow definitions with compatible controls and retained history.
  10. Thin REST and MCP contract tests prove that definitions, runs, lifecycle controls, approvals, and compatibility loop calls delegate to the same service and enforce localhost/security boundaries.
  11. UI tests assert user-visible graph/run behavior from API responses and SSE events: validation errors, state transitions, map expansion, resource waits, logs/artifact links, pause/cancel/approval, and compatibility loop views.
  12. End-to-end coverage includes one small migration-template run with fake or fixture executors: discover two items, implement, run two parallel reviews, join, fix, validate, and serialize commits.
  13. Tests follow existing repository patterns: table-driven Go tests, temporary SQLite databases, shared fake platform/server integrations, Zustand store tests, Vitest component tests, and stable ARIA/data-testid Playwright locators.
  14. New branches, parsers, state transitions, scheduler decisions, permission boundaries, and recovery paths receive tests in the same change so the coverage ratchet does not drop.
  15. Performance checks exercise a large synthetic map without launching real agents to verify that ready-node calculation, persisted state, and UI response shapes remain bounded and paginatable.

Out of Scope

  • Drag-and-drop or canvas-based workflow authoring in the first release.
  • Arbitrary graph cycles or unbounded repeat policies.
  • Multi-repository workflows.
  • Actual multi-host scheduling, cross-host artifact transfer, or cross-host worktree leasing. The data model only preserves the upgrade path.
  • A new secrets vault; workflows reference secrets resolved from existing host environment/configuration.
  • Container or VM sandboxing beyond ocman/OpenCode permission, directory, environment, and process controls.
  • Exactly-once guarantees for external side effects such as pushes, merges, or forge mutations.
  • Specialized Git, forge, compiler, test-framework, or code-review node types.
  • Automatic semantic merging of conflicting patches.
  • Automatic unrestricted merging to protected or production branches.
  • Cost or duration limits being mandatory; only concurrency is mandatory.
  • Replacing OpenCode as the agent runtime in the first release.
  • Removing compatibility loop surfaces immediately; they remain for one release.

Further Notes

The reference workflow succeeded because a human designed a translation pipeline around agents: shared guidance, stable work units, adversarial review, centralized fixing, controlled Git behavior, captured diagnostics, validation, and aggressive parallelism. The workflow engine should encode those coordination guarantees while leaving implementation judgments to agents.

This is intentionally a general DAG engine rather than a hard-coded migration campaign. The first migration template is the tracer example and acceptance proof, not a privileged execution path.

The implementation should be delivered as tracer-bullet tickets rather than one large change. A useful first vertical slice is: persist and validate a two-node definition, start a run, execute a fake agent followed by a fake command, stream state, and render the read-only run graph. Dynamic map/join, workspace leasing, triggers/loop migration, and the full migration template can then extend the same run seam.

## Problem Statement Ocman can launch child sessions, isolate work in worktrees, and repeatedly trigger a single action through Agent Loops. It cannot yet coordinate a large, durable multi-agent workflow such as the Bun Zig-to-Rust migration described in the reference video: discover thousands of work items, fan them out, run implementers and adversarial reviewers in parallel, fan findings back into fixers, serialize commits, gate progress on builds/tests, and resume safely after interruption. Today a lead agent can approximate parts of this by creating many sessions and loops, but correctness lives in prompts and agent memory. Ocman has no persisted graph, deterministic dependency scheduling, artifact contracts, workspace ownership, fan-in semantics, or workflow-level recovery. At high concurrency this leads to agents fighting over Git state, repeated expensive commands, unclear progress, and unsafe retries of uncertain side effects. ## Solution Add a first-class, versioned DAG workflow engine to ocman. Users and agents define workflows declaratively as YAML or JSON. A workflow version contains triggers, typed nodes, dependency edges, CEL conditions, artifacts, resource requirements, repeat policies, and workspace policies. Each trigger firing or manual start creates an immutable, separately auditable Workflow Run pinned to one workflow version. The engine executes agent, command, approval, subworkflow, map, and join nodes. Dynamic map nodes expand a JSON work-item list into pinned subworkflow runs, while join nodes aggregate ordered results and statuses. Named resource pools and a required run concurrency cap bound machine load. Worktree shards and exclusive/path-scoped leases prevent concurrent writers from corrupting shared state. Immutable artifacts connect nodes without shared mutable scratch state. Existing Agent Loops become compatibility views over triggered one-node workflows. Existing cron, schedule, PR, child-completion, and turn-completion behavior moves into the workflow trigger layer. Current loop APIs, MCP tools, and UI remain as wrappers for one release while persisted loops are migrated. The first reusable template demonstrates the target capability: discover migration units, map each through implement -> two parallel adversarial reviews -> join -> fix -> validate -> serialized commit, then run subsequent compile/test campaigns from captured diagnostic artifacts. ## User Stories 1. As an ocman user, I want to define a workflow as YAML or JSON, so that orchestration is reviewable and reproducible. 2. As an ocman user, I want workflow definitions to be immutable and versioned, so that edits cannot change active runs unexpectedly. 3. As an ocman user, I want every run pinned to one workflow version, so that I can reconstruct exactly what executed. 4. As an ocman user, I want to validate a workflow before activation, so that malformed graphs never begin execution. 5. As an ocman user, I want cycle detection across nodes and subworkflows, so that invalid recursive workflows are rejected. 6. As an ocman user, I want to start workflows manually, so that one-off migration campaigns do not require a trigger. 7. As an agent, I want to start and inspect workflows through MCP, so that orchestration can be initiated from a coding session. 8. As an API client, I want to start and control workflows through REST, so that the UI and integrations share one behavior. 9. As an ocman user, I want cron, interval, PR, child-completion, and turn-completion triggers, so that existing loop use cases remain possible. 10. As an ocman user, I want every trigger firing to create a separate run, so that recurring executions have independent histories and outcomes. 11. As an ocman user, I want trigger overlap policies of skip, queue, and parallel, so that recurring runs behave predictably. 12. As an ocman user, I want overlap to default to skip, so that a slow cron workflow does not create an accidental backlog. 13. As an ocman user, I want an agent node to launch an OpenCode session, so that coding work is a native workflow operation. 14. As an ocman user, I want agent attempts to use fresh sessions by default, so that unrelated work does not inherit stale context. 15. As an ocman user, I want optional session affinity, so that retries or related nodes can preserve useful conversational context. 16. As an ocman user, I want command nodes to run bounded shell commands, so that builds, tests, diagnostics, and Git operations can be deterministic gates. 17. As an ocman user, I want command nodes to inherit scoped permission rules, so that workflows cannot bypass ocman security. 18. As an ocman user, I want approval nodes, so that sensitive transitions such as merging can wait for a human. 19. As an ocman user, I want reusable subworkflow nodes, so that common pipelines can be composed without duplication. 20. As an ocman user, I want subworkflows pinned when a run starts, so that downstream edits cannot alter the active execution. 21. As an ocman user, I want map nodes to consume JSON arrays, so that discovered files, errors, or tests become parallel work items. 22. As an ocman user, I want each mapped item to have a stable key, so that retries and restart recovery do not duplicate completed work. 23. As an ocman user, I want map to invoke a subworkflow per item, so that complex per-item pipelines remain reusable. 24. As an ocman user, I want join nodes to aggregate results in input order, so that output remains deterministic despite parallel completion. 25. As an ocman user, I want per-item statuses in joined output, so that partial failure can be inspected and handled. 26. As an ocman user, I want join policies for all-success, always, and minimum-success, so that fan-in behavior matches the workflow intent. 27. As an ocman user, I want independent branches to continue after an unrelated branch fails, so that one bad item does not waste all useful work. 28. As an ocman user, I want optional global fail-fast, so that workflows with unsafe downstream effects can stop immediately. 29. As an ocman user, I want CEL conditions on edges, so that branches can depend safely on node outcomes and JSON artifacts. 30. As an ocman user, I want bounded repeat-until policies around nodes or subworkflows, so that review/fix cycles are expressible without graph back-edges. 31. As an ocman user, I want every repeat policy to declare a hard attempt limit, so that an unmet predicate cannot repeat forever. 32. As an ocman user, I want immutable named artifacts between nodes, so that concurrent branches do not communicate through mutable scratch state. 33. As an ocman user, I want artifacts for JSON, text, file references, diffs, and diagnostics, so that common coding workflows have explicit contracts. 34. As an ocman user, I want agent output collectors for final messages, Git diffs, files, and JSON files, so that agents do not need a special completion call. 35. As an ocman user, I want output validation after a node completes, so that missing or malformed artifacts fail the node visibly. 36. As an ocman user, I want large logs and payloads stored outside SQLite, so that the state database remains responsive. 37. As an ocman user, I want large artifacts content-addressed, so that repeated payloads can be referenced without unnecessary copies. 38. As an ocman user, I want run metadata retained after payload cleanup, so that old workflow outcomes remain auditable. 39. As an ocman user, I want large payloads deleted after 30 days by default, so that unattended workflows do not fill the disk indefinitely. 40. As an ocman user, I want to override artifact retention per workflow, so that important migration evidence can be preserved. 41. As an ocman user, I want every run to require a concurrency cap, so that machine load is intentionally bounded. 42. As an ocman user, I want cost and wall-clock duration to default to unlimited, so that long campaigns do not stop without an explicit user limit. 43. As an ocman user, I want optional cost, token, and duration limits, so that expensive workflows can still be bounded. 44. As an ocman user, I want named resource pools with capacities, so that agents, compilers, tests, and commits can have different limits. 45. As an ocman user, I want nodes to declare resource requirements, so that the scheduler starts work only when required capacity is available. 46. As an ocman user, I want a workflow to own a bounded pool of worktree shards, so that disk use and isolation are controlled. 47. As an ocman user, I want exclusive workspace leases by default, so that mutating tasks cannot interfere accidentally. 48. As an ocman user, I want path-scoped leases for declared non-overlapping paths, so that many agents can safely share a small number of large worktrees. 49. As an ocman user, I want path overlap rejected before concurrent work starts, so that two writers cannot own the same file scope. 50. As an ocman user, I want mutating Git commands denied for path-leased agents, so that a centralized coordinator owns staging, reset, stash, commit, and push. 51. As an ocman user, I want commit operations serialized per shard, so that concurrent implementation does not corrupt Git state. 52. As an ocman user, I want workflow permissions to restrict writable roots, commands, and explicit environment variables, so that unattended execution stays scoped. 53. As an ocman user, I want workflow definitions to reference secret names rather than values, so that definitions are safe to store and share. 54. As an ocman user, I want hosts to resolve secrets at execution time, so that secret material never enters the workflow definition. 55. As an ocman user, I want known secret values redacted from logs and artifacts, so that observability does not leak credentials. 56. As an ocman user, I want a durable record of every node attempt, so that I can understand retries, outputs, and failures. 57. As an ocman user, I want still-running agent sessions reconnected after an ocman restart, so that useful work is not abandoned. 58. As an ocman user, I want interrupted side-effecting attempts marked unknown, so that ocman never blindly repeats a commit or push. 59. As an ocman user, I want retry-safe nodes to opt into automatic replay, so that deterministic reads and checks can recover unattended. 60. As an ocman user, I want uncertain branches paused for inspection, so that recovery favors correctness over pretending exactly-once execution. 61. As an ocman user, I want pause to stop new scheduling while active nodes finish, so that pausing does not destroy in-progress work. 62. As an ocman user, I want cancel to request termination of active attempts, so that runaway execution can be interrupted. 63. As an ocman user, I want canceled descendants marked skipped when unreachable, so that the final graph explains what did not run. 64. As an ocman user, I want a read-only DAG view, so that I can inspect dependencies before activation without needing a visual editor. 65. As an ocman user, I want a live run graph, so that I can see pending, ready, active, blocked, failed, skipped, unknown, and successful nodes. 66. As an ocman user, I want map branches collapsible by item, so that large campaigns remain navigable. 67. As an ocman user, I want node logs, artifacts, attempts, resource waits, and workspace leases visible, so that stalled work can be diagnosed. 68. As an ocman user, I want SSE updates for run state, so that the graph updates without browser polling. 69. As an ocman user, I want to filter runs by project, workflow, trigger, state, and time, so that active and historical work can be found quickly. 70. As an ocman user, I want a reusable migration template with implement, dual review, fix, validate, and commit stages, so that the workflow from the reference video is immediately demonstrable. 71. As an ocman user, I want reviewers to receive only declared artifacts such as the diff and guidance, so that adversarial review remains independent from implementer context. 72. As an ocman user, I want compiler or test output captured once and partitioned into mapped work, so that dozens of agents do not repeat expensive discovery commands. 73. As an ocman user, I want validation commands to gate commits, so that stubs, skipped tests, or broken output cannot be considered successful solely because an agent says it is done. 74. As an existing loop user, I want my persisted loops migrated, so that upgrading ocman does not discard automations or history. 75. As an existing loop user, I want current REST and MCP loop controls to keep working during migration, so that agents and integrations do not break immediately. 76. As an existing loop user, I want the Loops UI to remain available as a compatibility view, so that one-node recurring workflows remain familiar. 77. As an ocman maintainer, I want the new workflow UI capability-gated rather than platform-branched, so that frontend platform neutrality remains intact. 78. As an ocman maintainer, I want workflow execution to remain host-local initially, so that multi-host scheduling does not block a useful first release. 79. As an ocman maintainer, I want run, attempt, and lease records to allow an optional host identity, so that future multi-host scheduling does not require a schema redesign. 80. As an ocman maintainer, I want one execution service shared by REST, MCP, triggers, and compatibility wrappers, so that behavior cannot drift by entry point. ## Implementation Decisions 1. The domain uses these canonical terms: - Workflow Definition: the named workflow and activation metadata. - Workflow Version: an immutable validated DAG plus trigger and policy configuration. - Workflow Run: one execution pinned to one Workflow Version. - Node Run: the run-scoped state of one static or dynamically expanded node. - Node Attempt: one executor invocation for a Node Run. - Artifact: immutable named data produced by one node and consumed by others. - Workspace Shard: a worktree from the run-owned bounded pool. - Lease: temporary ownership of resource capacity, a shard, or declared paths. - Loop: a compatibility name for a triggered one-node workflow. 2. Workflow versions are declarative YAML or JSON. The server normalizes both to one canonical representation before validation and persistence. 3. Workflow versions are immutable. Editing creates a new version. Runs and subworkflow references are pinned before execution begins. 4. The graph remains acyclic. Repetition is represented by a bounded repeat-until policy on a node or subworkflow, never by a back-edge. 5. CEL evaluates edge conditions and repeat predicates against a constrained environment containing node outcomes and declared JSON artifacts. Shell and template execution are not condition languages. 6. Static validation rejects node cycles, recursive subworkflow cycles, duplicate IDs, missing references, invalid artifact contracts, invalid CEL, undeclared resource pools, invalid trigger configuration, and absent/non-positive concurrency caps. 7. Workflow Runs are persisted separately from definitions and versions. Each trigger firing creates a new run with an immutable trigger snapshot and pinned dependency set. 8. Trigger overlap supports skip, queue, and parallel; skip is the default. Trigger state and pending queued firings are durable. 9. Existing cron, interval, PR, child-completion, and turn-completion implementations become workflow triggers rather than a separate loop execution model. 10. Existing loops are migrated to workflow definitions containing one node and the corresponding trigger. Historical loop iterations map to historical run/attempt records as far as their available data permits. 11. Existing loop REST/MCP/UI surfaces remain compatibility wrappers for one release and operate on one-node workflows. New code uses workflow APIs and terminology. 12. The node types in the initial release are agent, command, approval, subworkflow, map, and join. Git, forge, build, and test behavior are composed from command or agent nodes rather than receiving bespoke node types. 13. Agent nodes launch through the existing platform/session service and launcher seams. Fresh sessions are the default; an explicit session-affinity key permits reuse. 14. Agent completion is inferred from the platform session reaching idle or a terminal state. Configured collectors capture final messages, Git diffs, files, or JSON files and validate declared outputs. 15. Command nodes execute through one command-executor boundary that applies working-directory, permission, environment, cancellation, output, and resource policies. They return exit status, stdout/stderr references, and declared collectors. 16. Approval nodes durably wait for an explicit user/API decision. They consume no executor capacity while waiting. 17. Map consumes a JSON array artifact. Each item has a stable key and invokes a pinned subworkflow. Duplicate keys are invalid. Join returns input-ordered values plus per-item outcomes. 18. Join policies are all-success, always, and minimum-success. Failed branches block only dependent nodes by default; unrelated branches continue. Global fail-fast is opt-in. 19. Artifacts are immutable and named. Initial artifact kinds are JSON, text, file reference, diff, and diagnostics. Consumers receive only declared inputs. 20. Artifact metadata and references live in state.db. Large payloads and logs live in a content-addressed store under ocman data storage. The default payload retention is 30 days; run metadata remains. 21. Workflow definitions contain secret references only. Secret resolution happens at the owning host during attempt startup. Known resolved values are redacted from captured output. 22. Every run requires a positive concurrency cap. Cost/token and duration limits are optional and default to unlimited. When configured, limits include descendant map items and subworkflows. 23. Named resource pools provide independent capacities. Nodes atomically acquire all required capacities before starting and release them on terminal attempt state. 24. A run owns a configurable bounded pool of worktree shards for one repository. The first release runs on one host and one repository. 25. Workspace leases support exclusive and path modes. Exclusive is the default. Path leases require declared normalized path scopes and may coexist only when scopes do not overlap. 26. Path-leased mutators cannot execute Git operations that mutate repository-wide state. Commit/push coordinator nodes use exclusive or serialized per-shard capacity. 27. Run, attempt, artifact, resource, shard, and lease records include optional owning-host identity where needed, but the initial scheduler selects only the local host. 28. Scheduler state is durable. Ready-node calculation, lease acquisition, and attempt creation are transactional enough that concurrent ticks cannot dispatch the same attempt twice. 29. Exactly-once side effects are not claimed. After restart, live sessions are reconnected where possible. Interrupted attempts become unknown unless explicitly retry-safe. Unknown attempts pause dependent scheduling pending user resolution. 30. Pause prevents new attempts while allowing active attempts to finish. Cancel requests executor termination; terminal cancellation releases leases and marks unreachable descendants skipped. 31. The scheduler publishes run/node/attempt changes through the existing SSE infrastructure. The browser does not busy-poll for workflow progress. 32. REST, MCP, trigger dispatch, and loop compatibility wrappers all delegate to one workflow service. The service owns definition validation, version activation, run lifecycle, and state transitions. 33. The initial authoring UI accepts YAML/JSON and displays validation errors plus a read-only graph. Drag-and-drop graph editing is deferred. 34. The run UI displays graph state, dynamic map branches, attempts, artifacts, logs, resource waits, shard/path ownership, trigger information, and controls. 35. Frontend workflow affordances use capability gating and remain host/platform agnostic. 36. A reusable migration workflow template ships as an example/preset. It discovers items once, maps a per-item subworkflow, runs two independent diff-only reviewers, joins findings, runs a fixer, validates, and serializes commits. 37. Expensive diagnostics are modeled as command outputs and partitioned artifacts, so downstream agents consume captured slices rather than rerunning discovery. 38. Architecture documentation is updated because this adds a new orchestration seam, persistence model, executor flow, and browser/backend workflow data flow. ## Testing Decisions 1. The primary seam is a workflow-run harness around the workflow service, backed by a real temporary state database, fake agent/command executors, and a controllable clock. 2. Tests submit complete definitions, start or trigger runs, drive scheduler ticks/executor outcomes, and assert externally visible run states, node states, artifacts, leases, attempts, and final outcomes. Internal helper call order is not asserted. 3. Definition tests cover YAML/JSON normalization, immutable versions, DAG validation, CEL compilation, subworkflow pinning, recursive-cycle rejection, artifact contracts, resource references, and required concurrency. 4. Scheduler tests cover readiness, conditional branches, independent failure continuation, fail-fast, all join policies, repeat-until limits, deterministic map ordering, stable-key resume, and bounded concurrency. 5. Resource/workspace tests cover atomic pool acquisition, capacity release, shard bounds, exclusive leases, normalized path overlap, non-overlapping path concurrency, Git-policy enforcement, and serialized commit capacity. 6. Recovery tests persist each interruption point, recreate the service, and verify session reconnection, retry-safe replay, unknown side-effect state, paused dependents, and no duplicate attempt dispatch. 7. Artifact tests use the public workflow-run seam to verify collection, validation, immutability, content addressing, redaction, missing outputs, and retention cleanup while preserving metadata. 8. Trigger tests use a fake clock and existing fake forge/session-status seams to verify firing, overlap skip/queue/parallel, durable trigger state, and one immutable run per firing. 9. Migration tests start from representative pre-workflow databases and verify active, paused, completed, and errored loops become usable one-node workflow definitions with compatible controls and retained history. 10. Thin REST and MCP contract tests prove that definitions, runs, lifecycle controls, approvals, and compatibility loop calls delegate to the same service and enforce localhost/security boundaries. 11. UI tests assert user-visible graph/run behavior from API responses and SSE events: validation errors, state transitions, map expansion, resource waits, logs/artifact links, pause/cancel/approval, and compatibility loop views. 12. End-to-end coverage includes one small migration-template run with fake or fixture executors: discover two items, implement, run two parallel reviews, join, fix, validate, and serialize commits. 13. Tests follow existing repository patterns: table-driven Go tests, temporary SQLite databases, shared fake platform/server integrations, Zustand store tests, Vitest component tests, and stable ARIA/data-testid Playwright locators. 14. New branches, parsers, state transitions, scheduler decisions, permission boundaries, and recovery paths receive tests in the same change so the coverage ratchet does not drop. 15. Performance checks exercise a large synthetic map without launching real agents to verify that ready-node calculation, persisted state, and UI response shapes remain bounded and paginatable. ## Out of Scope - Drag-and-drop or canvas-based workflow authoring in the first release. - Arbitrary graph cycles or unbounded repeat policies. - Multi-repository workflows. - Actual multi-host scheduling, cross-host artifact transfer, or cross-host worktree leasing. The data model only preserves the upgrade path. - A new secrets vault; workflows reference secrets resolved from existing host environment/configuration. - Container or VM sandboxing beyond ocman/OpenCode permission, directory, environment, and process controls. - Exactly-once guarantees for external side effects such as pushes, merges, or forge mutations. - Specialized Git, forge, compiler, test-framework, or code-review node types. - Automatic semantic merging of conflicting patches. - Automatic unrestricted merging to protected or production branches. - Cost or duration limits being mandatory; only concurrency is mandatory. - Replacing OpenCode as the agent runtime in the first release. - Removing compatibility loop surfaces immediately; they remain for one release. ## Further Notes The reference workflow succeeded because a human designed a translation pipeline around agents: shared guidance, stable work units, adversarial review, centralized fixing, controlled Git behavior, captured diagnostics, validation, and aggressive parallelism. The workflow engine should encode those coordination guarantees while leaving implementation judgments to agents. This is intentionally a general DAG engine rather than a hard-coded migration campaign. The first migration template is the tracer example and acceptance proof, not a privileged execution path. The implementation should be delivered as tracer-bullet tickets rather than one large change. A useful first vertical slice is: persist and validate a two-node definition, start a run, execute a fake agent followed by a fake command, stream state, and render the read-only run graph. Dynamic map/join, workspace leasing, triggers/loop migration, and the full migration template can then extend the same run seam.
Author
Owner

Resolved by merged PR #338, which delivered the first-class workflow engine and its tracer-bullet scope.

Resolved by merged PR #338, which delivered the first-class workflow engine and its tracer-bullet scope.
dries closed this issue 2026-07-16 00:56:08 +02:00
Sign in to join this conversation.
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
dries/ocman#311
No description provided.