Skip to content

v0.4.0

Choose a tag to compare

@mikeroySoft mikeroySoft released this 15 Oct 04:12
· 90 commits to main since this release
Immutable release. Only release title and notes can be modified.
977fadc

Compared against 0.3.8 (fc264ad). Released channel stable points at v0.4.0. Minor bump: the first release that exposes the factory to an external agent (factory mcp); /api/act is removed and the dashboard is replaced by the outcome-control workspace.

  • factory mcp: a stdlib-only stdio MCP server (protocol 2025-06-18) exposing factory_repositories plus the four evidence reads (observe, inspect, investigate, capabilities) across every repository registered with an absolute path in the host config. Each call runs one factory evidence subprocess; evidence exit 2 becomes isError: true, abnormal children become a short tool error, and no mutation tools, resources, prompts, or network transport are offered.
  • Atlas now includes an interactive ticket-flow reference derived from the canonical map: connected step diagrams, searchable navigation, incoming/outgoing/retry conditions, and printed source evidence. The authenticated read-only graph endpoint uses bounded local documentation included in the package; browsing it does not trigger pipeline work.
  • Bounded PR evidence now selects required fields with one fixed read-only GraphQL query instead of repeating full repository metadata in REST PR lists. Observe/inspect and viability samples retain the existing byte, time and candidate limits; pagination and provider errors remain explicit unknowns. An incomplete PR read can no longer turn a PR-selected case into a false unknown_case.
  • Outcome-control workspace: one packaged, native operator application with live-only Flow, explicit attention and outcome evidence, contextual persistent FM chat, Codebase/Atlas, themes, and revision-checked repository Settings. Network serving defaults to 0.0.0.0; API access requires the owner-only workspace key and mutations require exact same-origin intent. Registered repository roots remain authoritative; District host administration is not proxied.
  • Human actions now use server-issued, expiring previews, actor/target revalidation, durable intent and restart-safe receipts; ambiguous effects are not retried. Removed the direct /api/act bypass. Upstream-sync cases cannot be requeued as ordinary agent work. Owner release/install/health/acceptance attestations are source- and initiative-revision-bound, distinct from machine verification or deployment authority.
  • Dashboard observation no longer locks or writes the lifecycle journal. Single-flight bounded background collection keeps cached reads responsive; wait/WIP and Ops totals expose retained-window coverage rather than claiming complete history. CLI and Codebase extraction remain available independently.
  • Report-only mutation testing as a pr-tier gate check (#197): .factory.toml adds mutation (when = "pr"), which runs scripts/mutation.py once per PR head. It mutates only the Python files under factory/ changed in origin/main..HEAD with cosmic-ray (pinned in the dev-only mutation dependency group), runs the full unittest suite against each mutant in parallel copies of the tree, and prints every survivor as path:line: <operator> \old` -> `new`. Mutants are sampled deterministically (sorted, shuffled with seed 197, at most 200, those on changed lines first) and one absolute deadline (10 minutes, or [gate].timeoutminus 60s if smaller) covers preparation, the baseline and every mutant: a mutant starts only if its full timeout fits before the deadline, and any left unrun are reported asincomplete. It exits 0 whatever survives and nonzero only when the tool itself cannot run, including a mutant cosmic-ray could not apply or test; a diff without factory/Python changes printsno mutable files. The pr-tier gate report now keeps the output of passing checks (## output`, last 80 lines), so report-only findings reach the reviewer.
  • Automatic factory learn (#212): with [manager] configured, the dispatcher runs factory learn --last 10 at the end of a pass once 10 tickets have finished (latest merged/escalate row) since the last learn event, manual or automatic, and journals it with trigger: "auto". It skips while an agent/lessons-* or agent/curate-* PR is open, keeping the count; without a manager it logs learn: skipped (no manager) and records nothing. A failure is journaled as learn with status: "failed" and a reason, never fails the pass, and keeps the count so the next pass retries. Lessons and curate branches are now agent/lessons-<date>-t<N> / agent/curate-<date>-t<N>, N the newest included ticket, so same-day runs no longer collide. learn.run() is the callable entry point; an unparseable model reply now raises ValueError instead of SystemExit.
  • Approved-PR refresh no longer escalates on concurrent CHANGELOG.md Unreleased lines (#204): .gitattributes sets CHANGELOG.md merge=union, so a rebase or merge onto moved main keeps both sides' lines. Escalation round numbers now count escalate rows in rotated events.jsonl.N.gz segments too; after a rotation the next escalation was numbered from 1 again, mapped to an already-answered handoff request id (<ticket>/<round>), and got no manager round or new handoff request. The PR frontier's own manage rows (which carry pr and number rounds per PR) no longer count as an escalation's manager round: an escalation on a ticket with an open PR whose round matched a frontier decision used to skip the manager and go straight to a handoff request. Review-intake escalations (opted-in PRs, escalate rows with pr) now get a handoff request on their new ready-for-human issue; they still never get a manager round.
  • Candidate escaped defects (#194): factory stats --escapes lists, per closed bug issue, its fix commits (first-parent agent/<bug>: commits on origin/<main>, else the merge commit of a merged PR that closes it) and the candidate tickets whose agent/<m>: commits git blame at the fix's parent names for the modified or deleted lines, with line counts. Otherwise it reports unlinked, no_blamed_lines or no_candidates. Regressed-by: #N in the bug body overrides blame (source: declared). The footer reads Candidate escapes: X of Y merged agent tickets (Z%); --json prints the rows. factory learn evidence ends with a "Candidate escapes" section for the 10 most recently closed such bugs. Read-only; no model calls, events or GitHub writes.
  • A triage model timeout skips that ticket instead of ending the pass (#208): the ticket keeps needs-triage, nothing is posted, its triage-ticket scope still records triage_endpoint_timeout, and the pass exits 1. The next pass tries tickets whose last triage attempt timed out after the others, so one slow ticket no longer starves older ones. A refused or unreachable endpoint (including a connect timeout, which still records triage_endpoint_timeout) still ends the pass with exit 2.
  • Meta-loop verification changes (#195): factory learn's CURATE diff may now append [[gate.check]] tables and [gate].protected_paths entries to .factory.toml and add or edit files under the base ref's protected paths (e.g. tests/**). Both TOML versions are parsed; removing or editing an existing check, removing a protected path, changing [leak_scan], any other .factory.toml change, deleting or renaming a protected file, or touching .github/ rejects the whole diff with a reason and pushes nothing. CURATE_OFFER asks for a gate check or regression test first, then an AGENTS.md standards entry, then a prose lesson. Curate and lessons PR bodies state "Harness change from the meta loop — human merge required."; the merge stage and manager frontier never pick them up. Escalation-time CURATE, the worker protected-path guard and the reviewer are unchanged. promotion.CHANGE_KINDS adds gate-check.
  • PR-tier gate checks (#196): [[gate.check]] takes when = "attempt" (default) or when = "pr"; any other value is a config error naming gate.check[<i>].when. factory gate (worker and dispatcher, every attempt) skips pr checks and reports them - <name>: SKIP (pr tier); factory gate --tier pr runs only them. After the attempt gate passes and before each review the dispatcher runs the pr checks once per head (.factory/gate-report-<n>-pr.md, same timeout and exclusive lock) and appends that report to the reviewer's gate report. A pr-tier FAIL goes back to the worker as findings and uses one review_rounds slot; exhausted rounds escalate with pr checks failed. The manager FIX review runs the pr checks the same way and escalates pr checks failed on a FAIL. The merge-stage refresh re-gate runs the attempt tier only. Without pr checks nothing changes.
  • Human-correction metrics (#199): per agent PR, factory stats counts human_change_requests, human_comments (review-thread and conversation) and human_commits (human author or committer), read for any PR state through feedback.correction_reads. The actor is factory when it is a Bot or the dispatcher's [install].dispatch_env login, and human otherwise. Every action before that login's first observed action is unattributable and left out of every percentage (n/a). human_resolved_pct now uses the same rule: User removals are no longer human by default, and with dispatch_env unset every resolution is unattributable. The stats footer adds Human-touched PRs, and factory dashboard --json adds metrics.human_touched_pct, the share of attributed merged agent PRs with any human correction. The dashboard reads each merged PR's corrections once per process.
  • Machine-user dispatcher identity (#198): host [install].dispatch_env is rendered only into the triage+dispatcher unit (unset: units unchanged), so the dispatcher's gh and git push can run as a separate account while the dashboard keeps the human one. factory doctor reports both gh api user logins and fails when dispatch_env is set and they match. Worker and reviewer subprocesses run without GH_TOKEN/GITHUB_TOKEN and with GH_CONFIG_DIR set to a fresh empty directory. Workers only commit; the dispatcher pushes agent/<n> (first push, review bounce, refresh, manager FIX) with --force-with-lease=agent/<n>:<sha> against the remote sha it last fetched or pushed: a worker history rewrite lands, a push by anyone else since then is refused instead of overwritten, and no other ref is ever force-pushed. Review-request opt-in accepts the dispatcher's and, under dispatch_env, the dashboard's login.
  • Worker briefs rerank the noun-matched top-40 file pool with Jev when TYPESAFE_API_KEY is set; a missing key or provider error falls back to noun order. Tracked README.md and CHANGELOG.md are added whenever at least one file matched (noun-free issues still get no brief), and successful rerank cost is logged (#166).
  • Ticket flow map (#168): docs/ticket-flow.md draws every path a ticket can take (view, state, action, out, gap), each edge citing its factory/ symbol; tests/test_ticket_flow_map.py fails when a label transition or escalate reason in factory/ and the map disagree. Linked from the README Architecture section.
  • Gate checks run without FACTORY_LIFECYCLE_CONTEXT (#170): a repo's own test suite no longer appends its fixture rows to the live .factory/events.jsonl. The gate's own enter/handoff/child_start/result/child_exit/exit rows are unchanged.
  • factory doctor resolves worker, worker-wrap, reviewer, manager and gate-check binaries against the installed unit's PATH, honoring the last assignment and naming the PATH source in missing-binary diagnostics (#171).
  • Root AGENTS.md (#160): a feature map of every factory/ module with its entry files, tests and gate checks, linking to README.md and skills/factory/SKILL.md. tests/test_agents_map.py fails when a module is unmapped or a mapped path does not exist.
  • Idle dispatcher passes write no lifecycle rows (#173): the dispatcher, scheduling and triage scopes buffer their trail and drop it when the pass claims, triages, merges, waits on or fails nothing (was ~31 rows per pass). Any written row flushes its buffered ancestors first, so passes that act keep the full trail. The dashboard heartbeat reads systemd, not the journal, and still reports the last pass.
  • Engine drift (#172): each dispatcher pass's enter row records engine_version and engine_commit (District's [defaults.engine].sha, else the commit-named uv install source). When the managed repository is factory itself, factory doctor WARNs installed engine and factory dashboard --json sets engine.warning if that commit differs from origin/<main>.
  • Leak scan stops flagging generic words (#167). The default pattern drops bare internal and private and keeps only word-bounded strong signals: internal hostname suffixes, confidentiality notices, ticket-tracker names and Atlassian Cloud URLs (see DEFAULT_LEAK_PATTERN in factory/config.py). Repos that relied on the bare words lose that coverage; add them back with [leak_scan] extra = ["internal", "private"]. New [leak_scan].extra regexes, from the repo file and from host [defaults.leak_scan]/[repo."owner/name".leak_scan] (for codenames that must not be committed), add to the pattern, never replace it. Failures list each hit as path:line: [matched term] line.
  • Cancelled CI checks that never got a runner are infrastructure, not code failures (#164): merge eligibility, the post-bounce recheck, and review readiness rerun the run's failed jobs once (gh run rerun --failed), keep factory-approved, and wait as CI pending; while the run is still in progress (another check pending, or GitHub reports the run not completed) they wait without rerunning. A starved rerun escalates once as "CI runner unavailable" naming the checks, label kept. Cancels that ran steps still count as failed. Journal events ci-infra and ci-rerun.
  • Separate worker timeouts (#162): [dispatch] idle_timeout (minutes, default 30) kills a worker with no log output and no worktree change as stuck, a failed attempt on the normal retry path; [dispatch] exit_grace (seconds, default 300) terminates a worker that committed or wrote its handoff but stayed quiet without exiting, then the dispatcher gates it. budget_min stays the overall ceiling and now also stops a running worker. Attempt journal rows (timeout, reason) and escalation packets name the timeout that fired. [triage] timeout (default 60 s) replaces the hard-coded triage request limit.
  • Worker usage (#161): every worker attempt journals one llm-usage event with stage = "worker", ticket, attempt, and model, prompt_tokens, completion_tokens, cached_tokens, cost as the worker reported them (null when not). A table [workers.<label>] may set usage to dotted JSON paths summed over its log's JSON lines (optional match filter); cost falls back to [dispatch] cost_pattern. factory stats adds worker, per-stage dollar and per-ticket total columns; an optional host [prices."<model>"] table (USD per million tokens) prices events that report tokens but no cost. Triage and OMP review usage events now carry model.
  • Gate reports and lifecycle journals record ROCm, kernel, and amdgpu versions once per run; unavailable probes do not fail the gate (#154).
  • B1 isolated build experiment (#98): experiments/b1-build/build.py (start/status/stop/report) runs its case inside sandbox.py's boundary. That boundary is Bubblewrap with new user/PID/network/IPC/UTS/cgroup namespaces, --disable-userns, --clearenv, read-only /usr, private /tmp//home, and no /run, /etc, /sys or host home. It mounts only a read-only git archive checkout, read-only frozen inputs and a private tmpfs output directory. A transient systemd-run --user scope (1 GiB, no swap, 2 CPUs, 64 tasks), timeout and a per-file size cap enforce the limits; a host shim reads the applied cgroup limits back and refuses to run on mismatch. An unavailable or breached boundary makes the run invalid, never a host fallback. Inputs (raw issues, the existing evidence investigate initiative JSON) and a deterministic fact oracle are frozen on the host before rendering. Reports separate safety probes, input validity and product results. Runs land under the main root's .factory/experiments/b1-build/, three iterations per lineage; zero model/provider calls. Experiment-only: nothing in dispatch or workers uses the sandbox.