You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[feat] Add DAG conditions and composable sub-workflows #1078
Command inputs are available only through declared env: mappings; command strings are not template-expanded, and an input such as $(...) is not executed by the shell.
Verification
Focused graph, join, propagation, template, command-env, input/output, and ending tests; build/typecheck/lint; one command-only real-CLI E2E across a file sub-workflow. Update both workflow guide languages.
How do inputs reach a command step? #818 had an env: key, so values go in as variables and never into the shell string. I don't see it here, and #1082 mentions command inputs shadowing secrets, so maybe you have another way in mind. If templates are meant to expand inside command too, that's the case #818 tried to avoid: an input like $(...) would run as shell.
Inputs reach a command step only through an env: key. Those values become environment variables; the command string is not template-expanded, so an input such as $(...) is not executed by the shell.
#1082’s secret-shadowing rule applies to those environment-variable names.
References by name (workflow: review-and-fix here, agent: and prompt_file from #1077) could resolve through the namespace rule the other resources already follow in src/namespace-resolver.ts. A namespace item replaces the root item of the same name, and one name in two active namespaces is a conflict. With that rule in place, #1101 adding workflows/<ns>/ does not change what an existing reference points to.
I'm taking this one. A few points the ticket leaves open or that I narrowed while planning it:
needs after the first step is required.needs: [] declares another root. Without this, test then merge with no needs would merge after a failed test. Every example in Discussion: Workflow Runtime Integration #818 already follows the rule, and relaxing it later breaks nothing.
Recovery. A stopped dependency skips a step unless its if reads that dependency, and the if has to read every stopped one. Other branches keep settling. The run takes the outcome of the end it reached; with no end, it fails if any step stopped.
File sub-workflows resolve by name from the validated cache. Pull checks across files for a missing child or recursion and reports it on the parent's step. start snapshots the whole tree into the run, and a child with no runnable version fails start by name, so nothing falls back to an older child. Scanning workflows/<ns>/ stays with [feat] Make all distributed resources pushable with project and role namespaces #1101. Once the sync applies the namespace rule, references follow it with no engine change.
Top-level inputs:teamai workflow start <name> --inputs '<json>', checked before the run exists (invalid_inputs). Every declared input is required.
Expressions only read inputs and steps. The falsy values are false, null, 0 and ''. == is strict and case-sensitive, and a missing path is null. They can only read steps the current step transitively needs.
Templates are only {{ inputs.* }} and {{ steps.* }}, plus {{ with.* }} on a prompt step that declares with; any other {{ }} in a prompt stays as written, and so does ${{ }}. A with value that is a single template keeps its JSON type. env values are strings. A template inside command fails pull and points to env:.
Endings.end finishes the innermost workflow. An inline workflow can declare ends and outputs. done also reports the end a top-level run reached, so CI can tell how it finished.
For [feat] Add bounded loops and parallel for_each #1079: sub-workflow steps are created when their parent becomes ready and are addressed by path (check.classify, with room for [n]). Leases stay one at a time.
When a workflow step has end, it finishes the parent with that end once the child succeeds. My end-only check would have finished it without running the child.
- id: triageworkflow: triage-issueend: ready # runs triage-issue first; the parent ends `ready` only if it succeeds
Pull validates outputs, including fields against the step's output. The issue asks for it and my plan had dropped it.
outputs:
kind: steps.clasify.outputs.kind # typo: pull fails instead of the parent always reading null
inputs.* reads in if and templates are checked against the declared inputs.
inputs: { issue: number }
...
if: inputs.isue == 42 # typo: pull fails instead of the step being skipped silently
env values written as YAML numbers or booleans are accepted and passed as text.
A failed step does not stop the run. Independent branches keep settling, and the run fails at the end unless it reached an end.
- id: testcommand: npm test # fails
- id: docsneeds: []command: npm run docs # #1128: never runs. #1129: runs, then the run ends failed
Sub-workflows are snapshotted with the parent at start. A pull during a run cannot change or remove a child halfway through.
t0 start ship → #1129 snapshots ship + check-issue v1
t1 pull brings check-issue v2
t2 ship reaches its `check` step → #1129 runs v1; #1128 loads v2, or fails if v2 is invalid
Every step after the first declares needs, and needs: [] declares another root. When implicit order is mixed with roots, merge can run after a failed test as soon as any step declares needs.
- id: testcommand: npm test
- id: notifyneeds: [test]if: steps.test.outcome == 'failed'command: ./notify.sh
- id: merge # #1128: a root, so it merges after a failed test. #1129: fails pull until it declares needscommand: gh pr merge
Only {{ inputs.* }} and {{ steps.* }} are templates, plus {{ with.* }} on a prompt step that declares with. Any other {{ }}, and anything written as ${{ }}, stays literal, so prompts that quote GitHub Actions or Jinja keep working. A template inside command fails pull and points to env:.
Add `token: ${{ secrets.GH_TOKEN }}` to the job for issue {{ inputs.issue }}.
→ #1129: "...${{ secrets.GH_TOKEN }}... issue 42" #1128: "...token: $..."
teamai workflow start <name> --inputs '<json>'. The inputs are checked before the run exists, so a top-level workflow with inputs can run from the CLI.
teamai workflow start ship --inputs '{"issue":"42"}'
→ {"error":{"code":"invalid_inputs","message":"inputs.issue must be a number"}} exit 1, no run created
Activity
SaulMoro commentedon Oct 13, 2026
How do inputs reach a
commandstep? #818 had anenv:key, so values go in as variables and never into the shell string. I don't see it here, and #1082 mentions command inputs shadowing secrets, so maybe you have another way in mind. If templates are meant to expand insidecommandtoo, that's the case #818 tried to avoid: an input like$(...)would run as shell.hsuchifeng commentedon Oct 14, 2026
Inputs reach a
commandstep only through anenv:key. Those values become environment variables; the command string is not template-expanded, so an input such as$(...)is not executed by the shell.#1082’s secret-shadowing rule applies to those environment-variable names.
SaulMoro commentedon Oct 14, 2026
References by name (
workflow: review-and-fixhere,agent:andprompt_filefrom #1077) could resolve through the namespace rule the other resources already follow insrc/namespace-resolver.ts. A namespace item replaces the root item of the same name, and one name in two active namespaces is a conflict. With that rule in place, #1101 addingworkflows/<ns>/does not change what an existing reference points to.SaulMoro commentedon Oct 14, 2026
@hsuchifeng Can I assign this ticket to myself?
SaulMoro commentedon Oct 14, 2026
I'm taking this one. A few points the ticket leaves open or that I narrowed while planning it:
needsafter the first step is required.needs: []declares another root. Without this,testthenmergewith noneedswould merge after a failed test. Every example in Discussion: Workflow Runtime Integration #818 already follows the rule, and relaxing it later breaks nothing.ifreads that dependency, and theifhas to read every stopped one. Other branches keep settling. The run takes the outcome of the end it reached; with no end, it fails if any step stopped.startsnapshots the whole tree into the run, and a child with no runnable version failsstartby name, so nothing falls back to an older child. Scanningworkflows/<ns>/stays with [feat] Make all distributed resources pushable with project and role namespaces #1101. Once the sync applies the namespace rule, references follow it with no engine change.teamai workflow start <name> --inputs '<json>', checked before the run exists (invalid_inputs). Every declared input is required.inputsandsteps. The falsy values arefalse,null,0and''.==is strict and case-sensitive, and a missing path isnull. They can only read steps the current step transitively needs.{{ inputs.* }}and{{ steps.* }}, plus{{ with.* }}on a prompt step that declareswith; any other{{ }}in a prompt stays as written, and so does${{ }}. Awithvalue that is a single template keeps its JSON type.envvalues are strings. A template insidecommandfails pull and points toenv:.endfinishes the innermost workflow. An inline workflow can declareendsandoutputs.donealso reports the end a top-level run reached, so CI can tell how it finished.check.classify, with room for[n]). Leases stay one at a time.skipped,end).env, sub-workflow paths and recovery.SaulMoro commentedon Oct 14, 2026
Thanks @hsuchifeng for #1128. I went through it against the plan for this issue, and it caught gaps that I have now folded into #1129.
Taken from #1128
When a
workflowstep hasend, it finishes the parent with that end once the child succeeds. My end-only check would have finished it without running the child.Pull validates
outputs, including fields against the step'soutput. The issue asks for it and my plan had dropped it.inputs.*reads inifand templates are checked against the declared inputs.envvalues written as YAML numbers or booleans are accepted and passed as text.Templates nested inside
withobjects and arrays are rendered, each string by the same rule.withis no longer silently ignored on a step that is not a workflow step. Following #818, I went one step further:withnames values for that prompt, so one prompt file can serve several workflows;withfails pull and points toenv:.Where #1129 differs, and why
A failed step does not stop the run. Independent branches keep settling, and the run fails at the end unless it reached an end.
Sub-workflows are snapshotted with the parent at
start. A pull during a run cannot change or remove a child halfway through.Every step after the first declares
needs, andneeds: []declares another root. When implicit order is mixed with roots,mergecan run after a failedtestas soon as any step declaresneeds.Only
{{ inputs.* }}and{{ steps.* }}are templates, plus{{ with.* }}on a prompt step that declareswith. Any other{{ }}, and anything written as${{ }}, stays literal, so prompts that quote GitHub Actions or Jinja keep working. A template insidecommandfails pull and points toenv:.teamai workflow start <name> --inputs '<json>'. The inputs are checked before the run exists, so a top-level workflow with inputs can run from the CLI.