Flows YAML — engine + ops (loader, expression, registry, executor, ops) #1075
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks#1075
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
User story
As a maintainer, I want a complete YAML flow engine — file loader, expression language, op registry, linear executor, internal triggers, and the full set of operations — so that the new dispatch path can run any of the 9 default flows end-to-end behind a feature flag.
Resolves audit findings §1.1.1 (flow + legacy collision), §1.1.2 (injection bundle bloat), §1.1.3 (DAG model unused), §1.1.4 (string-based filters), §1.1.6 (no async hooks).
Acceptance criteria
Loader + file watcher
apps/server/src/domain/flows-yaml/loader.tsparses every*.ymlunderflows/defaults/andflows/custom/at boot;customshadowsdefaultsby name.chokidar(or bun fs.watch) with 200ms debounce; emitsflow.changed { name }on add/change/unlink.POST /flows/reloadforces full re-scan, returns{ loaded, errors[] }.Expression language
flows-yaml/expr/parser.ts.&&,||,!,==,!=,in,matches,<,<=,>,>=. Literals: string, number, bool, null. Member access. Function call.has_label,has_any_label,has_all_labels,author_is,repo_matches,comment_matches,event_action.event.*,steps.<id>.outputs.<key>,env.<KEY>.${{ expr }}interpolation inwith:arg values; whole-string returns native type, embedded stringifies.Op registry
Operationtype:name,argsSchema(Zod),outputsSchema(Zod),run(ctx, args),deps(capability keys).flows-yaml/ops/<name>.ts; index aggregates.buildOpContext(op, env)returns only declared capabilities; tests substitute fakes per op.outputsSchemabefore storing understeps.<id>.outputs.Linear executor + parallel block
flows-yaml/executor.tsruns steps in order: evaluateif:→ resolve interpolations → run op → recordflow_node_runsrow.continue-on-error: true;if: false→skipped, flow continues.steps[].parallel: [...]runs concurrently withPromise.all; outputs visible viasteps.<parent>.outputs.<sub>.….concurrency.group(expression-evaluated) gates with mutex;cancel-in-progress: trueaborts in-flight run.prioritydesc thennameasc.Audit
flow_runsopens on start, closes on terminal status (completed/failed/cancelled).flow_node_runsrows hold step status, duration, output, error.flow_node_runs.node_*→step_*.flow_runs.internal_trigger_sourcerecords parent flow/task id.Internal triggers
task.completed/task.failed/task.timed_outon terminal task state.flow.completed/flow.failed.pr.mergedafter Forgejo confirms merge (distinct frompull_request.closed).on: { task: [completed], pr: [merged], flow: [failed] }consumes them.Ops to author (one file per op)
dispatch,dispatch_breakdown,resolve_agent,dedup_check,dedup_record,dedup_cancel_stale,guard_reviewer_dispatch,guard_author_dispatch,detect_change_request,set_label,remove_label,comment.cancel_tasks(replaceshandleIssueUnassignedatwebhook.ts:296),pr_dependency_markers(replaceshandlePrDependencyMarkersatwebhook.ts:325),propagate_dependencies(replaceshandleStackedRebaseCascadeatwebhook.ts:333and the legacyhandleIssueClosedcallback).defaultArgInjectionsbundle..test.tsagainst a fake ctx; recorded fixture replays produce identical observable side effects to the legacy path.Tests
continue-on-error, parallel concurrency,concurrencymutex.on: task.completedflow runs after task.Out of scope
References
docs/specs/flows-yaml.md§3, §5.2, §6, §7, §8, §9, §10.apps/server/src/domain/flows/{flow-dispatch,executor,registry,validation}.ts, inline imperative calls atapps/server/src/http/webhook.ts:296–341,defaultArgInjectionsatflow-dispatch.ts:113–218,matchesFiltersatflow-dispatch.ts:356–407.