Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
353417a
fix(workflow): record bad_session in run records
SaulMoro Oct 20, 2026
550eb7e
fix(workflow): reset every tool's agent configuration to the run base
SaulMoro Oct 20, 2026
0866894
fix(workflow): reject reported changes that do not match the lease
SaulMoro Oct 20, 2026
d254464
fix(workflow): record the run base at workflow start
SaulMoro Oct 20, 2026
05fabd3
fix(workflow): observe a workspace prompt through the worktree's config
SaulMoro Oct 20, 2026
c01ae70
fix(workflow): stage only agent paths that exist
SaulMoro Oct 20, 2026
37e348c
fix(workflow): lease a ready step past one this driver holds back
SaulMoro Oct 21, 2026
0bfa3db
fix(workflow): run a read-only step inside a copy in that copy
SaulMoro Oct 21, 2026
ba955d6
fix(workflow): refuse agent paths under a symlink and keep .github wo…
SaulMoro Oct 21, 2026
fbc539f
fix(workflow): put a read-only copy back on its branch and keep what …
SaulMoro Oct 21, 2026
662c5fc
fix(workflow): say a reader's changes were NOT saved when keeping the…
SaulMoro Oct 21, 2026
5c4ba8c
fix(workflow): refuse later steps in a copy a reader could not put back
SaulMoro Oct 21, 2026
89a18ae
fix(workflow): leave a submodule a reader edited untouched instead of…
SaulMoro Oct 21, 2026
2baf740
fix(workflow): check a read copy is really back before saying it was …
SaulMoro Oct 21, 2026
6d45ecf
fix(workflow): restore a reader's submodules with the checkout procedure
SaulMoro Oct 21, 2026
3b644d3
fix(workflow): compare a reader's HEAD with the full branch ref
SaulMoro Oct 21, 2026
b40a993
fix(workflow): name the kept ref when a read-only command that change…
SaulMoro Oct 21, 2026
379608f
fix(workflow): refuse an existing workflow worktree that is not on it…
SaulMoro Oct 21, 2026
85ffcb9
fix(workflow): point a symlinked agent path at the fix that can work
SaulMoro Oct 21, 2026
4da1dfd
docs(workflow): say an unrestored read copy blocks later steps until …
SaulMoro Oct 21, 2026
315b583
fix(workflow): keep a reader's submodule commit on its kept ref
SaulMoro Oct 21, 2026
4116576
refactor(workflow): move putting a read copy back into worktree.ts
SaulMoro Oct 21, 2026
6f3202d
fix(workflow): put a read copy back to the status it had when the rea…
SaulMoro Oct 21, 2026
fb93a66
fix(workflow): block a read copy's put-back only on repositories the …
SaulMoro Oct 21, 2026
55ddd8b
fix(workflow): keep a reader's submodule commits reachable after the …
SaulMoro Oct 21, 2026
8f65ab6
fix(workflow): mark a read copy when its reader starts, so a killed d…
SaulMoro Oct 21, 2026
f8bda44
fix(workflow): recreate a deleted workflow copy, and refuse one that …
SaulMoro Oct 21, 2026
f62f164
docs(workflow): say a read copy goes back to how the reader found it,…
SaulMoro Oct 21, 2026
f01d278
refactor(workflow): let putBackCopy own the untouched case and the ke…
SaulMoro Oct 21, 2026
cef36e5
test(workflow): a reader that deletes a dirty submodule leaves the co…
SaulMoro Oct 21, 2026
dfc6920
fix(workflow): say a read copy goes back as the step found it, and wh…
SaulMoro Oct 21, 2026
b6961d5
fix(workflow): keep nested submodule commits, and put back only the s…
SaulMoro Oct 21, 2026
876d906
fix(workflow): name kept submodule commits by commit id, and prune wh…
SaulMoro Oct 21, 2026
4f071ba
fix(workflow): keep a copy while a step that has not started, or a lo…
SaulMoro Oct 21, 2026
2fe1bb4
fix(workflow): see submodule changes that ignore settings hide from a…
SaulMoro Oct 21, 2026
afff388
fix(workflow): fail a writer that left its branch before committing, …
SaulMoro Oct 21, 2026
dedbc1b
fix(workflow): lease and start steps in other checkouts while a headl…
SaulMoro Oct 21, 2026
cd90927
fix(workflow): fail a read-only step that moves HEAD to another branc…
SaulMoro Oct 21, 2026
ae8b860
fix(workflow): keep forced branch colors and external diffs out of th…
SaulMoro Oct 21, 2026
ba957de
fix(workflow): say where a failed push left a step's commits and how …
SaulMoro Oct 21, 2026
3ef4e18
fix(workflow): do not restore agent files under a parent a step repla…
SaulMoro Oct 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/dev/workflow-engine.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/guide/workflow-advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ Each call to `next` does this, in order:

