Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2cb7aa5
feat(fusion): register host capacity and generic agent messages
okisdev Oct 26, 2026
b8c5aeb
feat(fusion): drop the jev mixed concern check, rules for host limits…
okisdev Oct 26, 2026
4e48d4f
fix(fusion): settle engine verdicts without a workspace cwd, one salv…
okisdev Oct 26, 2026
c507566
Merge branch 'okie/settlement' into fix/okie-findings
okisdev Oct 26, 2026
95306aa
refactor(fusion): drop the unused settlement workspace root
okisdev Oct 26, 2026
ecd2c16
fix(fusion): fresh budgets for continued wrappers, role efforts, any …
okisdev Oct 26, 2026
5b1af59
Merge branch 'okie/lifecycle' into fix/okie-findings
okisdev Oct 26, 2026
b7fd7ad
fix(fusion): credit real verification output, flag untracked generic …
okisdev Oct 26, 2026
fabba36
Merge branch 'okie/guard' into fix/okie-findings
okisdev Oct 26, 2026
054e6e9
fix: classify codex network outages, report grok's json error message
okisdev Oct 26, 2026
8be5df1
Merge branch 'okie/failures' into fix/okie-findings
okisdev Oct 26, 2026
67eb2d2
fix: migrate legacy codex and grok job records instead of rejecting them
okisdev Oct 26, 2026
034985f
Merge branch 'okie/records' into fix/okie-findings
okisdev Oct 26, 2026
b2b3191
feat(fusion): host wide peer capacity gate and parent context in stats
okisdev Oct 26, 2026
21bae69
Merge branch 'okie/capacity' into fix/okie-findings
okisdev Oct 26, 2026
fb97104
fix: keep grok's record predicate, report parent context only when me…
okisdev Oct 26, 2026
9f7b3e2
chore: release 0.0.67
okisdev Oct 26, 2026
bac2075
fix(fusion): reserve host peer capacity per launch and continuation, …
okisdev Oct 26, 2026
71ac594
test(fusion): complete peer calls so capacity reservations release
okisdev Oct 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
"email": "hi@okis.dev"
},
"description": "Multi-model orchestration marketplace for Claude Code.",
"version": "0.0.66",
"version": "0.0.67",
"plugins": [
{
"name": "grok",
"source": "./plugins/grok",
"displayName": "Grok Companion",
"description": "Local Grok CLI delegation: task, review, resumable history, best-of-n tournaments, background jobs, stats, and setup health checks.",
"version": "0.0.66",
"version": "0.0.67",
"author": {
"name": "Harry Yep"
},
Expand All @@ -34,7 +34,7 @@
"source": "./plugins/codex",
"displayName": "Codex Companion",
"description": "First party local Codex CLI delegation for tasks, reviews, resumable threads, and durable background jobs.",
"version": "0.0.66",
"version": "0.0.67",
"author": {
"name": "Harry Yep"
},
Expand All @@ -55,7 +55,7 @@
"source": "./plugins/cursor",
"displayName": "Cursor Companion",
"description": "Local Cursor Agent CLI delegation: multi family consults, Composer implementation in owned worktrees, review, resumable history, background jobs, and setup health checks.",
"version": "0.0.66",
"version": "0.0.67",
"author": {
"name": "Harry Yep"
},
Expand All @@ -76,7 +76,7 @@
"source": "./plugins/fusion",
"displayName": "Fusion Orchestrator",
"description": "Multi-model orchestration: tier agents, routing rules, blind panel, ultra fleet, model config, and drift doctor.",
"version": "0.0.66",
"version": "0.0.67",
"author": {
"name": "Harry Yep"
},
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# changelog

## 0.0.67

- this release acts on the call records of a second host on 0.0.65: 24 sessions, 326 peer jobs, and 342 worker records since its upgrade.
- codex and grok job records written by older companions are migrated to the current schema on first read and rewritten under the record lock, instead of being rejected. 260 of 438 codex and 12 of 64 grok records on that host had become unreadable to `record-acceptance`, `/codex:result`, history, and retention.
- engine settlement runs the companion from its own directory, so a verdict queued for a worktree package still lands after the worktree is removed (all 43 "upgrade the plugin" settlement errors were this), and a spawn failure is reported as itself. the queued verdict path and `/fusion:stats --record` share one salvage rule, so an accepted salvaged timeout no longer fails with "pass --accept-failed-transport" (29 cases). settlement prefers the engine companion of fusion's own version, then the highest version, instead of the newest file.
- a SendMessage continuation of a peer wrapper measures its budget from the reopen; 33 of 40 continued codex wrappers had been stopped as over their wall clock right after continuing.
- grok `independence` and `live-web` dispatches get their own `high` effort default; sharing burst's model gave them `low`. cursor and grok role fields are recognized after a `·`, comma, or semicolon separator, so a cursor diversity brief no longer starts without a model.
- fusion denies a peer launch while all sessions on the host already run its peer capacity (one peer per 3 gib of memory, at least two). the host had panicked from memory exhaustion with up to 19 peer jobs in flight.
- the inline guard credits verification through the shared evidence rules (vitest, jest, pytest, and node test summaries) and treats `&&` chains and pipefail pipelines as exit observable; it had credited 30 of 1,136 passing verification commands. an Agent launched as `fork`, `general-purpose`, or `claude` gets an advisory that its work runs outside fusion collection.
- codex classifies upstream connectivity failures (workspace routing discovery timeouts, dropped streams, connection errors) as `network`, an infrastructure death, instead of `error`. grok records the message inside a multi line `Internal error: {` instead of its first line.
- `/fusion:stats` reports the median and p90 parent context at dispatch, and the parent context advisory defaults to 250,000 tokens (hosts compacting at 400,000 never reached 600,000).
- the jev brief sensor drops its mixed concern check: all 96 of its advisories were that check, and flagged dispatches were accepted as often as unflagged ones (89.2% against 90.5%).
- the rules say grok's strict sandbox is not a flag to pass (5 of 23 grok dispatches had failed on `--sandbox`), chain a wave's settlement behind its verification in one call, wait on ci with one background command instead of sleep turns, carry host constraints such as no local builds into peer briefs, and keep files a cursor package reads inside its workspace.

## 0.0.66

- the orchestration rules give the rendered browser pass of a UI package to the orchestrator after merge, and a peer brief says so: of 24 dispatched codex runs that loaded a browser verification skill, none produced a screenshot, and 14 stopped at `listen EPERM` when their sandbox refused the dev server port.
Expand Down
8 changes: 0 additions & 8 deletions bench/jev/briefs.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
[
{
"label": "mixedConcern",
"brief": "lane: codex | verification: npm test\nThe login test has been flaky since last week and nobody knows why. Figure out what is causing it and fix it. Files are somewhere under src/auth. Done when npm test passes ten times in a row."
},
{
"label": "mixedConcern",
"brief": "lane: codex | verification: node --test tests/stats.test.mjs\nfusion-stats sometimes double counts grok jobs. Investigate the root cause, then implement the fix and add a regression test in tests/stats.test.mjs."
},
{
"label": "tasteAsProse",
"brief": "lane: codex | verification: npm run build\nRewrite the error messages in src/cli/errors.ts so they feel friendly, elegant, and on brand. Make the naming cleaner and more idiomatic. Done when npm run build passes."
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "codex",
"displayName": "Codex Companion",
"version": "0.0.66",
"version": "0.0.67",
"description": "First party local Codex CLI delegation for tasks, reviews, resumable threads, and durable background jobs.",
"author": {
"name": "Harry Yep"
Expand Down
3 changes: 3 additions & 0 deletions plugins/codex/scripts/lib/codex-outcome.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ function classifiedFailureKind(message, fallback = "error") {
if (/unauthori[sz]ed|unauthenticated|authentication|login required|invalid api key|api key/i.test(text)) {
return "auth";
}
if (/workspace routing discovery timed out|stream disconnected before completion|error sending request for url|connection (?:refused|reset)|\b(?:ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN)\b|getaddrinfo|network is unreachable|Reconnecting\.\.\./i.test(text)) {
return "network";
}
if (/permission denied|operation not permitted|approval (?:required|denied)|sandbox (?:denied|violation)/i.test(text)) {
return "permission";
}
Expand Down
78 changes: 55 additions & 23 deletions plugins/codex/scripts/lib/state.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const DELIVERY_MODES = new Set(["foreground", "manual", "managed"]);
const SEMANTIC_STATUSES = new Set(["accepted", "rejected", "unverified"]);
const SEMANTIC_FAILURE_KINDS = new Set(["intent_override", "scope_rewrite", "wrong_approach", "style_mismatch", "oversized"]);
const ACCEPTANCE_SOURCES = new Set(["collector", "main-loop", "stats"]);
const JOB_REQUIRED_FIELDS = ["claudeSessionId", "delivery", "deliveryCollectedAt", "artifactsPruned",
"acceptanceSource", "acceptanceRecordedAt", "fileChangeCount", "semanticStatus",
"semanticFailureKind", "semanticFailureMessage", "serviceTier", "resolvedModel", "resolvedEffort",
"timeoutMs", "companionVersion"];
const JOB_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
const WORKSPACE_SLUG_PATTERN = /^.+-[a-f0-9]{16}$/;
const GIT_CACHE_TTL_MS = 3000;
Expand Down Expand Up @@ -503,17 +507,41 @@ function assertJobRecord(record) {
if (record.companionVersion != null && (typeof record.companionVersion !== "string" || !record.companionVersion.trim())) {
throw new TypeError("Job companion version is invalid.");
}
const requiredFields = ["claudeSessionId", "delivery", "deliveryCollectedAt", "artifactsPruned",
"acceptanceSource", "acceptanceRecordedAt", "fileChangeCount", "semanticStatus",
"semanticFailureKind", "semanticFailureMessage", "serviceTier", "resolvedModel", "resolvedEffort",
"timeoutMs", "companionVersion"];
if (record.schemaVersion !== JOB_SCHEMA_VERSION || record.engine !== "codex"
|| Object.hasOwn(record, "appliedServiceTier") || Object.hasOwn(record, "sessionId")
|| requiredFields.some((field) => !Object.hasOwn(record, field))) {
|| JOB_REQUIRED_FIELDS.some((field) => !Object.hasOwn(record, field))) {
throw new TypeError("Job record schema is invalid.");
}
}

export function migrateJobRecord(raw) {
if (!raw || typeof raw !== "object" || Array.isArray(raw) || raw.engine !== "codex"
|| (raw.schemaVersion != null && raw.schemaVersion !== JOB_SCHEMA_VERSION)) return raw;
if (raw.schemaVersion === JOB_SCHEMA_VERSION && !Object.hasOwn(raw, "sessionId")
&& !Object.hasOwn(raw, "appliedServiceTier")
&& JOB_REQUIRED_FIELDS.every((field) => Object.hasOwn(raw, field))) return raw;
const { appliedServiceTier, sessionId, ...record } = raw;
return {
...record,
claudeSessionId: raw.claudeSessionId ?? sessionId ?? null,
delivery: raw.delivery ?? (raw.background ? "manual" : "foreground"),
deliveryCollectedAt: raw.deliveryCollectedAt ?? null,
acceptanceSource: raw.acceptanceSource ?? null,
acceptanceRecordedAt: raw.acceptanceRecordedAt ?? null,
fileChangeCount: raw.fileChangeCount ?? null,
semanticStatus: raw.semanticStatus ?? "unverified",
semanticFailureKind: raw.semanticFailureKind ?? null,
semanticFailureMessage: raw.semanticFailureMessage ?? null,
serviceTier: raw.serviceTier ?? raw.request?.serviceTier ?? null,
resolvedModel: raw.resolvedModel ?? null,
resolvedEffort: raw.resolvedEffort ?? null,
timeoutMs: raw.timeoutMs ?? null,
companionVersion: raw.companionVersion ?? null,
schemaVersion: raw.schemaVersion ?? JOB_SCHEMA_VERSION,
artifactsPruned: raw.artifactsPruned ?? false
};
}

function ownerSessionId(record) {
const value = record?.claudeSessionId ?? null;
return typeof value === "string" && value.trim() ? value.trim() : null;
Expand Down Expand Up @@ -774,7 +802,7 @@ export function createJobRecord(fields) {
export function writeJobRecordFile(file, record) {
assertJobRecord(record);
withRecordLock(file, () => {
const existing = readJobRecordFile(file);
const existing = readJobRecordFile(file, true);
if (existing && TERMINAL_STATUSES.has(existing.status)) {
return;
}
Expand All @@ -783,7 +811,7 @@ export function writeJobRecordFile(file, record) {
return file;
}

export function readJobRecordFile(file) {
export function readJobRecordFile(file, locked = false) {
let text;
try {
text = fs.readFileSync(file, "utf8");
Expand All @@ -800,20 +828,27 @@ export function readJobRecordFile(file) {
quarantineCorruptRecord(file);
return null;
}
let migrated;
try {
assertJobRecord(parsed);
return parsed;
migrated = migrateJobRecord(parsed);
assertJobRecord(migrated);
} catch {
return null;
}
if (migrated === parsed) return parsed;
if (locked) {
atomicWriteFile(file, `${JSON.stringify(migrated, null, 2)}\n`);
return migrated;
}
return withRecordLock(file, () => readJobRecordFile(file, true));
}

export function updateJobRecordFile(file, patch) {
if (!patch || typeof patch !== "object" || Array.isArray(patch)) {
throw new TypeError("Job record patch must be an object.");
}
return withRecordLock(file, () => {
const existing = readJobRecordFile(file);
const existing = readJobRecordFile(file, true);
if (!existing) {
throw new Error(`No job record found at ${file}.`);
}
Expand All @@ -832,7 +867,7 @@ export function updateJobRecordFileWithCurrent(file, updater, options = {}) {
}
const allowTerminal = options.allowTerminal === true;
return withRecordLock(file, () => {
const existing = readJobRecordFile(file);
const existing = readJobRecordFile(file, true);
if (!existing) {
throw new Error(`No job record found at ${file}.`);
}
Expand All @@ -857,7 +892,7 @@ export function finishJobRecordFile(file, patch) {
throw new TypeError("A terminal job status is required.");
}
return withRecordLock(file, () => {
const existing = readJobRecordFile(file);
const existing = readJobRecordFile(file, true);
if (!existing) {
throw new Error(`No job record found at ${file}.`);
}
Expand All @@ -875,7 +910,7 @@ export function markJobCollectedFile(file, collectedAt = nowIso()) {
throw new TypeError("A valid collection timestamp is required.");
}
return withRecordLock(file, () => {
const existing = readJobRecordFile(file);
const existing = readJobRecordFile(file, true);
if (!existing) {
throw new Error(`No job record found at ${file}.`);
}
Expand Down Expand Up @@ -1012,18 +1047,15 @@ function expectedManagedArtifacts(workspaceDir, id) {
];
}

function readManagedJobEntry(workspace, workspaceDir, file, id) {
const recordDescriptor = regularFileDescriptor(file);
function readManagedJobEntry(workspace, workspaceDir, file, id, locked = false) {
let recordDescriptor = regularFileDescriptor(file);
if (recordDescriptor.kind !== "regular" || recordDescriptor.size > JOB_RECORD_SCAN_MAX_BYTES) {
return null;
}
let record;
try {
record = JSON.parse(fs.readFileSync(file, "utf8"));
assertJobRecord(record);
} catch {
return null;
}
const record = readJobRecordFile(file, locked);
if (!record) return null;
recordDescriptor = regularFileDescriptor(file);
if (recordDescriptor.kind !== "regular" || recordDescriptor.size > JOB_RECORD_SCAN_MAX_BYTES) return null;
if (record.schemaVersion !== JOB_SCHEMA_VERSION || record.engine !== "codex" || record.id !== id) {
return null;
}
Expand Down Expand Up @@ -1241,7 +1273,7 @@ function resumeProtectionChanged(entry, current, protection) {
function deleteManagedJobEntry(entry, tier, protection) {
return withRecordLock(entry.file, () => {
const workspaceDir = path.dirname(path.dirname(entry.file));
const refreshed = readManagedJobEntry(entry.workspace, workspaceDir, entry.file, entry.id);
const refreshed = readManagedJobEntry(entry.workspace, workspaceDir, entry.file, entry.id, true);
if (!refreshed || !TERMINAL_STATUSES.has(refreshed.record.status)
|| (refreshed.record.background && !refreshed.record.collectedAt)) {
return null;
Expand Down
2 changes: 1 addition & 1 deletion plugins/cursor/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "cursor",
"displayName": "Cursor Companion",
"version": "0.0.66",
"version": "0.0.67",
"description": "Local Cursor Agent CLI delegation: multi family consults, Composer implementation in owned worktrees, review, resumable history, background jobs, and setup health checks.",
"author": {
"name": "Harry Yep"
Expand Down
2 changes: 1 addition & 1 deletion plugins/fusion/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "fusion",
"displayName": "Fusion Orchestrator",
"version": "0.0.66",
"version": "0.0.67",
"description": "Multi-model orchestration: tier agents, routing rules, blind panel, ultra fleet, model config, and drift doctor.",
"author": {
"name": "Harry Yep"
Expand Down
1 change: 1 addition & 0 deletions plugins/fusion/rules-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"b3649bf8c8fb1f3ede0ccfc7fb727a8adf1e13628a18f4a84d08efbee75bd934",
"b41dfd4862a69bb58ee79761b8f7055c26433b337156d001665176b3c77fb948",
"b5283c0356c9004385157f3360ca81a3601253e370216b390049d069a29a2250",
"bc88b749a510ca4683f9f7df05ff4b28d75966d5daa00c7e9f46307fbdd1faa3",
"c29808811e61113fd7861f5e973354c18d59341645e3fb5217fa89a81c5cd3da",
"c435cc1486eeff00f5b3a1c76017d46d8f05d30ebd248435d575a1461d7da5d4",
"c5cb1e9d17cd2771e0f320e760a249188d69658ef3a63b31556000a64fed88e1",
Expand Down
Loading
Loading