You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added
A review of content this deployment already reviewed is replayed from a durable store instead of paying for the reviewer again.src/automation/reviewVerdictStore.ts keys a verdict on (kind, base, sha256(content digest)), the digest being every changed path paired with its content hash, and replays the stored outcome on a hit — from both the plain openswarm review path (reviewCommand.ts) and the publication hook (publicationReviewHook.ts). Measured over the recorded history (530 records, 512 carrying a verdict): 68 byte-identical pairs, 15 of them same-mode (8 direct→direct, 7 pr→pr) and 10 of those repeating the same verdict — on the order of 10 of 512 reviews, at a measured ~$0.26 and 93s p50 per reviewer call. Proven across two separate processes: a cold 27.0s / 6-model-call review became a 1.58s replay with zero model calls, same verdict. The key carries the base ref and the review mode deliberately, because 15 of the 53 cross-mode repeats in that history flipped their verdict — a CLI review of a tree and a publication review of the same tree are different reviews. What is skipped is the reviewer call; the advisor pass still runs over the reused verdict, so a replay can tighten (approve→revise/reject) exactly as a live run would and the cache cannot fail open past an escalation.
The reuse survives a daemon restart. The verdict lives in its own publication_reviews table in the automation database rather than in the in-process reviewedPublications Set it replaces, which was empty after every restart — the failure census records 141 owner_process_exited and 157 shutdown_cancelled, each of which threw away a verdict already paid for. A store that cannot be opened does not suppress a review; the hook reviews instead.
openswarm review --max --harness-only runs a deterministic quality harness with no LLM cost. The new harness (src/verify/qualityHarness.ts) enumerates every tracked source file via the git index and scans each one, then runs the discovered typecheck/lint/test/build commands inside the existing isolated verify sandbox. It is fail-closed by construction: a file over the 512 KiB ceiling, one containing NUL bytes, an unreadable path, a symlinked source, or a path that escapes the repository root each become an explicit error finding rather than a silent skip, and a listing that scanned nothing is itself an error — so the gate cannot report "passed" over a subset it never read. Findings are folded into the audit run as a synthetic .openswarm/quality-harness area, which means the markdown report and the exit-code contract carry the evidence on every --max run, not only when an LLM area happened to notice something.
Fixed
openswarm review --max no longer changes its verdict with --concurrency. The audit re-partitioned the source into reviewer areas until the fan-out saturated the pool (INT-2249), so the same files and the same reviewer produced 2 areas at concurrency 1 and 10 at concurrency 8 — and because aggregateAuditResults is worst-wins, the finer split could only turn an approve into a revise/reject. The audit's units of judgement now come from planAuditAreas, which is derived from --max-files-per-area alone (--concurrency is purely how many reviewers run at once), so a given file set gets one verdict. The finer, more parallel fan-out is still available, deterministically, by lowering --max-files-per-area; the --fix path keeps the pool-filling balanceAreasToConcurrency, where more areas only mean more parallel fix workers. The cost gate now prints the real agent-run count (the area count) and points at the granularity flag when the pool is under-filled.
A workflow execution can no longer be persisted in a state its DAG forbids.saveExecution now rejects a step marked completed without a completedAt, failed without an error, or advanced past a dependency that is still pending, running or failed — states that previously reached disk and were then read back as if the pipeline had progressed. It also gains a definitionStamp fence: a definition replaced underneath a live execution makes the next save refuse rather than record a snapshot that never ran against it.
The local issue store no longer serves a stale snapshot after a same-size replacement. The cache stamp was mtimeMs:size, which is unchanged when another process replaces the file by atomic rename within the same mtime tick and writes the same number of bytes — the process then kept serving the old contents indefinitely. The stamp now includes the inode.
A status transition's event log records the status the write actually saw.changeStatus and updateIssue read the current status inside the write transaction instead of from a pre-transaction read, so a concurrent transition can no longer stamp a stale oldValue into the audit trail.
The daily reporter retries a failed project once, and only advances its watermark when every project succeeded (after that retry). A transient Linear error previously left a project unreported until the next day, while a permanently failing project could hold the window open; the watermark now reflects post-retry reality.
A Linear project description keeps its automation summary. The compact [Done:n InProgress:n Todo:n] line was appended before truncation, so a long base description pushed it past Linear's 255-character limit and it was silently cut. The builder now reserves room for the summary first.
Repository-registry corruption that could not be quarantined says so.loadRepos reported a failed renameSync as if the corrupt file had been preserved, naming a recovery path that does not exist; the quarantine failure is now surfaced as its own error.
A failed local Linear mapping can no longer orphan-recreate the remote issue. If updateIssue/addEvent throws after Linear's createIssue succeeded, the new linearId is remembered in-process and re-persisted on the next call (with an idempotencyKey on the linked event), instead of the retry creating a second Linear issue.
Backlog grooming refuses to act on an issue id it was not given.validIssueIds is now mandatory at both parse time and apply time, so a hallucinated or out-of-scope id from the planner is dropped before any mutation path sees it, and an omitted scope means "nothing is in scope" rather than "anything goes".
Shell/SQL source under data directories still requires validation evidence.isValidationRelevantFile exempts locale/fixtures/snapshots trees as non-code, but its idea of "code" excluded sh, bash, sql, swift and others that a worker can meaningfully break; those now count.
Linear project-overview pagination surfaces a broken page walk. A response with no issues connection, or a missing/repeated endCursor, now throws instead of silently returning a truncated issue list that the overview reports as complete.
An idempotent createIssue rejects a materially different artifact under the same id. A caller-supplied id that already exists is treated as a retry only when title, description, parent and project agree; otherwise the store throws an explicit collision error instead of returning the stored row (which masked a re-planned decomposition) or overwriting it (which would destroy the first plan's artifact).
The dev.ts close handler always releases its task. Reporting ran before onComplete and activeTasks.delete, so a throw while formatting cost/output left the task registered forever; the reporting is now contained and the cleanup runs in finally.
memoryBridge emits one memory_linked event per link, not two (linkMemory already emits one).
A failed Linear SDK load is retryable. The rejected init promise stayed cached, so every later initLinearBridge call awaited the same rejection and the bridge never recovered within the process.
advisor role: a second, independent review of the same diff. Ported from the harness agent patterns. A separate, independently-prompted model is asked one narrow question — what concrete defects did the reviewer miss? — and its only permitted effect is to make the gate more cautious: it may append concrete findings the reviewer did not report and raise severity, but the merged decision is the max rank of the two (approve < revise < reject), so an advisor approve can never soften a reviewer revise/reject, and a raised severity with no concrete finding is discarded. Any error, timeout, empty, or unparseable output fails open — ran: false, the reviewer's result untouched, no exit code changed. review, review --max (per area), and the --fix re-review loop all run it before dedupe, so its findings are deduped against history like any other. Disabled by default (a second paid call per review), configured under autonomous.defaultRoles.advisor; its model must come from a different family than the reviewer's or it is a second identical opinion.
Declarative per-role subagent settings: tools and effort. Each role (worker/reviewer/advisor/…) may declare tools.allow / tools.deny and effort. An allow-list can only narrow the role's default tool set — it can never grant a tool the role would not otherwise have, so a misconfiguration cannot hand a read-only reviewer bash. deny is applied after allow (deny wins) and supports a trailing * (scratch_*). effort selects the native-loop reasoning level for the stage.