feat: add agent workflow engine #338

Merged
dries merged 52 commits from feat/agent-workflows into main 2026-07-16 00:48:05 +02:00
Owner

Summary

  • add immutable YAML/JSON workflow DAGs with approvals, commands, agents, subworkflows, maps, joins, triggers, retries, and recovery
  • add resource pools, workspace leases, artifacts, migration from agent loops, and workflow MCP tools
  • add workflow authoring, run inspection, examples, operational controls, and documentation

Testing

  • make lint
  • make test (Go suites and 1500 frontend tests)
## Summary - add immutable YAML/JSON workflow DAGs with approvals, commands, agents, subworkflows, maps, joins, triggers, retries, and recovery - add resource pools, workspace leases, artifacts, migration from agent loops, and workflow MCP tools - add workflow authoring, run inspection, examples, operational controls, and documentation ## Testing - make lint - make test (Go suites and 1500 frontend tests)
- independent dirty state per YAML/JSON buffer with equivalent fixtures
- realistic state/resource-derived node details plus stable key and pinned subworkflow in inspector
- semantic tablist controls for view and source format
- lazy-load the disposable prototype route via Suspense
- readable font sizes; keep state symbols visible (never color-only)
- branch coverage for every pipeline state derivation, route/nav integration, and narrow-layout stacking order; scoped scrollIntoView mock
Attach versioned manual/interval/cron/PR/child- and turn-completion
triggers to active workflow versions. Each firing snapshots the trigger
into an immutable run pinned to its version. Overlap supports
skip/queue/parallel (skip default); queued firings and detection state
are durable and survive restart, draining in deterministic order.

