Skip to content

v0.4.2

Choose a tag to compare

@mikeroySoft mikeroySoft released this 19 Oct 23:47
· 50 commits to main since this release
Immutable release. Only release title and notes can be modified.
ef4f3fe
  • 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.md as 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_change with "worker committed nothing over ; read the handoff", not pr_unpublished, and factory watch never files repeated no_change escalations 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 as pr_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_env values that contain spaces or other systemd-significant characters: an unquoted Environment=UV_EXCLUDE_NEWER=24 hours set UV_EXCLUDE_NEWER=24, which made every uv command in the dispatcher and gate exit with a usage error.
  • Runtime projection no longer reports identity_conflict for the dispatcher's ticket scope (#293): that scope advances attempt and review_round in 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 latest attempt/review_round.
  • Managers may use AUTHORIZE with an auditable justification and worker guidance to permit a required edit to an existing [gate].add_only_paths file, whether or not a PR exists. Factory adds factory-add-only-override and 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.toml still require the human-only factory-protected-override.
  • Automatic patch releases ratchet quality from the latest reachable semver tag: factory release measures merged-ticket human and first-pass rates, requires enough tickets, new CI-green main commits, 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 required gate check passes, and annotating the merged commit while advancing stable; [release].auto runs it after successful dispatcher passes and --dry-run reports the remote-derived state and next action without writes.
  • factory watch files 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' in worker, ticket and dispatcher: #256/#257/#260) or several reader codes from one journal (missing_enter, identity_conflict) make one bug whose 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_pass no longer pushes main. 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 one upstream sync: issue labelled ready-for-agent whose body carries the exact conflict list or gate report, and hands the candidate to agent/<n> (attempt row with attempt: 0, upstream-sync row with the tip and staged head; lifecycle stage upstream-sync, reason upstream_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 within max_attempts/budget_min with the gate rerun after every attempt, worker_round rejects 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 human factory-protected-override label, 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 reaches main only inside the reviewed PR, so a persistent conflict delays the whole batch rather than advancing main unreviewed. sync_escalate and the upstream_sync escalation code are gone; an open upstream 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> and git checkout --detach origin/<main> in the main checkout, and only then loads .factory.toml (the pin reads just [repo].main from 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 a project_escalation/root_dirty dispatcher exit row and a result row 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 with code="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 @me claim escalates with code="dispatcher_error" before re-raising, so a dispatcher crash hands the ticket back instead of leaving it assigned + ready-for-agent to be skipped forever.
  • Ops Attention never offers Re-queue agent for upstream sync: issues (#169): each dashboard ticket carries sync (title starts with dispatch.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 the requeue_agent refusal are unchanged; no label is added.
  • Escalation-reason experiment (#163): experiments/escalation-reasons/ hand-labels every ready-for-human escalation in this repository's journal as requirement/eval/environment/model/knowledge (62 escalations after folding re-fires, 42 tickets) and ships a deterministic classify.py that recovers the code of pre-#241 rows from their prose and agrees with the hand labels on 60/62; REPORT.md recommends a fixed code → tag grouping and two code-driven routing rules, and drops knowledge (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 an omp upgrade) the ticket stays unclaimed with a worker_unavailable / retry_next_pass wait row instead of a FileNotFoundError mechanism failure after assignment. dispatch --ticket is unchanged and still fails loudly.
  • factory watch no longer files events.jsonl unsupported_record as a runtime failure (#267): dispatch.record audit rows share the journal with lifecycle rows by design, so every live journal reports that code; it joins transitioning and byte_limit as 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. omp disappears from the service PATH after the frontier probe, or on an explicit --ticket run) on any attempt or review bounce, the ticket journals mechanism_failure with that reason, logs worker launch failed with PATH/configuration guidance (factory doctor), removes the @me assignee so the frontier retries once the host is fixed, and the dispatcher continues with the remaining tickets and learn; dispatch --ticket N still exits 1. When the reviewer cannot be started (land_pass refresh, ticket review, manager FIX), the review is rejected evidence (REVISE, reason FileNotFoundError: …) and the caller withdraws or escalates as for any rejected review. factory watch fingerprints worker launch failures at the worker/ticket stages, not as a dispatcher crash.
  • Runtime projection (#269): an execution whose enter row lies before a clipped journal window (byte_limit/event_limit) is no longer reported as a missing_enter error; the window truncation is already in history.gaps and the execution stays partial. missing_enter still fires when the retained rows start at sequence 1 without an enter, 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's sleep 5 timeout check, review verdicts, merge-eligibility outcomes) inherited FACTORY_LIFECYCLE_CONTEXT and appended its rows to the real events.jsonl under the worker's ticket; factory watch then filed the fixture's check_timeout as a factory bug. tests/test_factory.py and tests/__init__.py drop 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.py from #254 and also drop FACTORY_LIFECYCLE_CONTEXT in tests/test_gate.py before spawning fixture gates. tests/test_fixture_isolation.py verifies that fixture gate helpers journal into the fixture repository, not the worker's live ledger, where intentional 1-second check_timeout exits were being reported as runtime failures. Rows journaled before the fix remain in the ledger.
  • factory watch no longer files event_limit (#268): the runtime reader's events.jsonl history is clipped to the newest 512 events by design, so event_limit joins byte_limit and transitioning as a steady-state code that is not a runtime failure.
  • factory watch no longer files proc descendants_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 learn mines 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-efficiency score row's transcript (ties: lowest compliance, then a gate FAIL): every back-to-back repeated call, the final assistant message, then every tool call whose result mentioned error/Traceback/FAIL with 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 a score row 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.