Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,12 @@ in the desktop and applies on the next call:
fixed rules), how each ticket runs (MAF workflow or step by step), tasks and acceptance tests at once, automatic fix
rounds (one count per task across engines and restarts), Orchestrator retries, and the checks wait. When your
computer's Claude / Codex plan hits its limit, Autopilot waits for the reset — no tries spent — and carries on.
- **Workflow › Ticket flow** — the steps Autopilot runs for every ticket (Code → Checks → Review → Test → Merge by
default), drawn as a graph with a table view: an admin turns Review or Test off, picks the agent per step and skips a
step for tickets with a label (e.g. no Review for `type:chore`); per tenant with an optional per-project flow,
versioned with revert. Safety rules are enforced on save and on every run (checks always run, never both Review and
Test off for a ticket); a ticket keeps the flow it started with. Board › Autopilot shows the flow in one line.
([ADR-0014](docs/adr/0014-ticket-flow-is-data.md))
- **Settings → Audit log** — who changed what (settings, keys — never their values —, members, sign-in), CSV export.

**Your repo's own rules.** When a Board ticket runs against a repo, every agent follows the repo's agent
Expand Down
26 changes: 24 additions & 2 deletions docs/CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,15 +164,37 @@ pass / call, so a change applies within a minute, no restart. The Board's Autopi
| `Autopilot:ChecksTimeoutMinutes` | `60` | 5–240 | How long a ticket's MAF workflow waits for a pull request's checks before handing it back to the step-by-step passes. |
| `Autopilot:ChecksPollSeconds` | `60` | 15–600 | How often it looks at them. |

Fixed rules (not settings): *Build and merge* merges a change only when its checks are green, the Reviewer agent
approved it and the Testing agent passed it — which needs the member's computer connected. When that computer's
Fixed rules (not settings): *Build and merge* merges a change only when its checks are green and its ticket flow's
gates passed — by default the Reviewer agent approved it and the Testing agent passed it, which needs the member's
computer connected. When that computer's
Claude / Codex **plan hits its limit** (e.g. `You've hit your session limit · resets 11:50pm (Asia/Ho_Chi_Minh)`), the
RemoteAgent provider raises a typed `PlanLimitException` and records the member's wait (`IPlanLimits`); Autopilot
then starts nothing, retries nothing and spends no fix round until the reset (+1 minute; 30 minutes when the CLI gives
no time), logs one line in the Autopilot activity, shows it on Board › Autopilot and Board › Computers, and picks the
cut-short work up again afterwards. The wait is in memory: after a restart the next run meets the limit again (an
immediate answer) and the wait is back.

### Workflow › Ticket flow

The steps each ticket runs ([ADR-0014](adr/0014-ticket-flow-is-data.md)) are **data, not a setting key**: versioned
rows in `workspaces.ticket_flows` (per tenant; `BoardId` = a project's own flow, which wins over the tenant's), edited
by admins in Workflow › Ticket flow (members read-only) and read when a ticket starts. Nothing in `appsettings.json`.

| Step | Agent role | Can be off / filtered | On fail |
|---|---|---|---|
| Code | `IssueWork` — Coding (tickets) | no | handed back to Autopilot |
| Checks | — (CI + merge state) | no | back to Code (fix round) |
| Review | `Reviewer` | yes — *skip tickets labelled* / *only tickets labelled* | back to Code (fix round) |
| Test | `Acceptance` — Testing (tickets) | yes — same filters | back to Code (fix round) |
| Merge | — (source control) | no | handed back to Autopilot |

Refused on save **and** at run time (the run then uses the built-in flow and the Autopilot activity says why): a flow
that doesn't start at Code / end at Merge, turns Checks off or limits it, lets any ticket reach Merge with neither
Review nor Test (every combination of the filter labels is checked), loops other than *fail → Code*, leaves a step
unreachable, puts steps out of order, or names an agent that can't run the step. Fix rounds stay
`Autopilot:MaxFixRounds` on the task's persisted count. Every change is audited (`ticketflow.changed`) and can be
reverted; a task records the version it started with (`sessions.sessions.TicketFlowVersion`) and keeps it.

### Settings → Platform (platform admins)