1. It settles the run. If the run has ended, it answers `done`.
2. It hands back an open lease of this driver that `running` leaves out. A restarted driver sends `"running":[]` and gets its open leases back, one per call.
3. It leases the first ready step in file order. The steps of a sub-workflow take the place of the step that runs it.
4. When every ready step is leased, it answers `wait`, and `reason` names the first open lease.
3. It leases the first ready step in file order. The steps of a sub-workflow take the place of the step that runs it. A step that needs a workspace mode `driver.can.workspace` does not list, or a workspace another driver opened, is held back, and the next ready step is leased.
4. When every ready step is leased, it answers `wait`, and `reason` names the first open lease. When every ready step is held back, `reason` names each one, separated by `; `.

A driver that sends no `running` gets its open lease back on every call, so it runs one step at a time.

Expand Down Expand Up @@ -632,7 +632,7 @@ Without that capability, a `next` request that can take a lease applies this tab

Every decision without HITL and `hitl_required` failure logs a warning to stderr. Set `workflows.auto_approve` beside `workflows.max_parallel` in local config, or pass `--auto-approve` to `start --driver local` or `resume` for that process only. JSON `next` cannot set this override. The local driver declares HITL only when stdin and stderr are TTYs. It asks on stderr, accepts a choice followed by an optional note, and keeps polling while the question is open. An answer from elsewhere or a cancellation closes that question. Questions wait while an interactive tool owns the terminal.

`interactive: true` needs `prompt` or `prompt_file`. A driver without HITL fails it with `hitl_required`. The local driver opens the tool here in the workspace with inherited stdio and the prompt as its first message. Repository edits by the person are kept, and the step succeeds. An interactive step that sets `workspace: read-only` is checked like a command: a checkout change fails `read_only_dirty` and the edits stay. An interactive step in its own workspace can still change that workspace. Headless prompts remain read-only. Terminal Ctrl-C and Ctrl-\ go to the interactive tool; SIGTERM and SIGHUP stop it and the driver. A result file is optional when the interactive tool exits successfully without outputs. The driver polls every two seconds during prompts and stops a cancelled tool.
`interactive: true` needs `prompt` or `prompt_file`. A driver without HITL fails it with `hitl_required`. The local driver opens the tool here in the workspace with inherited stdio and the prompt as its first message. Repository edits by the person are kept, and the step succeeds. An interactive step that sets `workspace: read-only` is checked like a command: a checkout change fails `read_only_dirty` and the edits stay. Inside a workspace an enclosing step opened, they are kept on `refs/teamai/kept/<attempt>` and removed from the workspace. An interactive step in its own workspace can still change that workspace. Headless prompts remain read-only. Terminal Ctrl-C and Ctrl-\ go to the interactive tool; SIGTERM and SIGHUP stop it and the driver. A result file is optional when the interactive tool exits successfully without outputs. The driver polls every two seconds during prompts and stops a cancelled tool.

## Retry, cancel, and resume

