Repository navigation
cockpit v3: autonomy and auto-merge per subtree (S13) - #217
Merged
Merged
Conversation
Owners scope auto-merge (product.delivery.auto_merge_scopes) and the autonomy level (autonomy.scopes) to a goal, need, feature or project; the setting covers everything under it, also work added later. The merger and autonomy.decide look up the nearest scoped ancestor of a PR's or an op's project (its home feature, that feature's need, the need's goal). A scope always names its base: main is never inferred. Both lists are owner-only (class B, AGENT_NEVER) and stored under their own top-level keys, so an older server drops only them. The "Autonomy & merge" verb (u) previews what a scope covers now and later, its first effect and what it leaves alone, applies as a definition version and has Undo; node pages show "auto-merge -> <base> · from <node>" on every node under it. The v3 preview gains an autonomy level choice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ects as the tree does - landing: a new project is decided in the feature its plan extends (analysis.initiative.id), else under the proposal's opportunity; a create that only cites the ask that started a feature is no longer decided at that feature's level (it lands outside it). - Lineage mirrors the node tree: a project with no home feature sits under the opportunity of the proposal that created it (and that need's goal); a feature's need falls back to its origin proposal's opportunity, as the tree files it. Lineage.above uses the need only for a new feature. - node.auto_merge counts the factory-wide targets too (own or inherited); scope chips come first and use the accent tone. - factory_targets(lineage=False) for callers with their own lineage; the snapshot reads targets only while this server runs the merger; homes are computed with one HomeFinder per lineage. - web: auto-merge has a Keep state (sent only once touched) and a base field when the server asks the owner to name the base (args.base). - docs: a rollback drops merge: false and stricter autonomy scopes too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LittleChimera
force-pushed
the
factory/v3-m2-subtree
branch
from
November 4, 2026 06:12
5a916a0 to
5245965
Compare
|
❌ UI Snapshot doesn't match the committed baseline. If this UI change is intentional, update the baseline — each path renders in the same pinned image, so the result matches this gate:
Diff PNGs ( |
10 of 13 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related issue
Closes # (cockpit v3 milestone 2, plan item S13;
notes/factory-v3/final/plan.md)Summary
The owner asked for the temp-main tryout factory to "automatically merge, not wait for me", and said "i don't know how i'm supposed to steer it". This PR lets an owner set autonomy and auto-merge on one node, and the setting covers everything under that node, including work added later.
product.delivery.auto_merge_scopestakes entries[{ref, repo, base}]or{ref, merge: false}.autonomy.scopestakes entries[{ref, level}].refis a stored goalG-n, needO-n, featureI-nor project slug.repo#baseto the factory-wide targets for the projects under it.merge: falsescope keeps everything under it merged by people.AGENT_NEVER, so no agent proposes them, adds a scope or widens one. The verb routes areHUMAN_ORIGINand need MANAGE.u) on a node.main,masteror a protected branch unless the owner names it inargs.base.v1 and v2 behave as before. Factory-wide targets and
autonomy.levelwork as today. Nothing runner-side changed (omnigent/runner,tools,policiesandomnigent/factory.pyare untouched).ELI5: on the "Agent helper" outcome page, press
uand turn on "Auto-merge". The preview says: "Merges into: org/app#temp-main … Covers: 4 projects now (…) and any added under it later … Not affected: main stays merged by people (build-tier-drafts-3 is outside this subtree)". Apply it. From then on, every ready, green PR of those projects is merged by the server, including projects Atlas adds to that outcome later. The project on main still waits for you. Every page under the outcome shows the chip "auto-merge → temp-main · from Agent helper".Plan bullets and what this PR does
S13.
scopelists on autonomy anddelivery.auto_merge:{ref, autonomy?, auto_merge_base?}, inherited by descendantssubtrees.py(new):G-n,O-n,I-nor a project slug. Implicit parents and the roots are refused.nearest(chain, scopes).Lineage, which places a project the way the node tree files it:features.homes, oneHomeFinderper lineage);plan.opportunity, else the opportunity of the proposal that started it (unless it sits under a person's ask);strategy.goals);AutoMergeScopeDef(definition_blocks.py) andAutonomyScopeDef(autonomy.py) validate entries:{ref, autonomy?, auto_merge_base?}list. Each block stays owner-only and rollback-safe on its own. An auto-merge entry also names itsrepo(targets are repo and base pairs) and can saymerge: false. The verb takes the combined shape{auto_merge, base?, repo?, autonomy?}and writes both lists.auto_merge_targetsandautonomy_level:auto_merge_scopesandautonomy_scopes.SPARSE_FIELDS).autonomy.projects/levelkeep their values. Nothing merges by a scope then. That rollback is not safe in every direction: amerge: falsescope under factory-wide targets and an autonomy scope stricter thanautonomy.levelare dropped too, so the old server merges those PRs and decides those ops at the looser factory level (the docstrings anddocs/FACTORY.mdsay so).S13. The merger and
autonomy.decidelook up the nearest scoped ancestorauto_merge.Targetsis now{pairs, scopes, lineage}.Targets.cover(project)returns the pairs a project's PRs merge into and the scope that decided them.is_target,scope_refusal,local_refusal,merges_itselfandgithub_refusaltake the project. They also still accept the bare pairs, so callers that pass a frozenset keep working.factory_targets()reads the lineage the scopes need, and only when there are scopes. It reads only what is needed: initiatives for feature scopes; opportunities and the applied proposals for need and goal scopes. Callers that read their own lineage (the node projection) passlineage=False, and the snapshot reads the targets only while this server runs the merger.pr.auto_mergedevent recordsdata.scope, and the summary and log name it ("the merger logs the scope it used", plan §4).Targets, so "merges automatically" and "needs you: merge" follow the scope. The PL charter's merge policy follows a project or feature scope.autonomy.decide(…, levels=[…])decides each op at its own level.autonomy.op_leveltakes the strictest across the projects an op touches,proposals.scoped_levelsplaces them, and the result goes into the event data.analysis.initiative.id, whichlink_createdfiles it under), else under the proposal's opportunity. A create that only cites the ask that started a feature lands outside that feature, so the feature's level does not decide it.joined(the tryoutcreate_joinedclass) is computed when any scope istryout.proposal.openedrecordsscoped: [{op, ref, level}].S13. Owner-only,
HUMAN_ORIGIN; agents can never add or widen a scopeAGENT_NEVERgainsproduct.delivery.auto_merge_scopes.autonomywas already in it, soautonomy.scopesis covered.PUT …/definitionand the verb need MANAGE and a person's request (human_onlyfor a runner token).S13. A preview/apply/Undo verb "Autonomy & merge" on a node (
node_verbs._plan_auto)structure.verbslistsauto(feature and project by default) on a stored goal, need, feature or project._verb_appliesnow offers it only there; an implicit node gets 422not_here.auto_merge(true/false),base,repo, andautonomy(a level orinherit). A node whose ancestors already give what is asked keeps no entry of its own. If nothing changes, the verb answers 409nothing_to_do.rollup_delta: Auto-mergeoff → → temp-main (here), and AutonomyCareful → Tryout;{label, text}lines: Merges into, Covers, First effect (the open PRs that will merge themselves), Not affected, Autonomy;apply_label"Turn on / off for this subtree";can_applyis false (403authorityon apply);tuning.human_edit(a humanC-n, a definition version,based_onits version). Undo re-pins the version before while the one the verb made is still in force.auto-merge → <base> · this subtree / from <node>(accent tone) on the scope node and every node under it;auto-merge · offwhere an off scope actually stops something;autonomy · Tryout · from <node>(accent tone) where the level differs from the factory's;auto_merge: trueon covered summaries, own or inherited (the tile badge): a project whose PRs into its base merge by a scope or a factory-wide target; a goal, need or feature that a merging scope covers, else one with a live project under it that merges.VerbPreviews.tsx):auto_merge. "Keep as it is" drops the auto-merge part;args.base, which now also returns the base it would have taken), an "Into base" field appears with that base as its placeholder. Nothing is named for the owner: main is sent only once they type it.docs/FACTORY.mdcovers the scopes (the auto-merge section, the autonomy policy, the Tier 0 invariant row) and the verb's args.S13 tests (plan wording, "temp-main" spelled
tryout-baseperFORBIDDEN_SAMPLE_TOKENS)test_an_outcome_on_the_tryout_base_covers_its_projects_and_future_onesruns preview, apply and Undo. A project added later through the add verb is covered.test_auto_merge_subtree.py::test_the_outcome_merges_itself_and_main_waits_for_a_personand::test_a_project_filed_under_the_outcome_later_is_covered.drafts/api-statusonmainstill ask a person to merge.test_main_is_never_auto_merged_unless_the_owner_names_it, for both a project and a feature whose projects target main (400invalid_argsonargs.baseuntil it is named).test_agents_never_add_or_widen_a_scopecovers both paths and both lead seats.test_only_an_owner_applies_it_and_only_as_a_personcovers the editor (403) and the runner token (human_only).test_the_nearest_scope_decides_and_off_keeps_people_merging,test_a_tryout_outcome_grows_by_itself_while_the_factory_stays_careful(a lead's plan that extends the outcome applies at its tryout level, lands in it and is covered by its auto-merge; a create that only cites the ask waits),test_a_factory_wide_target_counts_on_the_nodes_it_covers,test_a_projects_scope_chain_is_its_place_in_the_tree(five fixtures, every project),test_a_scope_on_a_need_covers_the_projects_in_its_implicit_features,test_a_projects_level_covers_only_that_project(one proposal about two subtrees waits: the strictest wins),test_scopes_are_stored_apart_and_only_once_set(an oldAutonomyDefandDeliveryDefstill validate the stored blocks), validation, and the pure lookups.Deviations
repoand amerge: falseform.AutonomyAndMergestories render the new controls. Storybook was built locally to check the panel at 1440 (light, dark) and 390.Test Plan
definition_blocks.upgrade: both_rename_on_areasand the scope lowering kept).uv run --frozen pytest -q -n 8 tests/server/factory: 4514 passed.tests/server/factory/test_subtree_scopes.py(32) andtests/server/factory/test_auto_merge_subtree.py(3). Updated: the golden defaults,test_auto_merge(Targets.pairs),test_autonomy_levels,test_definition, andtest_node_verbs(autois now served).tests/examples/test_factory_sample_tokens.py: 3 passed.web/):tsc -b,oxlint,prettier --check .are clean.vitest run src/components/factory src/pages/factory: 130 files, 2093 tests passed, with tests for the autonomy choice, the Keep state (a level alone on an auto-merged node sends noauto_merge) and the base field.pre-commiton the changed files: ruff, prettier, oxlint, web tsc and the hygiene hooks pass. pyrefly shows only the pre-existing errors (opentelemetry imports,reconcile.py, the Gensee launcher).To try it: run a factory with
OMNIGENT_FACTORY_AUTO_MERGE_S>0.?cockpit=v3on a feature and pressu.pr.auto_mergednames the scope), while a project on main stays in Needs you. Undo within 10 minutes takes it back.analysis.initiative.id) applies at once (an FYI), while the factory stays at its own level.Review follow-ups (second commit)
I-1was decided atI-1's level but filed outsideI-1(infeature:from-C-n), so a tryout scope let an agent add projects anywhere by citing the ask. Now onlyanalysis.initiative.id(whatlink_createdfiles under) places a create in a feature.Lineage.aboveuses the proposal's opportunity only for a new feature, never over an extended one.Lineagereads the applied proposals for need and goal scopes, so a scope onO-nor its goal covers a project filed underfeature:from-C-nof a proposal that plannedO-n. A feature with noplan.opportunitytakes its origin proposal's opportunity, as the tree files it. A test checks, for every project in five fixtures, that the scope chain is the project's stored ancestors in the tree.node.auto_mergecounts the factory-wide targets, so the verb opens on turning it off where the factory's target already covers the node (no more 409 by default).args.baserefusals show an "Into base" field.merge: falseand stricter autonomy scopes.factory_targets(lineage=False)for the node projection (one lineage read per build, not two); the snapshot reads targets only while the merger runs; oneHomeFinderper lineage instead of one per project.Demo
N/A (server-first; the preview's new choice is covered by the v3 web test and the existing stories).
Type of change
Test coverage
Coverage notes
The merger runs end to end over the in-memory GitHub fake. The verb, the definition storage and the autonomy decision are tested through the real routes.
Changelog
Owners can turn on auto-merge and an autonomy level for one feature or project in cockpit v3 ("Autonomy & merge"), and the setting covers everything under it.
🤖 Generated with Claude Code