**Run limits (per tenant)** — `Pipeline:Limits:MaxConcurrentRuns` (default `4`) and `Pipeline:Limits:RunsPerMinute`
Expand Down
65 changes: 65 additions & 0 deletions docs/adr/0014-ticket-flow-is-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# ADR-0014 — Ticket flow is data: Autopilot runs what the Workflow app shows

- **Status:** Accepted · 2026-10-24
- **Context source:** owner — "what the UI shows must be what runs" (connected features); the Workflow app only knew
the legacy pipeline roles while Autopilot ran a graph hard-coded in `TicketWorkflow`.

## Context

Two graphs described one product:

- the **Workflow app** (`OrchestrationStudio` → `GraphRunner` → `GraphExecutor`) draws and runs graphs of the legacy
5-agent pipeline (Requirement / Coding / Testing / QA via `AgentRoleMap`);
- **Autopilot** ran every ticket through a Microsoft Agent Framework workflow whose edges were C# in
`TicketWorkflow.Build` (Code → Checks → Review → Test → Merge, failures back to Code), and the step-by-step rules
(`AutopilotPlanner`) encoded the same order a second time.

Nobody could see the per-ticket flow, let alone change it (skip review for chores, skip acceptance testing for docs),
and the next gate (a design / screenshot check) would have meant a third hard-coded copy.

## Decision

