Draft: Add Duo workplan: decision log, confidence scoring, status flow
Draft — for hands-on trial, not review-ready. See "Known gaps" before reviewing; several pipeline gates are knowingly red.
Companion: gitlab-org/modelops/applied-ml/code-suggestions/ai-assist!6398 (closed) (the flow definitions). Neither half works alone.
What this is
The planning half of a "software factory" for GitLab Duo. An agent researches a work item against the real codebase, settles what it can on its own, records every decision it makes, and publishes an implementation plan that a separate critic agent scores before implementation starts. A human steers by owning decisions, not by writing the plan.
What's in it
WorkItems::Decision — an append-only decision log. Rows are never edited, only superseded, so the record of why a plan looks the way it does survives being changed. source records who decided (the agent on its own, a human answering a gate, a resolved thread); kind separates plain choices from acceptance criteria. The two are orthogonal on purpose.
This turned out to be the steer channel rather than just an audit trail: gate answers round-trip through GitLab and come back to the agent via get_work_item, which was discovered rather than designed.
WorkItems::AgentPlan confidence. Six scored dimensions plus a content digest and scored_at, so a plan whose content or decisions moved after scoring reports as stale rather than being silently trusted.
An add_workitem_decision MCP tool. The flow records its own decisions through the same API a human would use. The target work item is pinned from the session rather than chosen by the model — an early live run filed 5 of 8 decisions against an unrelated issue it had been reading as prior art.
Configurable work item status transitions. Flow lifecycle events map to work item statuses via .gitlab/duo/factory.yml in the target project, so teams define their own factory stages instead of inheriting hardcoded ones. No file means a built-in mapping; a malformed file is refused rather than silently defaulted, because a team that wrote a file wanted their rules, not ours.
Command Center. A cross-project view of the agent sessions that need a human, with a topbar pill count.
Async flow starts — no chat session, no Duo Chat window.
Trying it out
bundle exec rails db:migrate— five migrations;db/structure.sqlis deliberately not in this MR (see below).- Enable feature flags
workplan_flowandworkplan_decision_log(both default off). - Enable the
workplan/v1foundational flow for your project. - For MCP decision recording:
mcp_clienton, plusduo_workflow_mcp_enabledon the group. If these are off the flow still runs and simply records no decisions, silently — worth checking before concluding it's broken. - Optionally add
.gitlab/duo/factory.ymlto map flow events onto your statuses.
Note the flow runs as a CI job tagged gitlab--duo, not in your browser — so runner concurrency caps how many plans can be built at once.
Known gaps — please read
Pushed with --no-verify. These gates are red and I know it:
db/structure.sqlomitted. This working tree carries ~1,200 lines of unrelated schema drift; only ~74 lines belong to these migrations. Shipping it would have buried the real change. Danger will flag "New migrations added but db/structure.sql wasn't updated" — correct, and deliberate.permissions_docs— four new GraphQL types needauthorize_granular_tokenplus per-boundary tests:WorkItemAgentPlanConfidence,WorkItemAgentPlanConfidenceDimension,WorkItemWidgetDecisionLog,WorkItemDecision. Real work, not yet done.graphql_introspection_checkandopenapi_docs— generated artefacts not regenerated.unused-methods-linter— flagsconfidence_band,decisions_stale?,superseded?,active_decisions. At leastdecisions_stale?may be genuinely dead: the GraphQL resolver re-implements the same predicate with a BatchLoader rather than calling the model method. Worth resolving properly rather than adding to the exclusion list.validate-fast-spec-helper— not yet investigated.- Danger: "definitely too big (36k lines)". Fair. This is an exploratory branch for people to try, not a merge candidate; it wants splitting before it becomes one.
Rubocop, eslint, prettier, jsonlint, haml-lint, gettext, stylelint and commit-message linting all pass.