Add review workflows, tree-shaped workflows and a workflow editor

This commit is contained in:
Ian Gulliver
2026-10-20 18:52:04 -07:00
parent 9869fad988
commit f2188c53eb
31 changed files with 2179 additions and 863 deletions
+74 -61
View File
@@ -78,23 +78,6 @@ paths:
schema: { $ref: "#/components/schemas/User" }
"400": { $ref: "#/components/responses/Error" }
/api/me/review-models:
put:
operationId: updateReviewModels
description: Sets the models that review the calling user's tasks.
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/ReviewModels" }
responses:
"200":
description: The updated user.
content:
application/json:
schema: { $ref: "#/components/schemas/User" }
"400": { $ref: "#/components/responses/Error" }
/api/me/lead/sessions:
get:
operationId: listLeadSessions
@@ -256,6 +239,26 @@ paths:
schema: { $ref: "#/components/schemas/Project" }
"404": { $ref: "#/components/responses/Error" }
/api/projects/{projectId}/review-workflow:
parameters:
- { name: projectId, in: path, required: true, schema: { type: string } }
put:
operationId: setReviewWorkflow
description: Sets the review workflow that reviews the project's snapshots from the next one on; empty turns reviews off. Records project.updated.
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/ReviewWorkflow" }
responses:
"200":
description: The updated project.
content:
application/json:
schema: { $ref: "#/components/schemas/Project" }
"400": { $ref: "#/components/responses/Error" }
"404": { $ref: "#/components/responses/Error" }
/api/projects/{projectId}/tasks:
parameters:
- { name: projectId, in: path, required: true, schema: { type: string } }
@@ -1210,21 +1213,27 @@ paths:
operationId: getWorkflow
responses:
"200":
description: "The workflow's file: its WorkflowDefinition as YAML, as an attachment named after it."
description: >
The workflow's WorkflowDefinition: as JSON when the request accepts only application/json, else its file as
YAML, as an attachment named after it.
content:
application/yaml:
schema: { type: string }
application/json:
schema: { $ref: "#/components/schemas/WorkflowDefinition" }
"404": { $ref: "#/components/responses/Error" }
put:
operationId: putWorkflow
description: >
Creates or replaces the workflow from its file, a WorkflowDefinition as YAML (or JSON). The name is one line
of 1 to 100 characters. Tasks that already froze the workflow keep their copy.
Creates or replaces the workflow from its file, a WorkflowDefinition as YAML or JSON. The name is one line of
1 to 100 characters. Tasks that already froze the workflow keep their copy.
requestBody:
required: true
content:
application/yaml:
schema: { type: string }
application/json:
schema: { $ref: "#/components/schemas/WorkflowDefinition" }
responses:
"200":
description: The stored workflow.
@@ -1256,7 +1265,7 @@ components:
User:
type: object
required: [login, display_name, git_name, git_email, notification_prefs, default_model, review_model, second_review_model, away]
required: [login, display_name, git_name, git_email, notification_prefs, default_model, away]
properties:
login: { type: string }
display_name: { type: string }
@@ -1264,8 +1273,6 @@ components:
git_email: { type: string }
notification_prefs: { $ref: "#/components/schemas/NotificationPrefs" }
default_model: { type: string, description: The model the app starts new tasks on. }
review_model: { type: string, description: The model that reviews this user's tasks, unless it wrote the work under review. }
second_review_model: { type: string, description: The model that reviews this user's tasks when review_model wrote the work under review. }
away: { type: boolean, description: "While true, byre answers the questions agents ask in this user's tasks at once." }
Away:
@@ -1280,13 +1287,6 @@ components:
properties:
model: { type: string }
ReviewModels:
type: object
required: [review_model, second_review_model]
properties:
review_model: { type: string }
second_review_model: { type: string }
NotificationPrefs:
type: object
description: Which task events notify this user in the macOS app.
@@ -1310,7 +1310,7 @@ components:
Project:
type: object
required: [id, name, repo_path, default_branch, remote, created_by, created_at]
required: [id, name, repo_path, default_branch, remote, created_by, created_at, review_workflow]
properties:
id: { type: string }
name: { type: string }
@@ -1319,6 +1319,13 @@ components:
remote: { type: string }
created_by: { type: string }
created_at: { type: string, format: date-time }
review_workflow: { type: string, description: "The review workflow whose stages review every snapshot of the project's tasks; empty for no reviews." }
ReviewWorkflow:
type: object
required: [workflow]
properties:
workflow: { type: string, description: "A saved workflow of kind review, or empty for no reviews." }
ProjectCreate:
type: object
@@ -1430,51 +1437,56 @@ components:
workflow:
type: string
description: >
The name of a saved workflow to run instead of creating one task with no agent: each stage with no after
stages becomes its own task, titled "<title>: <stage>", with the title as the run's brief. The response is
the first of those tasks.
The name of a saved workflow to run instead of creating one task with no agent: each stage that starts it
becomes its own task, titled "<title>: <stage>", with the title as the run's brief. The response is the
first of those tasks.
WorkflowSummary:
type: object
required: [name, description]
required: [name, description, kind]
properties:
name: { type: string }
description: { type: string }
kind: { type: string, enum: [task, review] }
WorkflowDefinition:
type: object
description: >
A workflow's file, which holds everything but its name: its stages, each run as its own task once the stages
it comes after have finished. Sent and returned as YAML.
required: [stages]
A workflow's file, which holds everything but its name: its steps, run in order. Each stage runs as its own
task once the steps before it have finished. Sent and returned as YAML.
required: [steps]
properties:
description: { type: string, x-go-type-skip-optional-pointer: true }
stages:
kind:
type: string
enum: [task, review]
x-enum-varnames: [WorkflowKindTask, WorkflowKindReview]
x-go-type-skip-optional-pointer: true
description: >
task (the default): each stage runs as its own task. review: each stage is a reviewer of a snapshot, run
when a project names the workflow as its review workflow; a stage gets the earlier stages' reviews.
steps:
type: array
minItems: 1
items: { $ref: "#/components/schemas/Stage" }
items: { $ref: "#/components/schemas/WorkflowStep" }
Stage:
WorkflowStep:
type: object
required: [name, prompt]
description: >
Exactly one of stage (with its model or agents, and prompt), order or parallel. A stage gets the outputs of
the stages that end the step before it: the last stage of an order, every branch's last stage of a parallel.
properties:
name: { type: string, description: "One line of 1 to 100 characters, unique within the workflow." }
after:
type: array
items: { type: string }
description: >
The stages whose outputs this stage gets, by name; it starts once all of them have finished. Absent means
the previous stage in the list (for the first stage, none); empty means it starts from the brief.
base:
type: string
enum: [parent, main]
x-enum-varnames: [StageBaseParent, StageBaseMain]
description: >
Where the stage's task branches from. parent (the default): the commit its one after stage finished at, or
for a stage with none, where the workflow was started from. main: the project's default branch. Refused on
a stage with more than one after stage, which always branches from the default branch.
stage: { type: string, x-go-type-skip-optional-pointer: true, description: "The stage's name, one line of 1 to 100 characters, unique within the workflow." }
model: { type: string, x-go-type-skip-optional-pointer: true, description: "The stage agent's model, which also picks its vendor: gpt-* models run on Codex, gemini-* models on Antigravity, every other model on Claude Code. Required without agents, refused with them." }
prompt: { type: string, description: "The stage agent's instructions; with agents, the instructions every agent of the team gets before its own." }
prompt: { type: string, x-go-type-skip-optional-pointer: true, description: "The stage agent's instructions; with agents, the instructions every agent of the team gets before its own." }
order:
type: array
items: { $ref: "#/components/schemas/WorkflowStep" }
description: Steps run one after another, each getting the outputs of the one before.
parallel:
type: array
items: { $ref: "#/components/schemas/WorkflowStep" }
description: Branches run at the same time, each getting the outputs of the step before the parallel.
agents:
type: array
items: { $ref: "#/components/schemas/TeamMember" }
@@ -1520,7 +1532,7 @@ components:
name: { type: string }
after:
type: array
description: The stages it comes after, with the definition's defaults applied.
description: The stages whose outputs it gets, from the steps' shape.
items: { type: string }
state:
type: string
@@ -1669,12 +1681,13 @@ components:
Review:
type: object
description: An agent session on the vendor the task's agent doesn't use, reviewing one snapshot's change.
required: [id, task_id, snapshot_n, agent_session_id, vendor, model, state, error, created_at, finding_states, finding_responses]
description: One stage of the project's review workflow, an agent session reviewing one snapshot's change.
required: [id, task_id, snapshot_n, stage, agent_session_id, vendor, model, state, error, created_at, finding_states, finding_responses]
properties:
id: { type: string }
task_id: { type: string }
snapshot_n: { type: integer }
stage: { type: string, description: "The review workflow's stage this reviewer ran; empty when skipped." }
agent_session_id: { type: string, description: "The reviewer's session; empty when skipped." }
vendor: { type: string, description: "Empty when skipped." }
model: { type: string, description: "Empty when skipped." }