Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
dc45531
fix(pipeline): make native agent timeouts observable and bounded (#877)
kunchenguid Aug 28, 2026
ab25442
chore(main): release 1.60.0 (#875)
github-actions[bot] Aug 28, 2026
13cdf1a
chore: backpass extracting memory to skills
kunchenguid Aug 28, 2026
ff4af29
fix(branchsync): settle wedged custody records via --keep-local
hmcdaniel03 Aug 28, 2026
ced6c28
fix(branchsync): close settlement edge cases found in review
hmcdaniel03 Aug 28, 2026
abc623e
no-mistakes(review): fail-close keep-local settlement advertisement; …
hmcdaniel03 Aug 28, 2026
6d9d16a
fix(branchsync): fail-close settlement advertisement and scope abort …
hmcdaniel03 Aug 28, 2026
b1a9a22
no-mistakes(review): fail-close settlement advertisement on unreadabl…
hmcdaniel03 Aug 28, 2026
3db517c
no-mistakes(review): close settlement dead end after lost gate compar…
hmcdaniel03 Aug 28, 2026
684a4aa
no-mistakes(review): name an exit on every keep-local recovery refusal
hmcdaniel03 Aug 28, 2026
e59c7c1
no-mistakes(review): name a completable exit on every recovery refusal
hmcdaniel03 Aug 28, 2026
7867fc2
no-mistakes(review): narrow refusal-exit claims and name conflicting …
hmcdaniel03 Aug 28, 2026
39fc187
no-mistakes(review): correct settlement guidance and name stranded an…
hmcdaniel03 Aug 28, 2026
7a4be00
no-mistakes(review): scope stranded-anchor guidance and custody abort…
hmcdaniel03 Aug 28, 2026
1ace9ce
fix: scope run-abort custody help and align anchor probe with its write
hmcdaniel03 Aug 28, 2026
7a0e7a7
fix(branchsync): make custody-settlement refusals leave nothing behind
hmcdaniel03 Aug 29, 2026
87d8c11
no-mistakes(review): make keep-local refusals name the anchor they wrote
hmcdaniel03 Aug 29, 2026
24c96a4
no-mistakes(review): keep lost-swap refusal local-scoped; drop daemon…
hmcdaniel03 Aug 29, 2026
711ca05
no-mistakes(review): condition axi sync keep-local flag help on gate …
hmcdaniel03 Aug 29, 2026
45a2a79
no-mistakes(document): document TUI custody settlement and run-refusa…
hmcdaniel03 Aug 29, 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
15 changes: 15 additions & 0 deletions .agents/skills/agent-tuning/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
name: agent-tuning
description: Use when changing agent model or effort configuration, adapter mappings, or eval candidate profiles.
user-invocable: false
metadata:
internal: true
---

**Unified Agent Tuning (`internal/agentcfg`)**

- `agentcfg` is the single owner of the harness-neutral model/effort surface and of the mapping down to each harness's native mechanism (claude/copilot `--effort`, codex `-m` + `-c model_reasoning_effort`, grok `--reasoning-effort`, pi `--thinking`, opencode's session-message `model`/`variant`, acpx `--model` for `cursor`/`acp:<target>`). Add a harness there, not in an adapter or in eval. `rovodev` and `antigravity` are deliberately declared unmappable, so a request for them is a config error rather than a flag that is silently ignored.
- `agent.NewWithOptions` is the one funnel: it validates `Options.Profile` and splices the mapped args after the operator's raw `agent_args_override` args, so both the pipeline (`cfg.AgentProfileFor`) and eval replay (`Candidate.Profile()`) reach every harness by the same path. Never re-derive a model or effort flag at a call site.
- Precedence is fixed: a raw `agent_args_override` flag that already pins a knob natively wins and the mapped value is not emitted, which is what keeps every pre-`agent_config` configuration byte-identical and stops a harness receiving one knob twice. `agent_config` is global-only for the same reason as `agent_args_override`.
- Eval candidates are `agent,model=<model>[,effort=<level>]` (the previous `agent+model` spelling is refused with a migration message), effort is part of the persisted candidate identity, and `agentNeutralGlobalConfig` strips `agent`, `agent_args_override`, and `agent_config` so a replay never inherits the capturing machine's pins.
- Regressions: `internal/agentcfg`, `internal/agent/profile_test.go`, `internal/config/config_agent_config_test.go`, `internal/daemon/pipeline_agent_profile_test.go`, `TestParseCandidate*`, `TestReplayPinsCandidateModelAndEffortOnTheHarness`, `TestCaptureStripsEveryHarnessPinFromThePinnedConfig`.
38 changes: 38 additions & 0 deletions .agents/skills/branch-sync-and-push-safety/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
name: branch-sync-and-push-safety
description: Use when changing local branch synchronization, custody recovery, post-review head binding, rebasing, or force-push safety.
user-invocable: false
metadata:
internal: true
---

**Guarded Local Branch Synchronization (`internal/branchsync`)**

- `sync`, `axi sync`, and the TUI `u` action share one service whose only ordinary worktree mutation is a clean guarded move to an exact freshly verified pipeline push binding: strict fast-forward for behind branches, or an anchored reset to an equivalent diverged pipeline head when local unique work is already represented there. Under `--recover`, the worktree can only strict-fast-forward to the gate-preserved head, or adopt a diverged preserved head that `preservedContainsLocalWork` proves carries every local change. Passive status never fetches, and blocked states never reset, stash, merge, rebase, force, switch, delete, or update an external remote.
- Give each network remote operation its own bounded child context derived from the caller: `Refresh` must not share one deadline across sequential `git.LsRemote` and `git.FetchRemoteBranchToPrivateRef` calls, and `Apply` uses the same per-operation budget for its final live check. The per-operation budget is `Service.RemoteTimeout`, sourced only from the operator's global `branch_sync_remote_timeout` setting (default `config.DefaultBranchSyncRemoteTimeout`, 60s); `RepoConfig` deliberately has no matching field. `Recover`'s local-gate fetch is outside this network deadline contract. Regressions: `TestRefreshSlowSuccessfulLsRemoteDoesNotStealFetchBudget`, `TestRefreshSlowButSuccessfulLsRemoteAloneExceedsItsOwnBudgetReportsOffline`, `TestRefreshRaisedRemoteTimeoutAcceptsTheSameLegitimateSlowLsRemote`, `TestRefreshParentCancellationStopsFetchAfterLsRemoteSucceeds`, `TestServiceRemoteTimeoutDefaultsToConfigDefault`, `TestLoadGlobal_InvalidBranchSyncRemoteTimeout`, `TestLoadRepo_BranchSyncRemoteTimeoutIsNotARepoSetting`.
- Successful pipeline pushes persist the exact SHA, credential-free target fingerprint/ref, and generation; legacy rows remain nullable and must never infer provenance from mutable `head_sha`. Structured PR lifecycle retires merged/closed branches. The service rechecks the invoking worktree, target, live remote equality, ancestry or equivalent-divergence proof, generation, and all mutable assumptions immediately before apply.
- A TERMINAL run with unpublished pipeline commits (moved head) is recoverable only from verified, non-conflicting evidence: inspection and `Recover` share one eligibility model. Equal/ahead local ancestry can create the local anchor without requiring gate access, but available gate evidence must agree; importing a missing preserved head requires exact or safely anchorable gate evidence, a clean worktree, and either ancestry or the content-preservation proof below. Only then does inspection report `blocked_pipeline_owned_recoverable` + `next_action recover_custody` with the exact submitted/current-head and relation facts (active runs keep the plain block). Missing, non-commit, symbolic, or conflicting evidence, and import cases that are dirty or genuinely divergent, never advertise `recover_custody`; where the record is self-inconsistent (missing, non-commit, or conflicting evidence) inspection names the keep-local settlement below, and every other shape falls back to manual reconciliation. `sync --recover` anchors the preserved head at `refs/no-mistakes/recover/<run>` before stamping `runs.custody_returned_at`.
Cancellation RELEASES a terminal run that never changed the submitted head (`head_sha == submitted_head_sha`, no push, no custody stamp): selection keeps it visible so it never misreports as `blocked_wrong_branch`, and it classifies `user_owned` - no `next_action`, non-blocking exit, never represented as recoverable custody, `--recover` there is an idempotent no-op that mutates nothing, and a fresh `axi run` or separately authorized direct push is never blocked.
Equal/ahead worktrees anchor locally without requiring gate access, but an available gate's existing recovery ref must agree with the recorded head; behind/diverged worktrees verify and fetch the preserved head from the run-specific recovery ref, fast-forwarding only a clean behind worktree.
A cancelled validation routinely leaves a preserved head that is a REBASE of the local branch, which equality and ancestry read as plain divergence, so a clean diverged worktree is adopted when `preservedContainsLocalWork` proves containment. That proof is an executable `merge-tree` three-way merge whose result must equal the preserved head's tree, anchored on the merge-base - never `runs.base_sha`, the previous gate head. It deliberately does NOT use patch identity: patch IDs discard hunk locations and whitespace, so they cannot tell a genuine replay from a same-shaped edit to another identical block, and a containment claim built on them is not a proof. Everything undecidable escalates, including a rebase whose fix rounds also rewrote operator lines, where nothing separates a deliberate fix from a dropped change.
Adoption anchors the pre-recovery local head at `refs/no-mistakes/recover-local/<run>`, then moves the branch with Git operations that fail closed on their own rather than after an observation - an atomic `update-ref` CAS plus `read-tree -m -u`, never check-then-act followed by `reset --hard`, which destroys anything landing in the gap. `recoverAdoptPreserved` owns the reasoning.
Terminalization pins every verified unpublished head at `refs/no-mistakes/recover/<run>` before the managed worktree can be removed. Recovery reads that run-specific ref rather than requiring the gate branch to match, so aborts, rebases, and pre-push failures remain recoverable while an independently moved gate branch is preserved. Legacy recorded heads that still exist as dangling gate objects are anchored on recovery; a truly missing recorded head never advertises an impossible `recover_custody` command and instead reports the keep-local settlement below, falling back to manual reconciliation whenever that settlement is itself unreachable.
When the operator keeps a behind or diverged local head instead of taking the preserved head, `--keep-local` never touches the worktree and CAS-moves the gate branch to the kept head, staging objects via gate-side fetch - never a push, which would fire the receive hook and start a run. The CAS is reached only where the gate branch still names a different head; equal/ahead, `user_owned`, already-recovered, and absent-gate-branch paths return custody without moving any branch ref (equal/ahead still writes the private recovery anchor, which is what anchoring a locally reachable preserved head means), and the flag help must keep saying so.
`--keep-local` is also the ONLY settlement for a SELF-INCONSISTENT custody record (#824): a terminal run whose recorded pipeline head is in no reachable object store, or whose own recovery evidence names something else, has nothing verifiable to import, so every default recovery refused, `abort` of the terminal run was a no-op, and the branch stayed `pipeline_owned` forever. Inspection names that exit (`next_action.code: return_custody_keep_local`) instead of #814's dead-end manual-reconciliation pointer, terminal-run `abort` responses carry the same command, and the TUI `u` action reaches the same settlement - keyed on that advertised next action, never on a safety code, so it cannot drift from the predicate that decides where the settlement can complete. #814's polarity (never advertise `recover_custody` for an unverifiable record) is preserved. Terminal-run no-op abort help is separately allowlisted to custody-settlement codes only (`custodySettlementHelp`) AND gated on `StatePipelineOwned`, so an abort that cancelled nothing never answers by prescribing a fresh run against a released branch, nor by repeating ordinary-divergence `git log` advice; a SUCCESSFUL cancellation still echoes the branch's own next action.
Three invariants make that safe, and the `recoverSettleInconsistent`, `recoverKeepLocal`, and `selfInconsistentCustodyRecord` doc comments own the mechanics and the per-shape rationale. First, the settlement is fail-closed and never a shortcut past unique content: every reachable copy of the recorded head is pinned before anything moves, a head that still exists but cannot be pinned refuses, the gate moves only by CAS, and the settlement's own refusals plus every refusal raised while keep-local moves the gate branch name `inspect_and_reconcile_manually` (`blockedPlan` nils `NextAction`, so this is per-site via `recoverBlocked`, never a global guarantee). The one failure that is not a refusal gets the same treatment: `finishRecover`'s stamp failure runs AFTER the Git side already succeeded, so it reports that the changes are applied and names the same recovery command again (`recoveryRetryAction`), which completes because every Git step it repeats is idempotent once applied. Second, absence must be PROVEN, not inferred: `git.CommitPresence` treats ONLY git's exit 1 (the store was read and the object is not there) as an absence, because the settlement's whole safety argument is "nothing still has this head, so nothing can be lost". An unreadable store and a present-but-wrong-type object (`cat-file -e` exits 0 for a tree, blob, or tag) are both undetermined and refuse; collapsing either into "absent" let the settlement complete while the object was still there. `settlementAnchorsFree` mirrors the same probe so the advertisement agrees with the write. Third, the predicate may name the settlement only where `Recover(keepLocal)` reaches it AND it can complete, so unverified (#707's scope), uninspectable, symbolic, and mid-adoption evidence, plus an unreadable gate branch or an occupied stranded/gate anchor, all fall back to manual reconciliation. Advertising any of those recreates the very wedge this change removes.
Write ordering inside `recoverKeepLocal` is load-bearing: `refs/no-mistakes/recover-gate/<run>` guards exactly the CAS that moves the gate branch off the displaced head, and nothing before that swap can strand it, so the anchor is written immediately before the CAS and every refusal that can precede it leaves no ref of ITS OWN behind - which is what makes that half of the claim true by construction rather than by a cleanup that could itself fail. A refusal only speaks for the whole attempt if it also carries what its CALLER wrote, so `recoverKeepLocal` takes an anchor note (`keepLocalNoChangeClause`): a delegation that anchored the preserved head at `refs/no-mistakes/recover/<run>`, or a settlement that pinned it at `refs/no-mistakes/recover-stranded/<run>`, makes every PRE-SWAP refusal report "no branch, worktree, or file changes were made" plus where that anchor now is, and only a delegation that wrote nothing makes the blanket "no files or refs were changed" claim. The lost CAS is excluded from that substitution on purpose: it has already written the gate anchor, so it keeps its own narrower "no LOCAL files or refs were changed" claim and APPENDS the note. The anchor CONFLICT check stays first because it is read-only. Exactly one post-write refusal remains, the lost CAS, and it deliberately keeps the anchor: the swap failed because the gate moved, so that pin may be the only ref still naming the displaced head, and its message says "no LOCAL files or refs were changed" and names the ref to reconcile.
The full relation matrix and fail-safe rules live in the `Recover` doc comment in `internal/branchsync/sync.go`.
- Public guidance is owned by `internal/skill/skill.go` plus live AXI strings, then regenerated with `make skill`. Core regressions live in `internal/branchsync` (incl. `recover_test.go`, whose `wedgedCustodyFixture` is the #824 shape), `internal/cli/sync_test.go`, `internal/cli/axi_abort_custody_test.go`, `internal/cli/axi_settle_custody_test.go`, `internal/tui/branch_sync_test.go`, and e2e `TestAxiBranchSyncJourney` / `TestAxiCustodyRecoveryJourney` / `TestAxiCustodyRecoveryAfterRebaseJourney` / `TestAxiPrePushAbortUnmovedHeadCustodyJourney`.

**Post-Review Head Continuity and Push Binding**

- Every step after Review in the fixed pipeline order (Test, Document, Lint, Push, PR, CI) calls `assertPipelineHeadContinuity` at entry. The helper is the single semantic owner: equal or descendant live heads continue; backward, sibling, and unverifiable heads fail before the step performs work. Regression: `TestPostReviewStepsRefuseHeadClobberAtEntry`.
- A successfully completed full review atomically records `runs.review_approved_head_sha`; parked, failed, skipped, and legacy reviews carry no inferred authority. Push reads that durable binding, permits only the exact commit or a descendant, and pushes the verified immutable SHA rather than mutable `HEAD`. Never infer approval from `runs.head_sha`, a worktree, gate ref, or remote branch. Regressions: `TestPushStep_RefusesPostReviewClobberWithoutLaterPipelineCommit`, `TestPushStep_BindsRemoteAndDatabaseToVerifiedCommitWhenHEADMovesDuringPush`, `TestExecutor_FullRereviewReplacesApprovalWithoutAuthorizingParkedRound`.

**Rebase Base & Force-Push Safety (data-loss prevention)**

- The whole job of this tool is to not lose people's code; favor refusing the push and surfacing a finding over any clever recovery. The comments in `internal/pipeline/steps/forcepush.go` own the full reasoning; the invariants are the next three bullets.
- Rebase bases come from the freshly fetched authoritative remote refs, never local or stale state; and a branch built on unpushed local-default-branch commits parks with `NeedsApproval` + `AutoFixable=false` instead of silently widening the PR (`detectBundledLocalDefaultCommits`, #283).
- Every force-push routes through `resolveForcePushDecision`, which re-reads the live remote head and allows the push only for a new branch, an already-equal remote, an unchanged `lastSeenSHA`, or remote commits already incorporated by patch-id (excluding `^baseSHA` history the run knowingly rewrites). Anything else refuses, and a failed ls-remote/fetch fails closed; never degrade to a bare `--force`/`--force-with-lease` without an explicit anchor.
- `lastSeenSHA` must stay the head the run last **observed** (from run/prior-run push provenance or the remote-tracking ref), never the live remote tip: the rebase step refreshes `origin/<branch>` only on a normal push, NOT on a force push. CI repairs commit locally and restart validation at Review; the later Push step owns their remote update and force-push safety. Anchoring a lease to a SHA read immediately before pushing is the original #281 bug (it always passes and protects nothing); always-fetching the branch on force push recreates it. Never reintroduce either.
- Regressions: `TestPushStep_RefusesToClobberAdvancedUpstreamBranch` (#305), `TestForcePushRun_RefusesToClobberOutOfBandBranchCommit`, `TestRebaseStep_DetectsUnpushedLocalDefaultBranchCommits` (#283), `TestResolveForcePushDecision_*`, `TestExecutor_CIRestartRevalidatesBeforePush`, `TestPushStep_AllowsForcePushAfterMidRunRebaseOverPriorPushedGeneration` (#837), `TestPushStep_AllowsForcePushOnRerunOverPriorRunPushedGeneration` (#837).
Loading
Loading