1. **The ticket flow is a definition, not code.** `TicketFlowDefinition` (Pipeline module, next to `AgentRoles` and
`AutopilotSettings`): ordered step nodes + conditional edges. Node kinds: **Code** (Coding (tickets) —
`IssueWork`), **Checks** (CI), **Review** (`Reviewer`), **Test** (Testing (tickets) — `Acceptance`), **Merge**.
Each agent node names its `AgentRoles` key (so it runs with that agent's profile and prompt) and may carry label
filters (*skip tickets labelled …*, *only tickets labelled …*). Edges: `Pass` → the next step, `Fail` → back to
Code — a fix round counted on the task's persisted `FixRounds` against `Autopilot:MaxFixRounds` (no second
counter). `TicketFlowDefinition.Default` is exactly the old hard-coded flow.
2. **Safety invariants, checked on save and on every load** (`TicketFlowValidator`, plain-language reasons): starts
at Code · ends at Merge · Checks always runs before Merge (can't be turned off or filtered) · every ticket, whatever
its labels, passes at least one merge gate (Review or Test) — every combination of the labels named in the filters
is checked · no cycle except a fail edge back to Code · every step reachable · steps in their natural order · one
step per kind · a role the step can actually run. A stored definition that breaks one is refused and the run uses
the built-in flow (Autopilot logs why, once per board). `TicketFlowPlan.SafetyProblem` re-checks the per-ticket
plan before a MAF graph is built. `AutopilotPlanner.WithinCaps` stays the outer guard on slots.
3. **Both engines run it.** `TicketWorkflow.Build(steps, plan, …)` creates one executor per step the ticket runs and
wires `pass` / `fail → Code` / `done` edges from the definition. `AutopilotPlanner` reads the task's plan
(`PlanTask.Flow`) and makes no move for a step it skips (no review when Review is off for the ticket).
4. **Storage: versioned per tenant, optional per board** — `workspaces.ticket_flows` (Workspaces module, which owns
the board and Autopilot state), append-only: a save, a revert or "use the default flow" adds a version with who
and when. `BoardId = Guid.Empty` is the tenant's default; a board's own flow wins. Version numbers run per tenant,
so a task can **snapshot** the version it started with (`sessions.sessions.TicketFlowVersion`): a ticket under way
keeps its flow — in the MAF workflow and, after a restart, in the step-by-step passes — while new tickets use the
current one. Tenant-scoped: `TenantId` on every row, EF query filter, RLS (`RowLevelSecurity.Tables`), every
store method tenant-explicit; parsed versions are cached per (tenant, version) since versions are immutable.
5. **The Workflow app shows and edits it** — Workflow › *Ticket flow*: the steps as studio node cards (with each
agent's character) and as a table, an inspector to turn a step on/off, pick its agent and set label filters,
validation inline, Save / Discard / Revert; members read-only; the step a running ticket is on is highlighted live
(the only animation). Board › Autopilot summarises the flow in one line and links there.

It is *not* a `SettingDescriptor` (ADR-0011): it is structured, versioned data with its own invariants and editor —
like a workflow graph — but it follows ADR-0011's intent: configured in the desktop, per tenant, never in
`appsettings.json`, read per call.

## Consequences

- What the owner sees is what runs; changing the flow needs no deploy and applies to the next ticket.
- **Adding a step kind is local**: one `TicketStepKind` value + one `TicketStepKinds` entry (rank, roles, merge gate,
can send back) + one executor in `TicketWorkflow.Create` (+ a planner move if the step-by-step rules should run it).
The Design / screenshot gate is the first planned one.
- Today each agent step kind has exactly one agent that can run it, so the agent picker offers one choice per step;
the role is stored and validated so a second implementation (e.g. a security reviewer) only needs registering.
- Label filters read the ticket's labels when it starts (MAF) or on each pass (step by step); a task whose ticket
left the board falls back to the `type:` label it was created with.
- The legacy 5-agent Workflow graphs (`GraphExecutor`) are unchanged.
1 change: 1 addition & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Format: Status · Context · Decision · Consequences. Keep them terse.
| [0011](0011-ui-first-configuration.md) | UI-first configuration: settings registry, scopes, precedence | Accepted |
| [0012](0012-postgres-row-level-security.md) | Postgres row-level security for tenant tables (stage 1) | Accepted |
| [0013](0013-teams-and-projects.md) | Teams and projects inside a tenant (org → team → project → repo) | Proposed |
| [0014](0014-ticket-flow-is-data.md) | Ticket flow is data: Autopilot runs what the Workflow app shows | Accepted |

> ADRs 0001–0004 (modular monolith, LLM gateway, multi-tenant isolation,
> one-engine-three-surfaces) are scheduled as a back-fill in [ROADMAP.md](../../ROADMAP.md) Q2;
Expand Down
6 changes: 6 additions & 0 deletions docs/plans/autonomy-levels.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ others learned. The board shows each ticket's phase (Discovery → Coding → Te
rounds, seeded from the task's persisted count). A flow that ends without a merge, or one a restart cut short, is
picked up step by step. Settings › Autopilot › *Each ticket runs as* (MAF workflow / Step by step); its checks wait
and poll are settings too.
3d. ✅ **Ticket flow is data** ([ADR-0014](../adr/0014-ticket-flow-is-data.md)). The per-ticket graph is a
definition the owner sees and edits in Workflow › Ticket flow (per tenant, per project, versioned, revert): steps
on/off, the agent per step, label filters (no Review for `type:chore`, no Test for `type:docs`). `TicketWorkflow`
builds its MAF graph from it and the step-by-step rules skip what it skips; a ticket keeps the version it started
with. Safety invariants (checks always, never neither Review nor Test, fail → Code only) are enforced on save and
at run time. Next: a Design / screenshot gate as a new step kind.
3b. ✅ (#264) **Reviewer agent.** Between green CI and acceptance testing: a read-only agent reviews the diff in
context and posts its review on the PR; must-fix findings go back to the Coding agent (same bounded rounds).
4. ✅ (#242, #264, #244) **Merge policy.** Per project: *Build and merge* merges only on green checks + review approved +
Expand Down
2 changes: 1 addition & 1 deletion src/AgentOs.Modules.Pipeline/Agents/AutopilotSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public IEnumerable<SettingDescriptor> Describe()
{
DefaultValue = MafWorkflow,
Choices = [MafWorkflow, StepByStep],
Help = "MAF workflow: a Microsoft Agent Framework graph runs Coding → checks → Reviewer → Testing → merge for the ticket, routing every verdict. Step by step: Autopilot drives each step itself every minute. Both use the same agents, rules and evidence.",
Help = "MAF workflow: a Microsoft Agent Framework graph runs the ticket flow (Workflow › Ticket flow — Code → Checks → Review → Test → Merge by default) for the ticket, routing every verdict. Step by step: Autopilot drives each step itself every minute. Both run the same ticket flow, agents, rules and evidence.",
};
yield return Bound(MaxParallelKey, "Tasks at once",
"How many tickets the AI works on at the same time on a project (coding, fixing, reviewing or testing). Each one uses your computer.");
Expand Down
Loading
Loading