Skip to content

Releases: lahkiri/xeo-forge

Xeo Forge v1.25.2 — The Clean First Minute

Choose a tag to compare

@lahkiri lahkiri released this 02 Sep 10:01
de4d2f4

v1.25.2 — The Clean First Minute

Summary

v1.25.2 fixes what a stock Windows machine actually hits in the first ten
minutes. Every defect in this release was found by driving the full local
verification flow — clone → install → test suite → desktop:dev — on a
clean checkout, and every fix is verified at that same layer: the same
typecheck, the same suite, a fresh production build, and live HTTP checks
against the rebuilt standalone.

None of these are architecture changes. They are the difference between
"a Windows user with a space in their folder name never reaches the
product" and "the first minute just works."

The defect: desktop:dev was unbootable on any path containing a space

scripts/prepare-desktop.mjs spawned electron-rebuild.cmd through
spawnSync(..., { shell: true }) on Windows WITHOUT quoting the command
path. shell: true joins the command and args into one cmd.exe line, and
cmd.exe splits an unquoted program path at the first space — so a checkout
at C:\My Projects\xeo-forge failed with:

'C:\My Projects\xeo-forge' is not recognized as an internal or external command

…before Electron ever launched, and the message pointed at the FOLDER, not
at the tool, so nothing about it suggested a quoting bug. The fix quotes the
executable on win32. node-gyp additionally warns about spaces in the build
path on its own; that limitation is real and now documented instead of
discovered.

The failure output also stops being a bare exit code: when the rebuild
fails, the error now states the actual prerequisites (Windows: Visual
Studio Build Tools with the "Desktop development with C++" workload) and
the XEO_SKIP_NATIVE_REBUILD=1 escape hatch for UI-only local runs, with
its honest constraint — the standalone server then runs under the same Node
ABI it was built for.

The defect: a missing Go toolchain killed dev prepare

The Go runtime broker is optional at runtime: the Electron shell boots
without it, warns, and disables broker-backed local process supervision.
But prepare-desktop.mjs still hard-failed the whole prepare step when Go
was absent, for every developer, on every platform — even though only the
packaged installers actually need the broker binaries.

Go is now probed once up front. If it is missing, the broker builds are
skipped with a visible warning and prepare completes; the CI release
runners still have Go and still produce both platform binaries, so the
packaged app is unchanged. The principle the codebase already states —
optional runtime components must not block the dev path — now holds in
the prepare step too.

The defect: the intent router asked for clarification about plain work

classifyWorkIntent("Build a small script that says hello and verify it")
returned clarification_needed. The reason: script was not in
TARGET_PATTERNS, so a completely unambiguous software request had no
"target" in the router's eyes and Work stalled the user behind a visible
choice for no reason.

The pattern lists now cover the common software-work vocabulary for all
five advertised languages (en/ar/zh/ru/fr):

  • Nouns (targets): script, feature, bug, test(s), suite, function,
    module, package, library, endpoint, route, service, config,
    dependency/dependencies — plus the same field in Arabic, Chinese, Russian
    and French.
  • Verbs (direct actions): add, update, write, refactor, delete, remove,
    install, generate, scaffold, set up/setup — safe only in combination
    with a target hit, which is already the classifier's rule: a bare
    "add some notes" with no target stays conversational.

While pinning this, a dead French pattern surfaced: \b collapses before
an accented capital (É is not \w), so Écris un script… at sentence
start never matched \bécrit — the exact dead-pattern class the file's
own CJK comment warns about, sitting in the French list. The affected
alternatives are unanchored now, with a comment stating why.

