Repository navigation
v0.4.2
·
50 commits
to main
since this release
Immutable
release. Only release title and notes can be modified.
- The dispatcher advances an open release PR at the start of a pass, before landing a ticket, and opens new release PRs only at the end of a pass: a release PR opened at the end of one pass used to turn conflicting on
CHANGELOG.mdas soon as the next pass landed a ticket, and was closed and rebuilt forever. - A worker that passes the gate, commits nothing and writes its handoff (its ticket was already fixed or a duplicate) now escalates as
no_changewith "worker committed nothing over ; read the handoff", notpr_unpublished, andfactory watchnever files repeatedno_changeescalations as a factory defect (#300: #278, #279 and #291 were watch-filed tickets already fixed on main). A worker that commits nothing and writes no handoff (it crashed or exited early) still escalates aspr_unpublished, which watch keeps counting. The workspace capacity test now waits for request admission before probing the only client slot, so its probe cannot make the server reject the connection under CI scheduling. - Generated units quote
[install].env/dispatch_envvalues that contain spaces or other systemd-significant characters: an unquotedEnvironment=UV_EXCLUDE_NEWER=24 hourssetUV_EXCLUDE_NEWER=24, which made everyuvcommand in the dispatcher and gate exit with a usage error. - Runtime projection no longer reports
identity_conflictfor the dispatcher's ticket scope (#293): that scope advancesattemptandreview_roundin place so worker, gate and review children inherit them, so every live journal with a retried or reviewed ticket tripped the check. Only fields fixed at scope creation (dispatcher_run_id,root_execution_id,execution_id,parent_execution_id,ticket,stage) must agree across an execution's rows; the projected execution reports its latestattempt/review_round. - Managers may use
AUTHORIZEwith an auditable justification and worker guidance to permit a required edit to an existing[gate].add_only_pathsfile, whether or not a PR exists. Factory addsfactory-add-only-overrideand requeues the ticket through the normal gate and independent review. The label permits in-place edits only: deleting, renaming or changing the mode of an add-only file still fails, and a path matching both a protected and an add-only glob is protected (an additive change to it no longer passes without a label). Fully protected paths such as.factory.tomlstill require the human-onlyfactory-protected-override. - Automatic patch releases ratchet quality from the latest reachable semver tag:
factory releasemeasures merged-ticket human and first-pass rates, requires enough tickets, new CI-greenmaincommits, a no-worse recorded bar, and the release-tier gate, then converges in up to three dispatcher passes by opening a factory-owned release PR, squash-merging it after its requiredgatecheck passes, and annotating the merged commit while advancingstable;[release].autoruns it after successful dispatcher passes and--dry-runreports the remote-derived state and next action without writes. factory watchfiles one issue per defect, not per place it showed: the fingerprint is now kind + reason/unit/source, so a reason seen in several lifecycle stages (FileNotFoundError: 'omp'inworker,ticketanddispatcher: #256/#257/#260) or several reader codes from one journal (missing_enter,identity_conflict) make onebugwhose title lists the stages or codes. Host conditions the dispatcher already retries or escalates are never filed:worker_exit:*,triage_endpoint_*,github_command_failed. Existing open watch issues keep their old fingerprint, so a still-present signal refiles once under the new one.- Upstream sync through the ordinary pipeline (#151):
sync_passno longer pushesmain. It stages the candidate in a detached worktree (upstream tip merged--no-ff, or the newest conflict-free prefix when the tip conflicts), runs the host gate on a clean merge (that dispatcher-only run keeps--skip leak-scan --protected-override: nothing in it is worker-written), files oneupstream sync:issue labelledready-for-agentwhose body carries the exact conflict list or gate report, and hands the candidate toagent/<n>(attemptrow withattempt: 0,upstream-syncrow with the tip and staged head; lifecycle stageupstream-sync, reasonupstream_staged). The ticket then runs the normal worker → gate → review → PR → CI → merge stages: a staged candidate whose gate passed skips the worker, a failed one is repaired withinmax_attempts/budget_minwith the gate rerun after every attempt,worker_roundrejects a passing gate whose head lacks the recorded upstream tip, and exhausted attempts escalate like any ticket. The worker's gate gets no bypass:factory gate --upstream REF(repeatable; the dispatcher passes the tip and the staged head, also on refresh and manager FIX) ignores only protected-path hits whose HEAD content equals the staged head's, or the tip's for a path upstream itself changed, so worker edits to protected or add-only paths, including deleting or resetting fork-only files, still need the humanfactory-protected-overridelabel, and leak-scans the diff from the first REF, so worker-written content is scanned while upstream's is not. The merge stage keeps merging (not squashing) PRs carrying upstream ancestry. This deliberately removes 0.3.8's direct landing of the conflict-free prefix (d77f625, PR #117): a staged prefix reachesmainonly inside the reviewed PR, so a persistent conflict delays the whole batch rather than advancingmainunreviewed.sync_escalateand theupstream_syncescalation code are gone; an openupstream sync:issue, worker- or human-owned, still parks the stage until it closes. - Dispatcher root pinning (#181): every non-dry-run pass starts with
git fetch origin <main>andgit checkout --detach origin/<main>in the main checkout, and only then loads.factory.toml(the pin reads just[repo].mainfrom the raw file, so a stale root whose config the engine rejects still picks up the fix from origin/main), so config, lessons and the dashboard theme come from the committed tip rather than whatever branch or edits the root carries. A root with tracked or untracked changes outside.factory/is left untouched: the pass exits 1 with aproject_escalation/root_dirtydispatcher exit row and aresultrow listing the paths, and claims nothing. - Refused
agent/<n>push no longer strands the ticket (#244): when the remote branch gained a commit the worktree lacks (GitHub "Update branch"), the refused lease is escalated withcode="lease_refused"— issue un-assigned,ready-for-human, one comment — and the worktree is kept, on both the first push and review-bounce pushes. Any other exception after the@meclaim escalates withcode="dispatcher_error"before re-raising, so a dispatcher crash hands the ticket back instead of leaving it assigned +ready-for-agentto be skipped forever. - Ops Attention never offers Re-queue agent for
upstream sync:issues (#169): each dashboard ticket carriessync(title starts withdispatch.SYNC_TITLE), and the workspace routes a flagged ticket to the system-upstream card with resolve-and-close guidance even when it is not the snapshot's upstream blocker. Park/resume and therequeue_agentrefusal are unchanged; no label is added. - Escalation-reason experiment (#163):
experiments/escalation-reasons/hand-labels everyready-for-humanescalation in this repository's journal asrequirement/eval/environment/model/knowledge(62 escalations after folding re-fires, 42 tickets) and ships a deterministicclassify.pythat recovers thecodeof pre-#241 rows from their prose and agrees with the hand labels on 60/62;REPORT.mdrecommends a fixedcode → taggrouping and two code-driven routing rules, and dropsknowledge(3 secondary labels, 4.8%). Routing is unchanged. - Missing worker executable waits before claiming (#257): a frontier claim probes the ticket's worker executable on the dispatcher's PATH before
--add-assignee; when it is absent (for example during anompupgrade) the ticket stays unclaimed with aworker_unavailable/retry_next_passwait row instead of aFileNotFoundErrormechanism failure after assignment.dispatch --ticketis unchanged and still fails loudly. factory watchno longer filesevents.jsonlunsupported_recordas a runtime failure (#267):dispatch.recordaudit rows share the journal with lifecycle rows by design, so every live journal reports that code; it joinstransitioningandbyte_limitas a steady state the monitor skips.- A missing worker or reviewer executable no longer kills the pass (#260): when a worker launch raises
FileNotFoundError(e.g.ompdisappears from the service PATH after the frontier probe, or on an explicit--ticketrun) on any attempt or review bounce, the ticket journalsmechanism_failurewith that reason, logsworker launch failedwith PATH/configuration guidance (factory doctor), removes the@meassignee so the frontier retries once the host is fixed, and the dispatcher continues with the remaining tickets andlearn;dispatch --ticket Nstill exits 1. When the reviewer cannot be started (land_passrefresh, ticket review, manager FIX), the review is rejected evidence (REVISE, reasonFileNotFoundError: …) and the caller withdraws or escalates as for any rejected review.factory watchfingerprints worker launch failures at theworker/ticketstages, not as adispatchercrash. - Runtime projection (#269): an execution whose
enterrow lies before a clipped journal window (byte_limit/event_limit) is no longer reported as amissing_entererror; the window truncation is already inhistory.gapsand the execution stayspartial.missing_enterstill fires when the retained rows start at sequence 1 without anenter, or when the window is complete. - Test suite no longer journals into the live ledger (#254): a worker runs
tests/inside the dispatcher's handoff env, so every fixture that opened a lifecycle scope (the gate'ssleep 5timeout check, review verdicts, merge-eligibility outcomes) inheritedFACTORY_LIFECYCLE_CONTEXTand appended its rows to the realevents.jsonlunder the worker's ticket;factory watchthen filed the fixture'scheck_timeoutas a factory bug.tests/test_factory.pyandtests/__init__.pydrop the inherited context at import; tests that exercise inheritance set their own. - Fixture journal isolation (#253): retain the import-time context scrub in
tests/test_factory.pyfrom #254 and also dropFACTORY_LIFECYCLE_CONTEXTintests/test_gate.pybefore spawning fixture gates.tests/test_fixture_isolation.pyverifies that fixture gate helpers journal into the fixture repository, not the worker's live ledger, where intentional 1-secondcheck_timeoutexits were being reported as runtime failures. Rows journaled before the fix remain in the ledger. factory watchno longer filesevent_limit(#268): the runtime reader'sevents.jsonlhistory is clipped to the newest 512 events by design, soevent_limitjoinsbyte_limitandtransitioningas a steady-state code that is not a runtime failure.factory watchno longer filesprocdescendants_not_scanned(#284, #286): the passive runtime reader never enumerates/proc, so every open execution that launched a child (dispatcher → worker on any live pass) reports that code; it joins the steady-state codes the monitor skips. #286 was the same fingerprint refiled by a pass that ran before #284 landed.factory learnmines lowest-scored sessions (#235): each chosen ticket's evidence replaces the 40-line failing-attempt log tail (Working...for OMP workers) with an excerpt of its lowest-efficiencyscorerow's transcript (ties: lowestcompliance, then a gate FAIL): every back-to-back repeated call, the final assistant message, then every tool call whose result mentionederror/Traceback/FAILwith the result's first 10 lines, at most 3,000 characters per ticket under the existing 24,000-character evidence cap (the repeated calls come first so the cap only shortens the flagged-call list). Tickets without ascorerow keep the log tail. The lesson prompt asks for lessons that remove the cause of a repeated tool call, and the automatic end-of-pass run scores the chosen tickets' unscored attempts first.