Expand Down
12 changes: 7 additions & 5 deletions docs/guide/workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,29 +121,31 @@ An agent-specific entry wins over `default`. `claude`, `codex`, and `opencode` a
teamai workflow start build --driver local
```

`--driver local` runs a step with no `workspace` in the current checkout. A command there is read-only: if it exits 0 and changed repository files, their permissions, a symlink target, the destination of a staged rename, a file inside a dirty submodule, the index, or HEAD, the step fails and leaves that change in place. This includes a change inside an existing untracked directory; leaving that directory unchanged does not fail the step. A step with `workspace: new` changes files on its own branch. See [Workspaces](#workspaces). Steps that share the current checkout run one at a time. Steps in different worktrees run together up to [`workflows.max_parallel`](workflow-advanced.md#run-local-commands-in-parallel). Each attempt uses `result.json` under the scope's TeamAI data directory, outside the checkout. The driver calls the same `next` and `report` operations shown below:
`--driver local` runs a step with no `workspace` in the current checkout, unless an enclosing step opened a workspace. A command there is read-only: if it exits 0 and changed repository files, their permissions, a symlink target, the destination of a staged rename, a file inside a dirty submodule, the index, or HEAD, the step fails and leaves that change in place. This includes a change inside an existing untracked directory; leaving that directory unchanged does not fail the step. A step with `workspace: new` changes files on its own branch. See [Workspaces](#workspaces). Steps that share the current checkout run one at a time. Steps in different worktrees run together up to [`workflows.max_parallel`](workflow-advanced.md#run-local-commands-in-parallel). Each attempt uses `result.json` under the scope's TeamAI data directory, outside the checkout. The driver calls the same `next` and `report` operations shown below:

```text
teamai workflow start build
teamai workflow next <run> --json '{"driver":{"kind":"local","machine":"host","workspace":"/work"}}'
teamai workflow report <run> --json '{"attempt":"<attempt>","revision":1,"outcome":"success","driver":{"kind":"local","machine":"host","workspace":"/work"}}'
```

`outcome` is `success`, `failed`, or `released`. `released` puts the step back to pending. It does not use up a retry. A report for an attempt that was already settled returns `repeated_report`, even when the revision is old. The run keeps recent settled attempts for that check. An older attempt uses the other checks: an old revision returns `stale_revision`, and a current revision that is not the leased attempt returns `wrong_attempt`. Another driver returns `wrong_driver`. A payload field the CLI does not read, such as a misspelled `tokens` written as `tokenz` or a field from a newer protocol version, is accepted but reported back: the success envelope of `next`, `report`, and `respond --json` carries `ignored` with those names — top-level fields first, then `driver.<field>` names — names only and never values. The key is left out when nothing was ignored, and an error envelope stays as it is.
`outcome` is `success`, `failed`, or `released`. `released` puts the step back to pending. It does not use up a retry. A report for an attempt that was already settled returns `repeated_report`, even when the revision is old. The run keeps recent settled attempts for that check. An older attempt uses the other checks: an old revision returns `stale_revision`, and a current revision that is not the leased attempt returns `wrong_attempt`. Another driver returns `wrong_driver`. `changes` whose `branch` or `base` differs from the lease returns `invalid_report`, and the step stays leased. A read-only step in a workspace reports `read` (`branch`, `base`, and the `commit` it read) in place of `changes`. `changes` from it, a success without `read`, `read` from any other step, or `read` that differs from the lease returns `invalid_report` too. A payload field the CLI does not read, such as a misspelled `tokens` written as `tokenz` or a field from a newer protocol version, is accepted but reported back: the success envelope of `next`, `report`, and `respond --json` carries `ignored` with those names — top-level fields first, then `driver.<field>` names — names only and never values. The key is left out when nothing was ignored, and an error envelope stays as it is.

The run is stored in this scope's TeamAI data directory. The command exits when it finishes. A local run that ends `failed` or `cancelled` exits with status 1. No workflow process keeps running.

Use `workflow respond <run> <step> <choice>` for a waiting HITL request, `workflow retry <run> <step>` for a failed or stuck step, `workflow cancel <run> [step]` to stop work, and `workflow resume <run>` to continue locally. `next` answers `lease`, `hitl`, `wait`, or `done`. See [HITL](workflow-advanced.md#hitl-human-in-the-loop) and [Retry, cancel, and resume](workflow-advanced.md#retry-cancel-and-resume).

## Workspaces

A command with no `workspace` is read-only, even inside a workspace a parent opened. It runs in the current checkout. If it exits 0 and changed repository files, their permissions, a symlink target, the destination of a staged rename, a file inside a dirty submodule, the index, or HEAD, the step fails with `read_only_dirty` and leaves that change in place. A headless prompt with no `workspace` is read-only in the same way, unless an enclosing workflow or `for_each` opens a workspace. An interactive prompt keeps the person's edits, unless that prompt sets `workspace: read-only`.
A command with no `workspace` is read-only. Outside a workspace it runs in the current checkout. If it exits 0 and changed repository files, their permissions, a symlink target, the destination of a staged rename, a file inside a dirty submodule, the index, or HEAD, the step fails with `read_only_dirty` and leaves that change in place. A headless prompt with no `workspace` is read-only in the same way, unless an enclosing workflow or `for_each` opens a workspace. An interactive prompt keeps the person's edits, unless that prompt sets `workspace: read-only`. Submodule changes count even when `submodule.<name>.ignore` or `diff.ignoreSubmodules` hides them from `git status`: a read-only step that makes one fails, and a step that writes commits the submodule's new commit.

`workspace: new` runs the step in its own git worktree, on a branch named for this run, step, and pass. The step commits what it changed. The current checkout is not switched, reset, or cleaned. A later step that lists it in `needs` starts from the first of those branches and merges the rest, in `needs` order. A merge conflict stops there. A command fails with `merge_conflict`, and the message names the branch and the files. A prompt is told which files still conflict and which branches were not merged.
Inside a workspace an enclosing step opened, a read-only step runs in that workspace, so a test sees the change it follows. It reads the branch its `needs` changed, or the enclosing branch when they changed none. When they changed more than one branch, it fails with `workspace_base`: add a step that merges them, and need that step. It runs one at a time with the workspace's other steps, on the same base agent files, and only a driver that declares `continue` and owns that workspace takes it. It commits nothing. Afterwards the driver puts the workspace back as the step found it: if the step changed it or left its branch, what it changed is kept on `refs/teamai/kept/<attempt>`, and the submodule commits it made under `refs/teamai/kept-modules/<attempt>/`. The worktree and the submodules the step changed are then reset to the commit it read and its untracked files are removed, and an exit 0 still fails with `read_only_dirty`. Ignored files stay in place. A submodule the step changed that has uncommitted or untracked files cannot be kept, so then nothing is reset. When the workspace is not put back, the step fails with `workspace_base`. The workspace is marked when the step starts, so later steps there fail, even after the driver was killed during the step, until you put it back on its branch at that commit as the step found it and run `teamai workflow retry <run> <step>`. If the workspace already had changes when the step started, also delete the marker file the failure names. The pass records `read`: the branch, the base, and the commit it read. The workspace is a fresh worktree without ignored files such as `node_modules`, so a test installs what it needs, for example `npm ci && npm test`, unless the team's [`postDeploy`](distribution.md#team-scripts) does.

`workspace: new` runs the step in its own git worktree, on a branch named for this run, step, and pass. The step commits what it changed. A step that leaves that branch, for example with `git switch`, fails with `workspace_base`: nothing is committed or pushed, and the worktree stays as the step left it. The current checkout is not switched, reset, or cleaned. A later step that lists it in `needs` starts from the first of those branches and merges the rest, in `needs` order. A merge conflict stops there. A command fails with `merge_conflict`, and the message names the branch and the files. A prompt is told which files still conflict and which branches were not merged.

`workspace: continue` stays on the branch an enclosing `workflow` step opened with `new`. A prompt with no `workspace` continues that copy. A `for_each` with `workspace: new` gives each item its own branch, so items do not share a worktree. `steps.<id>.branches` lists those branches, and each `results` entry has `branch` when the item has one. An item with `after` starts from the branches it waits for. A top-level `workspace: continue`, with no enclosing `new`, fails the step with `workspace_base`.

The step sees the agent files from the commit the run started at: `AGENTS.md`, `CLAUDE.md`, `.claude`, `.codex`, `.opencode`, `.cursor`, `.agents`, and `.githooks`. A hook an earlier step committed does not run in a later step. The team's skills, rules, agents and MCP are then delivered into the worktree, as they are for any new worktree of this checkout. Those delivered files are not part of the step's commit. A file the step itself edits is committed.
The step sees the agent files from the commit the run started at: `AGENTS.md`, `CLAUDE.md`, `.agents`, `.githooks`, every tool's project directory such as `.claude` or `.tclaude`, the project MCP files `.mcp.json` and `opencode.json`, and Copilot's agent paths under `.github`. A hook an earlier step committed does not run in a later step. The team's skills, rules, agents and MCP are then delivered into the worktree, as they are for any new worktree of this checkout. Those delivered files are not part of the step's commit. A file the step itself edits is committed.

When the checkout has an `origin` and the step made commits, the driver pushes that branch. A later attempt that adds no commit still pushes when this branch has commits `origin` does not have. That includes a first push that never landed, and an origin that has no branches yet. A failed push fails the step with `push_failed` and keeps the worktree. With no `origin`, or when `origin` already has that commit, the run still succeeds and reports the branch.

Expand Down
6 changes: 3 additions & 3 deletions docs/guide/zh-CN/workflow-advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ steps:

1. 结算运行。如果运行已经结束,返回 `done`。
2. 把本驱动的、`running` 里没有列出的未结租约交还给它。重启后的驱动发送 `"running":[]`,每次调用拿回一个未结租约。
3. 按文件顺序租出第一个就绪的步骤。子工作流的步骤位于运行它的那个步骤的位置。
4. 每个就绪的步骤都已租出时,返回 `wait`,`reason` 指出第一个未结租约。
3. 按文件顺序租出第一个就绪的步骤。子工作流的步骤位于运行它的那个步骤的位置。需要 `driver.can.workspace` 未列出的工作区模式的步骤,或使用其他驱动打开的工作区的步骤,会被暂缓,改为租出下一个就绪的步骤。
4. 每个就绪的步骤都已租出时,返回 `wait`,`reason` 指出第一个未结租约。每个就绪的步骤都被暂缓时,`reason` 逐一列出它们,以 `; ` 分隔。

不发送 `running` 的驱动每次调用都会拿回自己的未结租约,因此一次只运行一个步骤。

Expand Down Expand Up @@ -632,7 +632,7 @@ teamai workflow resume <run>

每次无 HITL 决策和 hitl_required 失败都会向 stderr 写警告。在本地配置中与 `workflows.max_parallel` 一起设置 `workflows.auto_approve`,或给 `start --driver local`、`resume` 传 `--auto-approve`,仅对当前进程生效。JSON next 不能设置此覆盖。本地驱动仅在 stdin 和 stderr 都是 TTY 时声明支持 HITL,通过 stderr 提问,接受选择及其后的可选备注,并在提问期间继续轮询。其他进程回答或取消 HITL 请求会关闭问题。交互工具占用终端时,HITL 请求提问会等待。

`interactive: true` 要求 `prompt` 或 `prompt_file`。没有 HITL 的驱动以 `hitl_required` 失败。本地驱动在工作目录以前台方式打开工具,继承 stdio,并把 prompt 作为第一条消息。人对仓库的修改会保留,步骤成功。设置了 `workspace: read-only` 的交互步骤和命令一样检查:改了检出目录就以 `read_only_dirty` 失败,改动留在原地。在自己工作区里的交互步骤仍可以改那个工作区。无头 prompt 仍然只读。终端的 Ctrl-C 和 Ctrl-\ 交给交互工具处理;SIGTERM 和 SIGHUP 会停止工具和驱动。交互工具以 0 退出且没有输出时可以不写结果文件。驱动在 prompt 运行时每两秒轮询,发现取消后停止工具。
`interactive: true` 要求 `prompt` 或 `prompt_file`。没有 HITL 的驱动以 `hitl_required` 失败。本地驱动在工作目录以前台方式打开工具,继承 stdio,并把 prompt 作为第一条消息。人对仓库的修改会保留,步骤成功。设置了 `workspace: read-only` 的交互步骤和命令一样检查:改了检出目录就以 `read_only_dirty` 失败,改动留在原地。在外层步骤打开的工作区里,改动保存在 `refs/teamai/kept/<attempt>`,并从工作区移除。在自己工作区里的交互步骤仍可以改那个工作区。无头 prompt 仍然只读。终端的 Ctrl-C 和 Ctrl-\ 交给交互工具处理;SIGTERM 和 SIGHUP 会停止工具和驱动。交互工具以 0 退出且没有输出时可以不写结果文件。驱动在 prompt 运行时每两秒轮询,发现取消后停止工具。

## 重试、取消和继续运行

Expand Down
Loading
Loading