All of it is pinned by 8 new regression tests in test/intent.test.ts
(en ×5, ar, fr, plus one "action language with no target still asks for
clarification" guard).

The defect: /tasks returned 404

The task list UI lives at /work; /tasks predates it and 404'd. A
temporary redirect /tasks → /work now keeps the natural URL working
for users and old bookmarks. /tasks/:id keeps its real page.

Verified live on the rebuilt standalone: /tasks307 /work,
/tasks/:id still serves its page, /chat unaffected.

Also in this release

  • lib/mcp/client.ts default-imports package.json instead of
    named-importing version, clearing the Webpack deprecation warning
    ("should not import the named export … only default export is available
    soon"). MCP_CLIENT_INFO is unchanged at runtime.
  • README restructured for the first minute: a five-command quick start now
    sits at the top above the fold, and a new Desktop dev prerequisites
    section states what desktop:dev actually needs on Windows and Linux —
    the MSVC C++ workload, Go, and a space-free checkout path — plus what
    happens when each is missing.

Verified

  • tsc --noEmit clean.
  • vitest run: 968/968 pass (57 files), including the 8 new intent
    regressions.
  • next build clean; the mcp/client version warning is gone from the
    build output.
  • Live check on the rebuilt standalone: /tasks307 /work;
    /tasks/nonexistent-id → 404 (its real page contract);
    /chat → 200.
  • desktop:dev prepare step re-run locally with the quoting fix: the
    electron-rebuild invocation now reaches the toolchain check instead of
    dying at cmd.exe argument parsing.

Xeo Forge v1.25.1 — Local-First Agent Workbench

Choose a tag to compare

@github-actions github-actions released this 30 Aug 19:27
109a3cc

v1.25.1 — The Visible Switch

Summary

v1.25.0 shipped the in-session model switcher in the Work governance rail,
and it never worked: every real session rendered "No model selected for
this task" while the API it reads was returning correct data the whole
time. This release fixes that defect, pins the regression class in CI at
the display layer it failed at, and re-takes the desktop evidence frame
that had documented the empty state honestly.

The defect: a consumer reading a POST-only wrapper

WorkGovernanceRail fetched GET /api/providers and read body?.catalog
from the response. But the GET returns the ProviderCatalog DIRECTLY —
the wrapped { provider, catalog } shape belongs to POST /api/providers
only. The adoption gate body?.catalog was therefore always undefined,
setCatalog never ran, and the switcher rendered permanently empty. The
task row stored the right ids; the names just never resolved.

The part that let it ship is worth stating plainly: every verification
looked at the API's response — correct — and none looked at the value
that actually reaches the rail's display. The gap was in the wiring
between the two, and no test covered the wiring.

The fix

The rail's catalog plumbing is extracted into app/work/rail-catalog.ts:

  • adoptProviderCatalog(body) accepts the bare catalog (the actual GET
    contract), tolerates the wrapped POST shape, and refuses anything that
    is not actually a catalog — a producer shape change now degrades to the
    rail's honest empty state instead of a silent permanent blank.
  • resolveCurrentModel(catalog, modelId) derives the exact
    {modelName · providerName} label the rail renders, from the same
    catalog Settings uses, so the rail can never disagree with it.

The regression class is pinned, not just the bug

test/rail-catalog-adoption.test.ts pins three layers in CI:

  • C1 — the GET payload (bare catalog) is adopted; the wrapped shape stays
    tolerated; non-catalog payloads are refused.
  • C2 — the displayed label pair resolves from the adopted catalog, and an
    unknown model id yields the honest "no model selected" state.
  • C3 — source contracts: the rail consumes the API THROUGH the adoption
    helper (the original miss cannot silently return), GET /api/providers
    keeps returning the bare catalog, and the live-UI harness keeps its
    hard-fail assertions.

And because a unit test alone would repeat the original monitoring gap,
scripts/recapture-03-work.mjs boots the REAL Electron desktop shell
against a scripted OpenAI-compatible provider, drives a task to the
decision gate, and asserts the selected model name in the rail's
RENDERED text (aside.w-rail innerText) plus the switcher's actual
selected value. Hard-fail by design: if the value does not reach the
display, it errors out instead of capturing garbage.

Verified

  • test/rail-catalog-adoption.test.ts: 9/9 assertions pass; tsc --noEmit
    clean; production next build clean (full vitest suite + lint are CI's
    canonical gate).
  • Live display-layer proof: the harness asserted Kimi K3 (scripted) · Scripted Provider in the rendered rail text with the switcher's selected
    value matching the task row, zero page errors — and re-captured desktop
    evidence 03 (03-work-decision-gate-model-rail.png) showing the decision
    gate with the MODEL section actually filled.

Xeo Forge v1.25.0 — Local-First Agent Workbench

Choose a tag to compare

@github-actions github-actions released this 29 Aug 13:32

Xeo Forge v1.25.0 — The First-Class Desktop

The desktop build stops being a second-class citizen. Seven phases, eighteen items, one governing law carried from v1.24: verify in the actual desktop runtime → fix → prove live — nothing was declared done because it "looked logical", and every claim below carries its evidence (behavior tests RED-then-GREEN plus six screenshots captured from the real Electron shell, not the dev server).

The operator was being stranded — follow-up and decisions (Phase 3.1)

Traced "the follow-up box only appears after a failure" to three distinct defects, each proven RED by test before the fix: claimTaskForFollowUp refused cancelled tasks (D1); resolveTaskDecision hard-rejected late decisions, leaving the operator with no decision gate, a 409 from the composer, and a 409 from the decision route (D2); and the client ignored the terminal task_status event while waiting for a done that never comes on those paths (D3). All three are fixed: follow-up and decision paths never strand the operator on any terminal state, late decisions are audited as decided_late, and the messages API states honestly what happened. test/work-followup-contract.test.ts — 11 assertions, 7 of them RED before the fix. Commit abe7db1.

Providers stop being a dead end — editing, adding, removing, switching (Phases 2 + 3.2)

The discovery: the provider Edit API (PATCH provider/model, DELETE model, manual model add) always existed — only the UI hid it. The UI now exposes all of it: provider editing with a write-only API-key field (the UI shows an api_key_set signal, never the stored key), manual "add model" by id, and per-model delete inside a 60-model catalog. And the operator no longer leaves a live session to change its brain: an in-session model switcher lives in the governance rail (locked while a run is executing), wired through POST /api/tasks/[id]/model behind a updateTaskModel gate, emitting a model_switch audit event with old model → new model and the timestamp. Running sessions on the same provider are untouched by key edits — the key is read per provider call, never baked into a run. test/model-switch.test.ts — 10 assertions. Commit 05034da.

Sessions get real names (Phases 1.2 + 3.3)

Session titles are now generated from the first real exchange — a deterministic summary, not the raw opener — with bidi-safe word-boundary truncation (no more slice() cutting Arabic/RTL words mid-glyph), and temporal discrimination (today / yesterday / date) instead of a wall of identical rows. Greeting-only openers ("اهلا" fifteen times) stay untitled until the first assistant answer fills a real title; legacy rows fall back through the same truncation. Backed by a tasks.title column migration. Chat and Work both inherit the fix (item 3.3 verified, not assumed). test/session-titles.test.ts — 10 assertions. Commit 70211d5.

Browser Bridge pairing — from four manual steps to one approval (Phase 6.2)

Diagnosis first: "Not connected" with the extension loaded was a real product bug, not user error — the bridge behaved silently when no token was present, and a genuine reconnect-churn bug (the close event of an intentionally-closed socket tore down the new healthy connection every ~2s) made stable pairing impossible. The pairing flow is redesigned: the desktop shows a pairing request and the user approves it in the app — no token copying at all; approval persists and the browser reconnects automatically afterwards; denial closes honestly; the manual token path is demoted to an Advanced disclosure. scripts/smoke-browser-bridge.mjs — 13 live checks. Commit e83c928.

Preview stops lying; external browsing stays consent-first (Phase 6.1)

"Audit the code" failing with no message was diagnosed first and separately, as demanded: it is a task-level provider failure, not a preview defect — but the investigation exposed that the Preview tab had been hiding failures entirely. The Preview tab now shows the classified failure reason taken from the run's own error event. External browsing did not get opened up: the new domain allowlist editor lives in Settings → Runtime with external access OFF by default, explicit per-domain approval chips, and exactly one governance system — the agent layer reads the live bridge policy; no parallel policy path exists. Commit 21786c3.

Sandbox and Subagents get their Settings sections (Phase 5)

Settings grows section 07 Sandbox (the three isolation tiers described verbatim as the executor enforces them — standard honestly labeled "no OS-level isolation", strict as hardened process, docker as real container isolation — with a live Docker probe and honest, actionable guidance when Docker is absent: nothing downloads silently) and 08 Subagents (the four delegation guarantees stated verbatim plus the write-delegation boundary). A new standing rule lands in AGENTS.md §17: any future core governance feature ships with a visible Settings section from day one — never only buried in a task-start form. test/settings-governance-sections.test.ts. Commit 33c2ac7.

The window is ours — frameless shell with a custom titlebar (Phase 1.1)

frame: false plus a custom DesktopTitleBar built from the same design tokens as the rest of the app: brand line, drag region, minimize/maximize/close through IPC only, maximize-state published to the renderer, and a clean null on web so the component cannot leak into the browser build. Mounted in AppShell with explicit height budgets so no surface clips. Works on Windows and Linux — the evidence screenshots come from the Linux shell under Xvfb, and the Windows build ships from the same component. test/desktop-titlebar.test.ts — 6 assertions. Commit c8e18a3.

Design tokens: audited, not refactored for show (Phase 1.3)

The audit found something better than divergence to fix: app/globals.css is the only stylesheet in the repo — there was no desktop/web token drift to unify, because the desktop shell renders the same CSS. The real delta was the OS native frame, which no token could reach — and that is exactly what Phase 1.1 replaced with the tokened titlebar. Recorded honestly in the audit document instead of manufacturing a refactor.

Desktop-runtime evidence — and a real fix it surfaced

Proving things "on the desktop specifically" required a desktop harness, and building it caught a real defect: desktop:dev was unbootable — the dev-server path pointed at the app-router directory instead of the .next/standalone output the Electron shell loads. Fixed, plus desktop:prepare packaging. The capture harness (scripts/desktop-live-capture.mjs) boots the real Electron shell under Xvfb, seeds state through the live API, and captures six screenshots to download/desktop-evidence/: 01 titlebar home, 02 sidebar session titles, 03 work surface (failure handling + composer + governance rail + MODEL section), 04 Sandbox tiers, 05 Runtime pairing, 06 Providers. Commit 72e9fef.

What is deliberately NOT in this release

  • Write-capable subagent delegation — no code, by order and by law. The design the README demanded now exists as a draft: docs/subagent-write-concurrency-design.md — a per-task write ledger (leases + file generation counters + read-stamps), a refuse-never-merge conflict policy (stale writes fail closed with attribution; the agent re-reads and adapts; repeated conflicts lock a subagent out of the path; parent outranks subs), and file_mutation audit events making every write attributable (agent: parent | sub-N, generations, hashes). It ships as a document only — every line of code waits for the owner's explicit approval of the design. README gap #5 now points at the draft; the Settings → Subagents page boundary statement is unchanged.
  • Per-subagent follow-ups and per-subagent model overrides (gap #6): loop-level design, not rushed in.
  • Split/collapse layout controls for Chat and Work panes (gap #7, Phase 7, lowest priority): not built, disclosed.

Verification honesty

  • Full vitest suite: 934/934 green across 55 files (891 at v1.24.0 → 934; +43 assertions across the five new/expanded contract suites). tsc --noEmit clean; ESLint clean on every touched file. CI is the canonical gate.
  • Live desktop evidence: six screenshots from the real Electron shell under Xvfb, stored in download/desktop-evidence/, plus the smoke suites (smoke-browser-bridge.mjs 13 checks, smoke-desktop.mjs).
  • Every one of the nine commits was pushed immediately after its proof; the phase-by-phase evidence record with commit hashes lives in docs/audits/2026-08-29-desktop-parity.md.

Xeo Forge v1.24.0 — Local-First Agent Workbench

Choose a tag to compare

@github-actions github-actions released this 29 Aug 09:02

Xeo Forge v1.24.0 — The Reinforced Frame

The structure release: no new behavior was promised, so none was shipped — instead the three largest files in the codebase (1,917 + 1,476 + 1,091 lines) were decomposed into named, guarded, single-purpose modules, with the full test suite pinning behavior unchanged at every step. The governing law held throughout: test first → split → verify.

The three decompositions (all moves verbatim)

  • The agent loop (lib/agent/loop.ts 1,917 → 1,620): five concerns moved verbatim into lib/agent/run/protocol.ts (run protocol types + event contracts), model-client.ts (provider call/retry plumbing), language.ts (run-message language), memory.ts (memory candidates incl. persistMemoryCandidates), tool-bridge.ts (tool dispatch bridging). Contract tests re-pinned double-sidedly (definition site + call sites, definitionSites.length === 1) so duplication cannot silently return.
  • The DB query package (lib/db/queries.ts 1,476 → a 28-line pure re-export facade): the repo's ONLY application-table writer now lives in 9 domain modules (users, tasks, events, admin, credits, uploads, context, profiles, providers) plus a package-private shared.ts. All 53 import sites stay untouched — @/lib/db/queries remains the only sanctioned path — and the one cross-domain dependency (tasks → profiles) is wired explicitly. test/db-queries-structure.test.ts (7 assertions) freezes the export surface, forbids deep imports and facade SQL, and pins exactly-one definition site per function.
  • The work surface (app/work/WorkClient.tsx 1,091 → 216): 7 hooks + 7 components moved verbatim (useWorkRunState, useWorkspaceDiff, useGitStatus, useWorkDerived, useWorkActions, useDecisionCountdown, usePendingMemory; WorkRunPane, WorkGovernanceRail, WorkCenterHeader, WorkSecondaryTabs, WorkDiffTab, WorkRunList, WorkComposer). The DiffSink identity is memoized so the SSE subscription happens once per task, never per render — the resubscribe regression class is designed out, not just re-tested.

Dead surface removed

app/work/WorkIntake.tsx (438 lines) is gone, per maintainer decision. A comprehensive grep proved zero production importers: the /work route redirects to /chat, and the live demo entry lives in UnifiedWorkspace (POST /api/demo/work/<id>?demo=1) — WorkIntake was a dead duplicate of that flow. Only its source-reading test pins were removed with it (3 assertions); the four live demo-contract blocks stay, and the historical release records that mention it stay untouched. Icon hygiene was checked (all 4 imported icons remain used elsewhere). Evidence: Phase 1.5 of the audit document below.

New behavior evidence (the suite that makes future change safe)

  • test/run-agent-behavior.test.ts — 6 end-to-end scenarios through a REAL database and REAL event emitter against a mock OpenAI-compatible SSE provider: chat finalize-on-first-text, native reasoning events, inline <think> extraction, build-mode evidence gate (a real tool call, not bookkeeping), honest 401 failure, and the <action> fallback path. Build tasks follow the real authorization path end to end (createTask planning → planned → approveTaskPlan → run with the row's approved plan).
  • test/work-ingest.test.ts (7) + redirected double-sided contract pins for chat-hang H3, demo-replay pacer, cancellation, and ThinkingBlock.
  • Dead code was discovered and reported honestly during the README media refresh — disclosed first, deleted only on the maintainer's explicit call.

Docs that show, not tell

  • README now leads with a real 17-second governed-run GIF (plan → approve → build → verify → memory, captured from the real app against a scripted provider) plus fresh dark-theme captures; the v1.15-era hero shot — which literally showed "Run failed — no global model is configured" with three failed tasks — is gone. The demo provider used for the capture ships in scripts/ so the recording is reproducible.
  • AGENTS.md layout reflects the new architecture: the db/queries package + facade rule (enforced by the guard test), the lib/agent/run/ primitives, and the app/work decomposition.
  • docs/audits/2026-08-29-v124-structural-rework.md records the whole rework phase-by-phase with commit hashes, before→after numbers, and honest scope boundaries.

Verification honesty

  • Full vitest suite: 891/891 green across 50 files (866 pre-rework → 894 through the splits → 891 after removing the 3 dead-surface intake pins); tsc --noEmit clean; ESLint clean on every touched file. CI is the canonical gate.
  • Every phase was pushed immediately after its commit; the dead-surface deletion went through grep evidence → full suite → documentation in exactly the order the maintainer specified.

Known gaps (carried honestly)

  • Structure-only release: no behavior change is claimed anywhere, and the suite pins exactly that claim.
  • The v1.23 project map counted 93 functions above the complexity-15 threshold; line-count decomposition addressed the three largest files, and the complexity debt inside the remaining functions (including runAgent) was not systematically attacked.
  • The v1.23 known gaps carry forward unchanged: per-task permission overrides accepted but never supplied, GUI zone rules without a runtime consumer, the docker tier covering code_execute only, and read-only subagents.

Full rework evidence: docs/audits/2026-08-29-v124-structural-rework.md

Xeo Forge v1.23.1 — Local-First Agent Workbench

Choose a tag to compare

@github-actions github-actions released this 28 Aug 03:26
9c90570

Xeo Forge v1.23.1 continues the Local-First Agent Workbench. Upgrade from the minimum supported OTA bootstrap version and always use the latest release.\n\nGitHub's generated changelog follows.

What's Changed

  • Release v1.23.1 — The Answer Keeper II (chat answer-vanish fix) by @lahkiri in #29

Full Changelog: v1.23.0...v1.23.1

Xeo Forge v1.23.0 — Local-First Agent Workbench

Choose a tag to compare

@github-actions github-actions released this 28 Aug 01:14
2858d1f

Xeo Forge v1.23.0 — The Honest Machine

The version where every promise is probeable: thinking you can see, effort levels that mean something, subagents that inherit authority instead of escaping it, and a sandbox ladder that tells you the truth about isolation.

Phase 0 — the regressions, root-caused (not patched)

  • The endless-reply loop is dead. Root cause: the agent loop's no-tool-calls path had planning and build branches but no chat branch — every chat answer fell into build-mode "fake completion" detectors that demanded tool evidence chat can never produce (NO_WORK_PERFORMED_NUDGE), re-prompting the model forever while the surface streamed the repetition. Chat now finalizes on first text termination, verbatim. (Live-verified: loop source contract pinned in scripts/smoke_v123_phase0.mjs, 13/13.)
  • Thinking blocks exist and are honest. Two channels merged: native reasoning_content events AND inline <think>…</think> tags (DeepSeek-R1-style proxy gateways) — extracted on both the persisted path (server) and the live path (client). Thinking survives completion; a run with no streamable reasoning at a level above Minimal shows "this model did not stream separate thinking — nothing was hidden" instead of silence.
  • Markdown + bidi. Assistant answers render through the hardened lib/markdown.ts renderer (tables, nested lists, fenced code, links); message containers carry dir="auto" for mixed Arabic/English.

Chat, redefined

Chat is now exactly what the contract says: plain smart conversation — the ChatGPT/Claude shape. Its only tool is web_search (3-engine fallback chain, live-probed 3/3 including Arabic queries; fail-closed honest errors, never fabricated results). It cannot read files, run commands, or touch the workspace — the difference from Work is authority, not intelligence: same reasoning quality, same thinking levels.

Thinking-effort levels (eight, honestly classified)

Minimal · Low · Medium · High · Enchanted High · Extra · Max · Ultra — selectable per task in the chat composer and the Work setup, stored on the task row ("the row is the truth"). Each level maps to a native reasoning_effort parameter (live-probed: accepted by every working model on our reference proxy) plus, for the four top levels, simulated discipline passes (plan → self-check → alternatives → adversarial critique) injected as system directives. The UI marks hybrid levels (+sim), emits a thinking_level audit event per run, and shows the chosen level inside the thinking block.

Subagents — parallel delegation through the front door

delegate_research fans out 2–4 read-only research subagents (files/lists/web only — concurrent writes impossible by construction). Every subagent executes under the parent's exact authority through the identical authorizeToolCall gate; every step lands in the audit trail tagged sub-1…sub-N; failures are isolated and reported per-subagent. The delegation itself answers to the per-level subagent rules (denied at read-only, asked at assist, allowed from execute up).

Sandbox — three tiers, zero euphemism

  • Standard — labeled plainly: hardened process execution, no OS-level isolation.
  • Strict — 8 extra deny rules as DATA (network, transfer tools, process control, delegation) prepended to the run's rule set.
  • Docker — real isolation: every execution command in an ephemeral container (workspace bind-mount, 1 CPU / 512 MB / 128 pids, network off). Docker is probed per run, never assumed; unavailable → fail-closed refusal with a guided, consent-first install path (per-OS steps + official download + re-check). Nothing downloads silently; mid-flight failure never leaves a half state.

Chosen from the Work setup (field 06, with a live Docker status dot) — honest descriptions rendered verbatim at choice time.

Deep audit fixes (audited by adversary, fixed by evidence)

  • Hook audit citations were persisting null — the authority verdict (rule + index) now travels into every audit event.
  • The Python runner's cleanup command escaped the rules gate (del, Windows-only, path unchecked) — the interpreter runs through the same gated run(); cleanup is unconditional in-process.
  • Dead UI eliminated: the 698-line orphaned SettingsClient deleted; "Switch workspace" now opens the real chooser (or states "Managed"); the hardcoded "Ready" pill reflects actual setup state; the decorative account-menu glyph and the false "Gateway connected" label are gone.
  • WorkIntake authority bullets now change with the chosen level (read-only strikes out writes; assist says per-action approval; autonomous names the push/publish exception).

Live model intelligence (provider probes, 2026-08-28)

20 text models probed across 4 capabilities (native tools, reasoning channel, reasoning_effort, streaming): 8 fully functional on the reference proxy right now (kimi-k3, gpt-oss-20b, glm-4.6v-flash, opus-5-thinking, opus-4-8-thinking, deepseek-v4-pro, glm-4.5-flash, mimo-V2.5); the rest fail provider-side (auth_unavailable 503 / 504 / malformed upstream) — documented honestly, with ~88K tokens of measured probe usage.

Verification honesty

  • scripts/smoke_v123_phase0.mjs — 13/13 (Node 24 type-stripping) for the Phase-0 contracts; vitest twins in test/ run in CI (the sandbox cannot build better-sqlite3 locally; CI is the canonical gate).
  • web_search: 3/3 live queries across engines (Arabic + English).
  • Sandbox module: live probe (modes, strict rules, docker detection, wrap command) on this machine.
  • Provider probes: raw JSON + consolidated tables in research/.

Known gaps (disclosed, not hidden)

  1. Per-task permission overrides: the loop accepts them, no path supplies them yet (raise the level instead, for now).
  2. GUI zone rules: enforced logic, contract-tested, no runtime consumer yet.
  3. Docker tier covers code_execute; interactive terminal still runs on-host (visible warning when docker tier selected).
  4. Subagents are read-only; write-capable delegation waits for a proven concurrent-write design.
  5. Multi-platform presence and skill self-improvement (Hermes features): roadmap, not v1.23.

Full competitive evidence: docs/competitive/hermes-analysis.md · docs/competitive/openhands-analysis.md

Xeo Forge v1.22.0 — Local-First Agent Workbench

Choose a tag to compare

@github-actions github-actions released this 27 Aug 21:43
3623d66

v1.22.0 — One Language

Summary

Two user-reported defects drove this release, and both turned out to be
honesty problems wearing UI clothes.

First: the chat surface could hang forever in "Thinking" with a locked
composer — the user watched a stuck background process that never finished
and never explained itself. Root cause: the client treated the SSE stream
as the only source of status truth. Any missed done event (a dead
EventSource, a provider crash without a terminal event, an Electron reload
orphaning the stream) left the status "running" permanently — and the
cancelled status was not even in the client's terminal check, so the
composer stayed locked after a stop. The DB row knew the run was over; the
UI never asked.

Second: the app read as two different products. The workspace and settings
pages spoke the black minimal design language, while leftover unicode
glyphs — ⌘K ×
••• — read as placeholders from another era, and
the live work session still carried a handful of them. Every glyph is now a
stroke icon from one inline library, sized and colored by the same tokens.

The icon system

components/icons.tsx grew from 7 to 29 icons (Lucide geometry, MIT, zero
dependencies — inline SVG with currentColor strokes so themes stay
correct for free). The set now covers direction (arrows, chevrons, panel
collapse/expand), objects (search, message, folder, settings gear, user,
plug, command), actions (play, plus, stop, x, check), states (circle,
help-circle), and appearance (sun, moon, monitor). Every consumer was
rewired: the workspace sidebar and topbar, session rows, capabilities nav,
settings back-link and section arrows, MCP server rows and approval mark,
provider rows and form dismiss buttons, theme segmented control, dialog
close, command palette, app shell, diff rename arrows, thinking block,
tool ledger disclosure, run-failure mark, login brand marks, and the admin
back link. The new glyph-eradication smoke scan proves the vocabulary is
gone from every .tsx — with two documented exemptions: the icon library's
own doc comments, and setMod('⌘'), which is a keyboard key label
(rendered as "Ctrl" on Windows), not an icon.

The run that never lies stuck

The fix is a contract change: the stream is an input; the task row is
the truth.

  • isTerminalTaskStatus (in runtime-state.ts) now encodes the server's
    full terminal vocabulary — completed / failed / cancelled /
    planned — so a stop or a promote can no longer strand the UI in a live
    state.
  • While a turn is live, both the chat and work surfaces re-read the task
    row every 4 seconds and adopt the server's terminal status. The missed-
    done family of hangs self-heals within one poll, regardless of cause.
  • If the server finished but nothing streamed to this client, the persisted
    answer is already in the database — the client refreshes and renders it
    verbatim rather than showing an empty completion.
  • A Stop control is rendered for the whole live turn (not only before
    the first token), wired to the real cancel route; the reconciliation poll
    adopts the resulting cancelled status even if the stream never
    delivers it.
  • If the live connection drops, the surface says so honestly ("Live
    connection interrupted — still reconciling with the saved task state")
    instead of silently pretending to think.
  • The chat done-handler now reads the streamed text through a ref mirror,
    fixing a stale-closure dedupe that could double-append a summary.

Verification

Local environment cannot install node_modules (better-sqlite3 native
build — same limitation as v1.21), so verification is the same honest
three-part shape: TSX syntax validated via esbuild across all 22 touched
files; a pure-module smoke (scripts/smoke_ui_unify.mjs) passing 12/12
under Node 24 type-stripping — terminal-vocabulary behavior, both
reconciliation contracts, glyph eradication, icon-library integrity; and
test/chat-hang-reconciliation.test.ts (+13 assertions in CI) pinning the
regression permanently: the terminal vocabulary, both surfaces'
reconciliation behavior, the Stop hatch, and the honest stream-loss state.

Xeo Forge v1.21.0 — Local-First Agent Workbench

Choose a tag to compare

@lahkiri lahkiri released this 27 Aug 17:59

v1.21.0 — The Chosen Boundary

Summary

v1.20.0 shipped autonomy levels as rule data with 22 contract tests — and
disclosed, on discovery, that they were not actually reachable: the API
never accepted one and startAgentRun never forwarded one, so every run
executed at the default. This release closes that gap completely, plus a
third one found while wiring it: the loop built its tool context before
computing the rule set, so even an explicit level could not have reached
the tools. Authority is now chosen, stored, enforced, and displayed —
end-to-end, with each layer's test pinning it.

Chosen before the run exists

Work setup gained field 04 — Authority — backed by the real level set
(read_only / assist / execute / autonomous), and WorkIntake's
authority card carries the same picker with the chosen level's contract
line rendered live. The level is stored on the task row (idempotent ALTER
for existing databases) and validated at every boundary: unknown values
fail loudly naming the valid levels, because a typo that silently coerced
to a broader level is an escalation hidden in plain sight. Follow-up
messages, plan approvals, mode switches, and rejects all re-read the level
from the row — a later message can never smuggle in more authority than
setup granted.

Enforced where it runs

A new central gate (lib/agent/authority.ts, deliberately pure) evaluates
every world-touching call at the same chokepoint as the planning/chat
hard-lock: file writes → edit, command text → shell, http → network,
git ops → read/git_mutation, MCP tools → subagent, preview spawns →
shell. The universal denies remain re-appended LAST, so a
grant-everything override still cannot run rm -rf / or dd to a device.
Repo-local git work (add/checkout/revert) gained explicit allow
rules at execute — without them the silent default would have refused
routine staging, contradicting v1.20's own promise that routine work
proceeds.

ask fails closed

A mid-flight run cannot stop to hold an approval conversation — no
per-action approval queue exists yet. So an unresolved ask rule now
REFUSES the call with a citation of the deciding rule and how to grant it
(raise the level, or add an explicit per-resource override). Silently
proceeding would make ask read as allow in practice — the exact
authority escalation this layer exists to prevent. The interactive
approval queue is follow-up work, stated in the README's honest-boundaries
section rather than implied away.

Secrets, now true at dispatch

The rules always said secrets "ask at every level". At dispatch they now
block: file_read('.env') is refused at all four levels with an approval
citation, while .env.example stays readable. Before this release that
promise was enforced nowhere — the file was silently readable at every
level. Behavior change, on purpose, disclosed in the PR.

The panel shows the policy that runs

The Work live Authority panel now derives its rows from the stored level:
read_only locks writes and commands outright, assist gates them,
autonomous allows them while push/publish stays gated and force-push
stays denied. Each row keeps its hover "why", so "Why?" is answerable
from the panel itself, not a docs page.

Verification

CI green on the PR and again on master after the merge (Verify: install,
typecheck, full suite). +40 dispatch-time wiring contract tests pin
normalize/reject inputs, per-level matrices, secrets-at-every-level,
override-vs-universal-denies ordering, and scope honesty. The two pure
modules (permissions.ts, authority.ts) additionally smoke-verified
under Node 24 type-stripping: 31/31 assertions. The quickest live proof
an operator can run: start a Work task at read_only, ask it to write a
file, and watch the refusal cite its rule.

Known boundary

Interactive per-action approvals do not exist yet — unresolved asks
refuse, they do not pause. GUI zone governance (v1.21's other pillar,
from b6bf1b8e) composes unchanged: physical acts classify into zones at
design time, and irreversible can never evaluate to allow.

Xeo Forge v1.20.1 — Local-First Agent Workbench

Choose a tag to compare

@github-actions github-actions released this 26 Aug 10:06

v1.20.1 — The Honest Hotfix

Summary

This release exists because an audit ran real workloads against the
product instead of trusting its own tests. Every fix below was proven
against a live server before it shipped.

Your answer survives even when the run doesn't

The worst finding: a streamed chat reply lived only in the event log.
If the run failed or was cancelled, everything the user had already
read vanished from history — 6,280 characters of a real reply were the
proof. Now failRun and the cancellation path persist the accumulated
prose as the assistant message before the terminal status lands.

A greeting is not a work order

Chat still offered todo_update, git_op, and http_request — so the
model "worked" on a simple hello, mutating todos, until the progress
guard correctly killed the run as failed. Chat now offers read-only
inspection only (file_read, file_list, skill_view), and the chat
prompt orders zero tool calls for greetings. Live: greeting runs make
zero work-tool calls.

Cancel that actually cancels

A live test caught cancel reporting "no live loop found" while events
kept flowing for another 97 seconds — the registry was a per-bundle Map,
so the cancel route and the loop could hold different copies. The
registry is now anchored on globalThis: one instance per process,
verified with signalled: true and terminal state in under 5 seconds.

Provider errors speak human

Raw internals like 503 auth_unavailable (providers=...) are classified
into honest language with next steps — "The model provider rejected the
API key. Check the key in Settings → Providers, then retry." The full
technical text goes to server logs where it belongs.

Capability probes — proof, not promises

The audit demanded live evidence for every feature. These ship as
reusable scripts:

  • scripts/mcp-live-probe.ts — spawns a real MCP stdio server through
    the project's own client: handshake, listTools (9 tools with full
    schemas), tools/call — PASS. Server output arrives wrapped in the
    untrusted-data quarantine block, exactly as designed.
  • scripts/browser-live-probe.ts — starts the real browser bridge:
    loopback token auth, honest empty state, and navigate blocked by
    the read-only default until explicit permission — PASS.
  • scripts/tools-live-probe.tsskill_view reads a real imported
    skill file through its manifest with the path-escape guard intact;
    http_request returned HTTP 200 live; file_list works — PASS.

Verification

tsc --noEmit clean · lint clean · 818/818 tests · every hotfix
exercised against a running dev server with DB-level confirmation ·
CI green on the merge.

Xeo Forge v1.20.0 — Local-First Agent Workbench

Choose a tag to compare

@github-actions github-actions released this 26 Aug 05:49

v1.20.0 — The Governed Loop

Summary

The agent loop's biggest upgrade since the hardening release. Three pillars,
each one an answer to "what makes frontier coding agents strong?" — and each
one shaped by Xeo Forge's constraint: high autonomy under explicit human
authority
.

Pillar 1 — Progress replaced counters

The old stagnation guard asked "did you repeat yourself N times?". That
punishes a legitimate test-fix loop (same two tools, changing results) and
forgives a useless one (alternating two reads forever). The new progress model
asks "did the world change?":

  • New files read or changed → progress
  • Test outcome flipped (pass↔fail) → progress
  • A genuinely different error class → progress (a new failure is information)
  • Task state moved → progress

Zero-movement loops are nudged after a window and failed after grace — even
if every call fingerprint looks different. Information gain replaces fixed
read ceilings: 25 genuinely-new files stay un-nudged; re-reading known content
does not.

Pillar 2 — Authority as data

Permissions were scattered across if statements. Now they are ordered rules:

{ action: 'shell', resource: 'git push *', effect: 'ask' }
  • Every decision cites its rule — evidence bundles can answer why
  • Batch evaluation: a multi-file patch takes the strictest outcome
  • Four autonomy levels as real state: read_only · assist · execute · autonomous
    • execute is the default: routine work proceeds, anything leaving the machine asks
    • Publishing (npm publish, docker push, git push) asks even at maximum autonomy
    • Secrets ask at every level; outside-the-workspace asks at every level
    • Universal denies (rm -rf /, mkfs, metadata IP, force-push) survive any override — tested with a grant-everything configuration
  • Enforcement is real end-to-end: run args → effective rules → tool context → command executor, which names the denying rule in its error

Pillar 3 — Lifecycle hooks

Anything that must always happen must not depend on the model remembering it.

  • audit_pretool: every shell command recorded before execution with its permission citation — blocked attempts are evidence too
  • audit_posttool / tool_failure: outcomes appended at the single chokepoint both tool paths share
  • guardrail_verify: after successful executions in build mode, claimed files are checked against the workspace — "claimed but vanished" false completions get caught mid-run
  • completion_evidence: a deterministic touched-files bundle at finalize

Hook errors never break a run — they become evidence. Every firing lands in
the same seq-ordered stream as everything else: hooks inherit the audit trail.

Direction document

docs/loop-v2.md lays out the full v2 path with evidence tiers
([documented] / [observable] / [inference] / [bench]): explicit AgentState,
checkpoints, governed delegation with budgets and governance trees, best-of-N
with independent judges, dynamic verification contracts, and an execution DAG.
Nothing ships there until its prerequisite proves itself.

Verification

tsc --noEmit clean · lint clean · 805/805 tests (+48 across the three
pillars) · hook timeline labels confirmed in served bundles · permission
enforcement exercised through the executor.