Changelog¶
All notable changes to this project are documented in this file. The format follows Keep a Changelog, and versions follow SemVer.
Unreleased¶
Fixed (Phase 23 / DF-LIVE-08 opening file block indentation tolerance)¶
LocalCoderHarness._parse_file_blocksopening fence regex (_FILE_OPEN_RE) now allows up to 3 leading spaces of indentation (^ {0,3}), matching_FENCE_REand CommonMark code fence specifications. This prevents valid file blocks generated by models whose tokenizers emit a leading space token after[/INST](such as Mistral-7B) from being discarded as "no recognizable file code blocks".
Fixed (Phase 22 / Adversarial follow-up P1 strict audit schema)¶
AdversarialAuditor._parse_responsenow strictly validates the auditor model's output schema. Missing required fields (passed: bool,summary: str,findings: list), invalid types (including string"false"or"true"forpassed), non-dict finding entries, or missing finding summaries are now rejected as an unsuccessful audit (passed=False) with an explanatoryWARNfinding in categoryregressiondetailing the schema error, preventing malformed model outputs from falsely reporting success.
Added (Phase 22 / Adversarial follow-up P2 host isolation reconciliation)¶
- User-facing documentation across
docs/architecture.md,README.md, anddocs/index.mdhas been reconciled withspec.mdto explicitly state the boundaries ofGitWorktreeSandbox: it provides repository-level worktree isolation, preventing concurrent runs from colliding and bounding file modifications, but does not provide OS process, kernel namespace, or network isolation (process sandboxing is tracked inplan.md§10.5).
Added (DF-LIVE-06 scenario acceptance and patch-scope enforcement)¶
- Evaluation scenarios now explicitly specify
target_filesandprotected_paths, propagated toTaskSpecduring benchmark runs. Any generated patch modifying or introducing files outside declared targets is rejected asout_of_scope_patch(FAILED). - Benchmark test suites in all four evaluation scaffolds (
primes,calculator,textutils,csvparse) now cover boundary conditions and negative cases (e.g.n <= 1and composite numbers foris_prime, zero and negative operands forcalculator, tabs and newlines fortextutils, empty and quoted fields forcsvparse), preventing weak or broken implementations from falsely passing.
Fixed (DF-LIVE-05 eval campaign resilience and backend error handling)¶
- An unhandled backend exception during evaluation (such as
LocalEngineOfflineErroron Prism 503 or engine disconnect) no longer crashes the benchmark with an unhandled traceback or discards completed results. Each failed attempt is recorded as a structuredEvalRunResult(converged=False,status="FAILED", with the error details), the campaign report is persisted, and subsequent attempts continue under a bounded policy (halting after 3 consecutive backend errors). The CLI exits with a non-zero status code when backend errors occur, while operator cancellation (KeyboardInterrupt) preserves completed runs and propagates cleanly.
Fixed (DF-LIVE-04 healing attempt counting)¶
- Self-healing loop no longer overcounts repair attempts when harness errors (unparseable file blocks, syntax errors, or token-limit truncation) exhaust the retry budget. The retry check now happens before incrementing
healing_attempts, matching the gate-failure branch, sohealing_attemptsnever exceedsmax_healing_attempts(previously reportedmax_retries + 1).
Fixed (DF-LIVE-02/03/07 completion budget, truncation and reasoning)¶
- Prism requests now carry an explicit
max_tokens(the call's output reserve, at least 1024) instead of Prism's server default of 512. Telemetry recordsfinish_reason,max_tokensandreasoning_chars; a reply cut off by the token limit (finish_reason=length) fails with its own explicit reason and writes no file, instead of being reported as "no recognizable file code blocks" and retried unchanged. The engine's separate reasoning text is only measured, never parsed or executed.
Added (DF-LIVE-06a patch-scope gate)¶
- A run whose gates pass but whose patch touches a placeholder path (
path/to/..., copied from the prompt's example) or, with--target-file, any file outside the targets now endsFAILEDwithreason: out_of_scope_patchinstead ofAWAITING_REVIEW. The diff and evidence are kept andoperator_noteslists the paths.--allow-out-of-scope(TaskSpec.allow_out_of_scope) accepts such a patch and records the bypass. Observed on a live task where a green run also createdpath/to/frost_stats.py.
Fixed (DF-LIVE-01 model and endpoint routing)¶
- A planner on a different model than the coder, and
dark-factory eval --model, no longer fall back to the default model and the default Ollama/Prism endpoints.LocalCoderHarness.with_model()derives a harness that keeps the operator's URLs, timeout and context ceiling; the eval runner passes the evaluated model into everyTaskSpec. Previously an unintended default-model load could exhaust VRAM and make Prism answer 503.
Fixed (Phase 10.15 context window)¶
- Ollama silently truncated any prompt over its context window (4096 tokens here) to about half, dropping the file-format rules, so the model replied in prose. The harness now sizes
num_ctxper call (max 8192 by default, which keeps the 14B model fully in 12 GB VRAM), fails before the call with a--target-filehint when the prompt cannot fit, and reports a probable truncation as a failed step. Planner, auditor and mutator calls are sized the same way. - Self-healing no longer builds repair prompts from an unbounded gate dump (capped at 6000 characters, head and tail), and the auditor and mutator cap the diff they send, so large inputs do not trip the new pre-flight. A harness failure now leaves its reason in
verification_resultsand the operator notes. - Auto-detected context files are inlined only within a token budget (half of
max_num_ctx); larger ones become one-line stubs instead of flooding the prompt.
Fixed (Phase 14.5 review remediation)¶
- Adversarial audit: severity allowlisted (dashboard XSS), verdict derived from findings,
CRITICALoverall badge, LLM text sanitised in CLI output and evidence files, tolerant manifest loading. - Planner/auditor/mutator:
<think>blocks and prose around JSON handled, field types validated, prompt fences cannot be closed by diff content, model calls clamped to the run deadline. - Adversarial mutation: probe cleanup on every exit path, deadline during mutation ends as
TIMED_OUT, baseline gates re-run after the probe.
Security¶
- Python verification runner modules and pytest bootstrap imports are restored before gates, including ignored module/package/bytecode shadows. Explicit protected test paths retain this protection; restoration errors stop gate execution.
- Verification gate protection now also covers test-runner configuration (
pyproject.toml,setup.cfg,.coveragerc, andconftest.py/sitecustomize.py/usercustomize.pyat any depth). Previously an agent could weaken the gates by editing them. GitWorktreeSandbox.write_filerejects any path inside.git(worktreegitdir:pointer hijack).review --approverefuses to apply a patch onto files with uncommitted changes, so it can no longer commit the operator's work-in-progress.
Fixed¶
LocalCoderHarnessno longer truncates files that contain their own markdown code fences (e.g.README.md); unterminated blocks are discarded instead of written half-complete.- Gate-path detection used
lstrip("./"), which mangled.github/...intogithub/.... VerificationRunnernow short-circuits the next gate as soon astask_spec.timeout_minuteshas elapsed, instead of granting amax(1, int(remaining))second floor per gate. Honouring spec §202 ("clamps each gate's timeout to the time remaining").get_diffis independent of user git config (color.ui,diff.noprefix,diff.external, rename detection), is binary-safe, and raisesSandboxErrorinstead of returning a silent empty patch.parse_patch_fileshandles paths with spaces and quoted (non-ASCII) paths; staging uses literal pathspecs.activity_apply_patchis atomic: a failure after the branch was created restores the original branch, deletes the new one, and reverts touched files.- A run whose gates pass but whose patch is empty now ends
FAILED(empty_patch) instead ofAWAITING_REVIEW.
Added¶
dark-factory run --target-file PATH(repeatable) andTaskSpec.target_files: the files the agent edits are inlined in full as its only context, on every attempt including self-healing.ModelTelemetry.num_ctxrecords the context window requested from Ollama.- Advisory performance & quality analysis after green gates (
dark_factory.analysis): GPU VRAM/utilization and RAM sampling, phase bottleneck, tok/s, patch size, function complexity and missing-test checks with thresholds for this workstation. Recorded inanalysis.md/manifest.json, shown bydescribe,reviewand the dashboard; skip with--no-analysis. TaskSpec.timeout_minutesis enforced as a run deadline (TIMED_OUT);RunTimeoutErrorcarries partial evidence.KeyboardInterrupt/SystemExitend the runCANCELLEDwith evidence saved and the sandbox cleaned up.- Evidence is persisted before the
AWAITING_REVIEWtransition;recover()reconciles a crash in that window from the on-disk manifest. dark-factory rungained--timeout-minutes,--storage-dir,--ollama-url, and--prism-url;list/describe/review/recovergained--storage-dir, so operators can point the CLI at local engines and a journal location other than the defaults.tests/test_e2e.pynow covers the full vertical slice for every terminal path (TIMED_OUT,CANCELLED,empty_patch/FAILED, self-healing,recover()after a mid-run crash), apytest.mark.local_engine-gated smoke test against a real Ollama/Prism, and a CLI-level round trip (dark-factory run→review --approve) driven as a real subprocess.- Lightweight local performance tracing:
EvidenceManifest.phase_timingsrecords the wall-clock duration ofsandbox_create,agent_and_verify,diff_extract,evidence_preserve(everyexecute_runexit path, includingTIMED_OUT/CANCELLED), andpatch_apply(review_run). No external tracing backend — durations are persisted alongside the rest of the evidence and printed bydark-factory describe. test_e2e_real_local_model_smoke(pytest -m local_engine) now requires the real model to actually solve the task (AWAITING_REVIEW+ approved + re-verified), not just reach a clean terminal state; a newtest_e2e_real_local_model_multi_file_taskexercises the same real-model path across two files.- Concurrency/load tests:
test_concurrent_execute_run_isolated_sandboxes(8 parallelexecute_runs, checks per-sandbox isolation and SQLite WAL consistency) andtest_concurrent_review_run_serializes_git_mutations(8 parallel approvals against the same host repo — see Fixed below). test_e2e_real_local_model_realistic_bugfix(pytest -m local_engine): a diagnose-and-fix-without-regressing scenario against an existing (not stubbed) function, gated on bothpytestandruff check, matching real operator usage.test_e2e_cli_run_and_review_real_ollama(pytest -m local_engine): drives the realdark-factoryconsole script against a real local Ollama (no stub server, no--ollama-urloverride), extending 10.8's stub-backed CLI round trip.test_e2e_real_local_model_multi_iteration_healing(pytest -m local_engine): a quoted-CSV-parsing task that actually exercises the self-healing repair loop against a real model (healing_attempts >= 1), rather than tasks solved first-try.test_default_gate_paths_does_not_protect_lint_target/test_default_gate_paths_lint_step_alone_still_protects_baseline: regression coverage for the lint-target gate-protection fix above.dark-factory eval: a repeatable real-model benchmark suite (primes,calculator,textutils,csvparsescenarios, extracted fromtests/test_e2e.py's real-model fixtures intodark_factory.eval.scenariosso both share one implementation). Drives each scenario through a real local model,--repeat Ntimes, and reports per-scenario convergence rate, average healing attempts, and whether the stuck-detector fired. Runs land in a separate.factory/eval-runs/journal, never the main run history. Reports are saved as JSON under.factory/evals/<timestamp>.json.dark-factory dashboard: a local, read-only web dashboard for browsing run history, evidence, and eval reports. Built entirely on the Python standard library (http.server) — no new dependency. Binds127.0.0.1only (no host flag); every non-GETrequest returns405. Run detail pages render the unified diff with GitHub-style add/removed line highlighting (render_diff).- Adversarial Red-Team Pipeline contracts & storage: Added
AdversarialFindingandAdversarialReportdomain contracts, addedEvidenceManifest.adversarial_report(backwards-compatible with legacy manifests), and updatedEvidenceLocker.save_runto persistadversarial.mdsummaries alongside the evidence locker. - Local Adversarial Auditor Engine & Orchestrator Integration: Added
AdversarialAuditorindark_factory.verification.adversarial, executing structured red-team prompts across 4 dimensions (anti-cheating,boundary,security,regression); hookedactivity_adversarial_auditintoDurableEngine.execute_runas an audited phase timing (adversarial_audit) on green runs, with--no-adversarialCLI bypass. - Adversarial Red-Team Operator Surfaces:
dark-factory describerenders the Red-Team report with severity badges (CRITICAL,WARN,PASS) and category details;dark-factory reviewdisplays the adversarial audit findings prior to recording review decisions (and supports interactive[y/N]prompt in interactive sessions); and the local dashboard (render_run_detail) renders the Adversarial Audit card with severity badge styles and XSS-safe escaping. - Split-Model Architecture (Planner vs Executor): Decoupled high-level reasoning and architectural decomposition from code generation into two sequential phases. Added
LocalPlannerindark_factory.planning,ExecutionPlandomain contract, tracedtask_planningactivity inDurableEngine.execute_run,--planner-modeland--no-planCLI options,plan.mdevidence persistence, and rendering indark-factory describeand the local dashboard. - Active Adversarial Test Mutation (Tier 2): Added
AdversarialMutator(dark_factory.verification.adversarial_mutator) to synthesize dynamic hostile pytest unit tests (test_adversarial_probe.py) probing boundary conditions, edge cases, and anti-cheating violations. Hooked intoDurableEngine.execute_rununder traced phaseadversarial_mutationwhen--mutate-adversarial/TaskSpec.mutate_adversarialis enabled; probe failures feed back intoSelfHealingLoopto drive code repair. Preserved generated probe code inEvidenceManifest.adversarial_test_codeand saved asadversarial_test.pyin the evidence locker, while cleaning up ephemeral probe files from the sandbox worktree to keep the host repository patch clean. Rendered indark-factory describeand the local dashboard.
Fixed¶
SelfHealingLoopnow detects when a repair attempt fails with the exact same verification outcome as the previous attempt (byte-identical gate/exit code/stdout/stderr, normalized to ignore embedded wall-clock durations like pytest's own "in 0.03s" summary) and adds aSTUCK NOTICEto the next repair prompt telling the model its current approach isn't working and to try a fundamentally different one — root-caused from a real run where a local model reproduced the same conceptual bug across all 5 healing attempts. A plain exhausted-retriesFAILEDrun'soperator_notes(previously left unset) now says whether it died from a repeated identical failure or from different failures each time, andEvidenceManifest.repeated_failure_streakexposes the count directly (dark-factory describeprints it).VerificationRunner.detect_default_gate_paths()no longer protects a lint/type-check gate's own target file. Previously any step whose argv named a linter (ruff,mypy, ...) had its target file(s) treated as protected gate files and silently reverted to baseline before every verification pass — making it impossible for the agent to actually fix the file a lint gate was checking.review_run's approve path is now serialized perDurableEngineinstance (threading.Lock): concurrent approvals against the same host repository used to race ongit checkout -b/apply/commit(reproduced asfatal: Unable to create '.git/index.lock') since, unlike sandboxes, patch application mutates the host repo's actual working tree directly.CANCELLEDruns now preservediff.patchviaactivity_preserve_evidenceinstead of losing whatever the harness had produced before the interrupt.recover()also flags runs whose DB status already readsAPPROVED/REJECTEDbut whose on-disk evidence never caught up (crash between the status transition and the final evidence write) asFAILED, rather than leaving a silently incompleteAPPROVEDrecord.recover()'s orphan-detection status list is now derived fromRunStatus.is_terminalinstead of a hand-maintained SQL string list.- Git subprocess helpers (
orchestrator/activities.py:_git,sandbox/worktree.py) decode witherrors="replace", so a non-ASCII path outside the host locale can no longer surface as an unrelatedUnicodeDecodeError. Sandbox.restore_paths's contract now documents that implementations must honor:(glob)pathspec magic, not just literal paths.
Changed¶
- Docs and docstrings aligned with the code (plan §10.7): Foundry Local is no longer described as a backend (the harness is Ollama → Prism),
DockerSandboxmoved to Non-Goals inintent.md, Temporal Option C is described as a forward-looking seam (no worker registered), the unverified "<100ms" worktree startup claim was dropped, andrecover, the default protected-paths list, architecture-layer responsibilities,doctorwith one backend offline, and a GPU compatibility table are documented.tests/test_docs_alignment.pyguards these decisions and thedocs/sdlc/mirrors.
0.1.0 - 2026-09-20¶
First public release of Sovereign Dark Factory (local-dark-factory).
Added¶
- Sandbox Fabric (
dark_factory.sandbox): GitWorktreeSandbox: Lightweight, isolated execution environment created via detached git worktrees (git worktree add --detach).- Path-traversal security guards to prevent modifications outside the worktree.
- Verification gate restoration mechanism to prevent agents from tampering with test gates.
- Subprocess execution with strict argv commands, hard timeouts, and idempotent cleanup (
sandbox.destroy()). - Local Inference Harness (
dark_factory.harness): LocalCoderHarness: Multi-engine router supporting Ollama (qwen2.5-coder:14b), Prism CUDA (http://127.0.0.1:5272/v1), and Microsoft Foundry Local.- Zero cloud tokens guarantee: strictly local inference with $0.00 cloud API costs.
- AST-aware patch parsing (
parse_file_blocks) and generation telemetry collection (tokens, duration, tokens/sec). - Deterministic Verification & Self-Healing (
dark_factory.verification): VerificationRunner: Authoritative gatekeeper executing deterministic verification scripts (build.sh,test.sh,pytest).- Zero-gate safety guard: fails runs where no verification gates are discovered unless explicitly allowed.
- Self-healing loop: feeds error traces and compiler outputs back into the local model for up to
Niterative retry attempts. - Cumulative telemetry aggregation across multiple retry iterations.
- Durable Workflow Orchestrator (
dark_factory.orchestrator): DurableEngine: SQLite-backed state machine tracking task lifecycle (PENDING,RUNNING,VERIFYING,HEALING,AWAITING_REVIEW,APPLIED,REJECTED,FAILED).- Transactional state transitions and resilient crash recovery (
dark-factory recover). - Human-in-the-Loop review gate with SHA-256 patch verification.
- Evidence Locker (
dark_factory.storage): EvidenceLocker: Structured persistence for unified git diffs (diff.patch), run manifests (manifest.json), verification logs, and telemetry summaries.- CLI & Operator Controls (
dark-factory/local-dark-factory): dark-factory doctor: Inspects Ollama, Prism, GPU VRAM, git binary, and sandbox fabric.dark-factory run: Submits an autonomous coding task with optional timeout and gate overrides.dark-factory list: Lists all task runs and their execution states.dark-factory describe <RUN_ID>: Prints task manifest, conditions, gate results, and unified diff.dark-factory review <RUN_ID> --approve [--branch <NAME>]: Applies verified patch to target branch.dark-factory review <RUN_ID> --reject [--note <REASON>]: Rejects and archives run.dark-factory recover: Resumes interrupted tasks after system restart.- Documentation & CI/CD:
- MkDocs Material documentation site with API references, architectural diagrams, and SDLC guides.
- GitHub Actions CI pipeline testing across Python 3.11 and 3.12 with package wheel smoke-testing.
- Native GitHub Pages deployment pipeline.
- OIDC Trusted Publishing workflow for TestPyPI and PyPI.
SDLC unification — 2026-10-02¶
- Adopt the shared local-sdlc-kit runner and Codex workflow through AGENTS.md and .agents/skills, retaining project-specific checks in sdlc.toml.
- Document independent review, existing operator authorization and truthful reporting of blocked checks.