Reuses the existing loop cron/schedule timing, forge poller, and
session-status seams; interval/cron avoid backfiring historical slots,
PR reuses forge detection state, and completion triggers fire once per
idle edge. A 5s server tick evaluates triggers, isolating per-trigger
errors. Firing, run creation, and trigger-state updates commit in one
transaction. The Workflows UI shows trigger state/next/last/overlap/
result and refreshes on a workflow.trigger.updated SSE event. Migration
v23 backfills a manual trigger and run snapshot for pre-existing
workflows. Loops are intentionally not migrated (#325).
Make node outputs durable, immutable typed artifacts while keeping large
payloads out of SQLite.

- Content-addressed BlobStore (internal/workflows/blobstore.go) dedups
  identical payloads on disk under <ocman data dir>/workflow-artifacts;
  atomic writes, idempotent removal, ErrPayloadMissing after cleanup.
- state.db migration v26: workflow_artifact metadata table (typed kind,
  content hash, size, retention expiry, payload_deleted audit flag) plus
  workflow_version.retention_days. content_hash is non-unique so shared
  payloads keep independent metadata/retention rows.
- Artifact kinds JSON/text/file/diff/diagnostics mapped from command and
  agent collectors; producers publish once at successful completion
  (immutable), consumers read only declared-dependency inputs via
  ConsumableArtifacts.
- Definitions carry secret references (name+env) only; values resolve
  from host env at execution time, are injected into command env, and
  known resolved values are redacted from logs and artifact payloads.
- Default 30-day payload retention with per-workflow override; an hourly
  cleanup sweep drops expired payloads while preserving all metadata.
- REST: GET run artifacts list + artifact payload download (410 Gone
  when cleaned); frontend RunView inspects metadata and links retained
  downloads.

Tests cover dedup, content addressing, immutability, command+agent
redaction, retention cleanup preserving metadata, missing payloads,
restart durability, and the REST endpoints.
Add a required run-concurrency cap and optional named resource pools to the
workflow engine. Definitions may declare pools with positive capacities and
nodes may request units from multiple pools. The scheduler acquires every
requested pool plus one implicit run-concurrency slot atomically (all-or-
nothing) before an attempt goes active, so competing ready nodes cannot
oversubscribe the run cap or any pool. Held capacity is recorded in a durable
workflow_resource_lease table (migration v26) and released only when an
attempt settles (success, failure, cancel, budget stop, or restart recovery),
which also survives restart reconciliation and is surfaced per-pool (held +
waiting nodes) in the run detail and UI. Optional cost/token/duration limits
aggregate descendant agent work; omission means unlimited.
One-time migration (v26) copies every persisted loop into a one-node
workflow definition/version with the matching trigger and preserved loop
policies, and maps each loop iteration to a historical workflow run +
node attempt. loop_workflow_map keeps loop identifiers resolvable. The
copy is idempotent (skips already-mapped loops) and interrupted-safe
(runs inside the migration transaction). Old and fresh databases both
migrate cleanly.

The original loops tables and the loop REST/MCP/UI surfaces are left
intact as compatibility wrappers for one release; architecture docs note
the planned removal timing.
Add a run-owned bounded pool of worktree shards with durable
workspace leases layered on the #319 resource-pool acquisition path.

- Exclusive leases (default) keep a shard single-writer; path leases
  normalize and compare declared scopes so disjoint scopes share a
  shard while ancestor/exact overlaps serialize.
- Path-leased command nodes are denied repository-wide git mutation
  (stash/reset/checkout/commit/push and configurable extras); commit
  coordinators take exclusive or serialized per-shard capacity.
- Leases are acquired in the same transaction as pool capacity,
  linked to attempts, carry an optional owning host, released only
  after the attempt settles, and are surfaced in the run detail + UI.
- Shards are created host-locally through the existing git worktree
  service; migration v29 adds workflow_workspace_lease.

Tests cover path normalization, ancestor/exact/disjoint overlap, git
restrictions, cleanup on completion/failure/cancel, shard exhaustion,
serialized commit capacity, and durability.
Replace wall-clock timing with observable state so
TestCommandConcurrencyCapAndFailureCancelSiblings is deterministic:

- cap: assert the run-concurrency pool holds 1 unit with "two"
  waiting, then prove serialized dispatch (two starts only after one
  releases) instead of guessing with time.After(100ms).
- failure cancels sibling: "fail" waits for the sibling's pid file
  before failing (no fixed sleep .2), and the grandchild reap is
  polled (process gone) instead of a one-shot Kill check that raced
  with async reaping by init.

No production change; the sibling process-group SIGKILL is correct,
only the test assertion assumed instant reap.
fix: unify queued shell commands
Some checks failed
CI / Build Desktop (macOS arm64) (pull_request) Successful in 2m11s
CI / Frontend (pull_request) Failing after 6m29s
CI / Coverage Ratchet (pull_request) Failing after 7m13s
CI / Backend (pull_request) Failing after 7m40s
CI / Build (pull_request) Successful in 4m24s
CI / Playwright E2E (pull_request) Failing after 6m34s
CI / Semantic Tag (pull_request) Has been skipped
9a104e6c50
fix: stabilize workflow CI checks
Some checks failed
CI / Build Desktop (macOS arm64) (pull_request) Successful in 1m58s
CI / Frontend (pull_request) Failing after 5m46s
CI / Coverage Ratchet (pull_request) Failing after 7m26s
CI / Backend (pull_request) Successful in 7m31s
CI / Build (pull_request) Successful in 2m41s
CI / Playwright E2E (pull_request) Failing after 6m10s
CI / Semantic Tag (pull_request) Has been skipped
e17646eac8
fix: stabilize frontend workflow checks
Some checks failed
CI / Build Desktop (macOS arm64) (pull_request) Successful in 1m54s
CI / Backend (pull_request) Successful in 3m38s
CI / Frontend (pull_request) Successful in 4m30s
CI / Coverage Ratchet (pull_request) Failing after 7m7s
CI / Build (pull_request) Successful in 4m23s
CI / Playwright E2E (pull_request) Successful in 6m57s
CI / Semantic Tag (pull_request) Successful in 4s
8520373f98

Coverage ratchet: pass

Suite Baseline This PR Δ
go 72.80% 74.40% +1.60
frontend 62.94% 64.28% +1.34

Tolerance: -0.1%. Baseline stored on gh-pages.

<!-- coverage-ratchet --> ### Coverage ratchet: ✅ pass | Suite | Baseline | This PR | Δ | | |---|---|---|---|---| | go | 72.80% | 74.40% | +1.60 | ✅ | | frontend | 62.94% | 64.28% | +1.34 | ✅ | _Tolerance: -0.1%. Baseline stored on `gh-pages`._
feat: retire workflow output collectors (#343)
Some checks failed
CI / Build Desktop (macOS arm64) (pull_request) Successful in 1m47s
CI / Frontend (pull_request) Successful in 5m43s
CI / Backend (pull_request) Successful in 6m45s
CI / Coverage Ratchet (pull_request) Failing after 9m25s
CI / Build (pull_request) Successful in 3m57s
CI / Playwright E2E (pull_request) Successful in 6m27s
CI / Semantic Tag (pull_request) Successful in 4s
8c44a26411
test: cover workflow persistence lifecycle (#343)
Some checks failed
CI / Build Desktop (macOS arm64) (pull_request) Successful in 2m3s
CI / Backend (pull_request) Has been cancelled
CI / Coverage Ratchet (pull_request) Has been cancelled
CI / Frontend (pull_request) Has been cancelled
CI / Playwright E2E (pull_request) Has been cancelled
CI / Build (pull_request) Has been cancelled
CI / Semantic Tag (pull_request) Has been cancelled
47fcc461ec
ci: run tmux tests in coverage job
Some checks failed
CI / Coverage Ratchet (pull_request) Failing after 18s
CI / Build Desktop (macOS arm64) (pull_request) Successful in 1m53s
CI / Backend (pull_request) Has been cancelled
CI / Playwright E2E (pull_request) Has been cancelled
CI / Frontend (pull_request) Has been cancelled
CI / Build (pull_request) Has been cancelled
CI / Semantic Tag (pull_request) Has been cancelled
804e4281c0
fix(ci): install tmux without sudo
Some checks failed
CI / Build Desktop (macOS arm64) (pull_request) Successful in 2m9s
CI / Coverage Ratchet (pull_request) Failing after 4m28s
CI / Frontend (pull_request) Successful in 5m34s
CI / Backend (pull_request) Successful in 7m30s
CI / Build (pull_request) Successful in 3m23s
CI / Playwright E2E (pull_request) Successful in 5m58s
CI / Semantic Tag (pull_request) Successful in 4s
f79441e14b
fix(ci): set terminal type for tmux tests
Some checks failed
CI / Build Desktop (macOS arm64) (pull_request) Successful in 1m46s
CI / Coverage Ratchet (pull_request) Failing after 3m50s
CI / Frontend (pull_request) Successful in 4m35s
CI / Backend (pull_request) Successful in 4m46s
CI / Build (pull_request) Successful in 3m34s
CI / Playwright E2E (pull_request) Successful in 5m59s
CI / Semantic Tag (pull_request) Successful in 4s
41122149af
test: cover workflow core paths (#343)
Some checks failed
CI / Build Desktop (macOS arm64) (pull_request) Successful in 1m49s
CI / Frontend (pull_request) Successful in 6m0s
CI / Backend (pull_request) Successful in 7m55s
CI / Coverage Ratchet (pull_request) Successful in 9m8s
CI / Build (pull_request) Has been cancelled
CI / Playwright E2E (pull_request) Has been cancelled
CI / Semantic Tag (pull_request) Has been cancelled
173fd30b41
feat: add conversation metadata controls and timing
All checks were successful
CI / Build Desktop (macOS arm64) (pull_request) Successful in 1m42s
CI / Frontend (pull_request) Successful in 5m51s
CI / Backend (pull_request) Successful in 7m57s
CI / Coverage Ratchet (pull_request) Successful in 9m6s
CI / Build (pull_request) Successful in 3m30s
CI / Playwright E2E (pull_request) Successful in 6m45s
CI / Semantic Tag (pull_request) Successful in 4s
CI / Build Desktop (macOS arm64) (push) Successful in 1m41s
CI / Frontend (push) Successful in 6m28s
CI / Backend (push) Successful in 8m54s
CI / Coverage Ratchet (push) Successful in 10m2s
CI / Build (push) Successful in 3m10s
CI / Playwright E2E (push) Successful in 6m32s
CI / Semantic Tag (push) Successful in 8s
Release / Build Frontend (push) Successful in 1m5s
Release / Build Desktop (macOS arm64) (push) Successful in 1m35s
Release / Build darwin/amd64 (push) Successful in 3m33s
Release / Build linux/amd64 (push) Successful in 4m24s
Release / Build darwin/arm64 (push) Successful in 5m4s
Release / Build linux/arm64 (push) Successful in 2m34s
Release / Publish Release (push) Successful in 53s
ca553f69fa
dries merged commit ca553f69fa into main 2026-07-16 00:48:05 +02:00
dries deleted branch feat/agent-workflows 2026-07-16 00:48:05 +02:00
Sign in to join this conversation.
No description provided.