<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>index</title>
    <link>https://indexable-inc.github.io/index/</link>
    <atom:link href="https://indexable-inc.github.io/index/feed.xml" rel="self" type="application/rss+xml" />
    <description>Agents made writing code cheap; waiting on review queues and upstreams is now the expensive part. index is one open monorepo where every change lands now and reaches everyone: packages, NixOS modules, VM images, patched toolchains, and the agent infrastructure that ties them together.</description>
    <language>en-us</language>
    <lastBuildDate>Tue, 21 Jul 2026 00:10:00 GMT</lastBuildDate>
    <item>
      <title>Anonymous usage telemetry, end to end: spool-hot-path client, ix-wrap, and the usage.ix.dev collector</title>
      <link>https://indexable-inc.github.io/index/usage-telemetry-walking-skeleton</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/usage-telemetry-walking-skeleton</guid>
      <pubDate>Tue, 21 Jul 2026 00:10:00 GMT</pubDate>
      <description>Anonymous usage telemetry, end to end: spool-hot-path client, ix-wrap, and the usage.ix.dev collector. ix tools can now count their own usage, opt-out-able and counts-only. The whole pipeline landed today and was proven end to end on a workstation: a wrapped tool ran, and its day-bucketed count arrived in ClickHouse through the real client, the real HTTP collector, and the real schema file. The hot path never touches a database. Measured first: a SQLite/WAL upsert costs 8.7us warm but 10ms p99 under 8 parallel writers. So the wrapper appends one JSON line to a spool file with a single O_APPEND write (1.8us, lock-free, flat under any parallelism), and a flock-singleton compactor folds the spool into SQLite (WAL) at ~/.local/state/ix/usage.db. Wrapped-tool overhead measured at ~5.9ms per invocation, and a count-only mode records then exec()s for tools in tight loops. Privacy is structural, not promised. Failing invocations keep argv and cwd in the local database for agents to query (ix-usage errors --json), but the upload payload builder reads only the counts and meta tables; tests assert the wire bytes never carry argv, cwd, or error text. Consent precedence is IX_USAGE then DO_NOT_TRACK then CI then ~/.config/ix/usage.toml then default-on with a one-time stderr notice; interactive first runs get a prompt that shows the literal payload instead of describing it. A &quot;no&quot; is permanent silence, and local recording continues either way so a later opt-in has history. The wrapper is transparent by test. ix-wrap mirrors exit codes (128+signal for signal deaths), leaves ctrl-C to the child, forwards SIGTERM/SIGHUP, and passes stdout through byte-identical; seven transparency tests plus a nix withUsage seam that rewraps any package&apos;s bin/* (walking skeleton: git-log-pretty as .#usage-demo). The collector is a ledger first. usage.ix.dev (leader nginx, loopback axum service) writes every accepted POST verbatim to usage.reports_raw as the anti-abuse audit trail, then upserts usage.counts (ReplacingMergeTree keyed install/pkg/version/day) so re-sent 7-day windows are idempotent: in the E2E run, three POSTs produced three ledger rows and exactly one counts row. Anonymous by design, so the defenses are caps (64KiB body, 512 rows, sane count ceilings) and retroactive exclusion by install id. Not live yet: the Cloudflare record and fleet deploy ride the next terraform apply and deploy run (CI infra was down today, both PRs were validated locally and force-merged per standing decree). Follow-ups tracked in the design issue: tree-wide default-on wrapping via a central policy map, stderr-tail capture, ix report for defects only humans or agents can see, and the adoption-by-version dashboard.</description>
    </item>
    <item>
      <title>ix-term: a tailnet web terminal on server-side libghostty-vt</title>
      <link>https://indexable-inc.github.io/index/ix-term-web-terminal</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/ix-term-web-terminal</guid>
      <pubDate>Mon, 20 Jul 2026 23:30:00 GMT</pubDate>
      <description>ix-term: a tailnet web terminal on server-side libghostty-vt. ix-term-server is a web terminal meant to live inside the tailnet (term.ix.dev): a Rust server owns one libghostty-vt terminal state per session as the single source of truth, spawns PTY login shells on the serving host, and streams dirty rows to a Svelte frontend over a websocket. Browsers are thin views — reconnecting, or opening the same session on a second machine, always shows exactly what the server knows. The UI is an Arc-style left tab bar of sessions (create, inline rename, close), a DOM-rendered grid, and one seat rule: the last keystroke wins the driver seat, the driver&apos;s viewport owns the PTY size, and everyone else sees the driver-sized grid CSS-scaled down. No resize fights. Sessions carry IX_TERM_SESSION_ID, and the server publishes each session&apos;s pts path at /run/ix-term/sessions/&lt;id&gt;/pts. That is the whole contract behind ixterm open: writes a private OSC (ESC ] 5522 ; open ; &lt;abs path&gt; BEL) straight to the session pts in one write(2). The server strips it from the byte stream before the VT engine sees it (ghostty&apos;s OSC enum is closed, so a scanner in front of vt_write is also the seam that keeps the engine swappable) and opens the HTML file as a split next to the terminal — a sandboxed iframe whose CSP forbids all network fetches. Bad paths render as an error inside the session; there is no backchannel to the writer. Operators get a NixOS module with typed options: Auth is the reverse proxy / tailnet boundary; per-request Tailscale WhoIs is the tracked TODO before the term.ix.dev host wiring lands in the ix repo. The wire format was benchmarked under a synthetic full-screen redraw storm (numbers in the PR) and stays abstract enough to swap the client for ghostty-web WASM later. Update (2026-07-20): ix-term development moved to the private ix repo, and the packages were removed from index (#3810). The source links above point at the last index commit that carried them.</description>
    </item>
    <item>
      <title>Kernel wakes reach Claude Code again: mcp-ex speaks the claude/channel contract</title>
      <link>https://indexable-inc.github.io/index/kernel-channel-notifications</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/kernel-channel-notifications</guid>
      <pubDate>Mon, 20 Jul 2026 19:45:00 GMT</pubDate>
      <description>Kernel wakes reach Claude Code again: mcp-ex speaks the claude/channel contract. Since the Elixir cutover, every server-initiated kernel notification (finished background jobs, PrWatch outcomes, subagent events) was sent as an MCP logging notification, notifications/message. Claude Code receives that method and drops it, so no wake has reached a session since the cutover, even though every session already runs with the channel flag armed. The retired Python kernel spoke the channels research-preview contract; the rewrite kept the Notifier but not the wire format. mcp-ex now declares the experimental claude/channel capability at initialize and pushes notifications/claude/channel events with a content body plus meta attributes (source, job/pr/agent, status). Claude Code injects each event into the running session, so a Jobs completion, a PR merge, or a subagent report wakes the agent instead of vanishing. Notifier.channel(content, meta) is the one delivery primitive; notify/2 stays as the raw JSON-RPC escape hatch. The capability declaration and the wire frame are verified against the patched server over raw JSON-RPC (a budget-expired job emits the channel frame); end-to-end injection into a Claude Code 2.1.215 session lands with the next kernel deploy and is verified there.</description>
    </item>
    <item>
      <title>Gmail from any kernel cell</title>
      <link>https://indexable-inc.github.io/index/gmail-elixir-kernel</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/gmail-elixir-kernel</guid>
      <pubDate>Mon, 20 Jul 2026 19:00:00 GMT</pubDate>
      <description>Gmail from any kernel cell. The Elixir kernel (ix-mcp-ex) had no Google surface after the Python cutover: sending mail from a cell meant hand-building the gcal CLI and curling the Gmail REST API with a minted token. Now Gmail is aliased into every cell: Gmail.send(&quot;dev@ix.dev&quot;, &quot;subject&quot;, &quot;body&quot;) sends as the signed-in user (with cc:/bcc:/html:/thread: options), Gmail.search(&quot;from:github newer_than:7d&quot;, limit: 5) returns typed summaries, Gmail.show(id) decodes one message down to its text body, and Gmail.status() reports the auth state -- credentials present, grant stored, scopes covered -- as data. The refresh token and client secret never cross into a cell; they stay inside the NIF, the same property the shell-out had. The seam is one thin unibind export: packages/google/gmail/ex re-exposes the existing google-gmail client (send, metadata-hydrated search, full-message reads) and google-auth grant store as the :gmail_ex NIF app -- async NIFs on unibind&apos;s shared tokio runtime, records for every value that crosses. Like the TUI binding, the app is not a mix dep: the release bakes its store path into IX_MCP_GMAIL_EX and a shared loader (IxMcp.NifApp, factored out of TuiLocal) puts it on the code path at first use. The signed-out auth boundary is proven offline by checks.*.google-gmail-ex-run, and the mcp-ex smoke test drives Gmail.status() through the shipped release.</description>
    </item>
    <item>
      <title>Daemon workers now die with their client, and zombie-owned builds stop reading as in flight</title>
      <link>https://indexable-inc.github.io/index/nix-daemon-dead-client-wedge</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/nix-daemon-dead-client-wedge</guid>
      <pubDate>Mon, 20 Jul 2026 01:30:00 GMT</pubDate>
      <description>Daemon workers now die with their client, and zombie-owned builds stop reading as in flight. On hydra this morning, nix store builds reported 33 builds &quot;in flight&quot; for 10.5 hours, owned by three daemon workers that existed only as zombies; new builds of the same derivations (strsim et al.) sat &quot;building&quot; for 55 minutes with flat logs, and only launchctl kickstart -k system/org.nixos.nix-daemon cleared the jam. Sibling of the CLOSE-WAIT downloader wedge (p22): that was the cache half-close, this is the client-death half. Two fork patches close it. Patch 0033 gives the goal loop a level-triggered interrupt wakeup: interrupts used to be a flag plus a one-shot SIGUSR1, so one landing between the last checkInterrupt() and the poll() syscall was lost, and a worker whose builders were silent (no max-silent-time, no timeout, no min-free -- our daemon config) slept forever, holding goals, builders, build users, and path locks of a client that no longer existed. A self-pipe in the waitForInput poll set now makes client death (via MonitorFdHup) and SIGTERM abort the worker deterministically. Upstream master has the same gap; its Waker pipe is not interrupt-wired. Patch 0034 makes the status directory stop trusting corpses: writers hold a lifetime flock on their entry (&quot;livenessLock&quot;: true), which the kernel releases on any death -- SIGKILL, crash, or surviving as a zombie -- so nix store builds prunes them on sight. The old kill(pid, 0) probe kept phantom entries alive precisely because it succeeds for zombies; it remains only as a zombie-aware fallback for entries written by older daemons. Repro receipts on a throwaway daemon: before, a SIGKILLed worker&apos;s entry stayed listed with its builder orphaned; after, the SIGKILL client-disconnect and zombie-writer cases are functional tests (tests/functional/build-status.sh). Rolls to hosts on the next pin bump and to workstations via home-manager switch (p34).</description>
    </item>
    <item>
      <title>Benchmark-org meta-analysis: who is hardest to game, and where Fable 5, GPT-5.6, Kimi K3, and Qwen stand</title>
      <link>https://indexable-inc.github.io/index/benchmark-meta-analysis</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/benchmark-meta-analysis</guid>
      <pubDate>Sun, 19 Jul 2026 23:30:00 GMT</pubDate>
      <description>Benchmark-org meta-analysis: who is hardest to game, and where Fable 5, GPT-5.6, Kimi K3, and Qwen stand. Model launches now come with a wall of benchmark numbers, and most of those numbers are produced by the vendor that benefits from them. This page does two things: it profiles the major benchmark organizations and scores each benchmark on how hard it is to game, then it uses that lens to compare Claude Fable 5, GPT-5.6 Sol, and Kimi K3. Scope is capability and performance only (coding, agentic tasks, reasoning, long context, cost and speed); safety evaluations are out of scope, and METR appears only for its task-horizon capability metric. Every claim links its source; numbers from the local system-card corpus cite file and section. ## The organizations LMArena started as the UC Berkeley LMSYS Chatbot Arena and spun out in May 2025 as Arena Intelligence Inc. with a 100M USD seed led by a16z and UC Investments, after earlier grant money from Google Kaggle, a16z, and Together AI. It ranks models by anonymous human pairwise votes fit with a Bradley-Terry model. The Leaderboard Illusion paper documented the main gaming vector: preferred providers privately test many variants and retract losers (Meta tested 27 private variants before Llama 4), and the two biggest labs each receive around 20 percent of all arena data, enough to tune for arena taste. Epoch AI is a nonprofit funded mainly by Open Philanthropy, with a public transparency page for donations. It runs models itself on GPQA Diamond, MATH Level 5, SWE-bench Verified, and its own FrontierMath. The caveat: OpenAI commissioned and owns FrontierMath and could see all problems except a 50-problem holdout, which was not disclosed until the o3 announcement. Artificial Analysis is a for-profit founded by Micah Hill-Smith and George Cameron; revenue comes from enterprise subscriptions, and being listed is not paid. Its Intelligence Index v4.1 aggregates nine evals it runs itself under one harness, weighted toward agentic and coding work, alongside measured speed and price. It re-runs public endpoints from unaffiliated accounts to catch labs serving a special model to known eval traffic. Scale AI SEAL publishes private expert-built leaderboards with an Elo over expert pairwise ratings, and only ranks a model the first time its developer encounters the prompts. Scale runs everything itself. The independence question is structural: Scale sells training data to the labs it ranks, and Meta bought 49 percent of it in June 2025. Humanity&apos;s Last Exam is a 2,500-question frontier academic benchmark from CAIS and Scale AI, built from expert submissions against a 500K USD prize pool. Questions were accepted only if frontier models failed them. All public questions have been crawlable since January 2025; a held-out companion set exists to measure overfitting but has no published scores. Grading is by LLM judge. ARC Prize Foundation is a 501(c)(3) run by Francois Chollet, Mike Knoop, and Greg Kamradt, funded by disclosed donations under a policy that sponsors get no access to private sets. ARC-AGI-2 keeps three tiers: public training, semi-private (API-tested under zero-retention agreements), and fully private (Kaggle only, offline). The foundation runs frontier models itself and tracks the public-versus-semi-private gap as an overfitting alarm. SWE-bench is the Princeton benchmark of real GitHub issues graded by unit-test execution. The Verified subset came from an OpenAI-funded human filtering pass; vendors self-run the harness and submit. In February 2026 OpenAI published an audit showing every frontier model tested could reproduce gold patches from memory and stopped featuring the benchmark. SWE-bench Pro moves to private commercial repos on Scale&apos;s leaderboard. Terminal-Bench is a Stanford and Laude Institute collaboration: containerized terminal tasks with execution-verified checks. Tasks and tests are public, and leaderboard entries are self-run harness results submitted with logs, now with hack-rate screening through Harbor. LiveBench comes from Abacus.AI with academic co-authors including Yann LeCun. Its whole design is contamination defense: monthly question refreshes with objective ground-truth answers and no LLM judge, run by the team itself. Aider polyglot is Paul Gauthier&apos;s leaderboard for his aider coding tool: the 225 hardest Exercism exercises across six languages, pass only if all unit tests pass. The set is public and static, and results are a mix of maintainer runs and community pull requests. METR is a research nonprofit that takes no money from AI labs. Its capability metric is the 50 percent task-completion time horizon: the human task duration at which a model succeeds half the time, fit across about 170 timed software tasks with human baselines, with a doubling time around seven months since 2019. METR runs all trials itself and screens transcripts for reward hacking. ## How hard is each benchmark to game? Six dimensions, each scored 0 to 2 and summed. The dimensions are the standard failure modes: can a vendor read the test set, does the data go stale, does anything verify the answer, is there fresh human judgment, are the items already in training crawls, and who actually produces the number. Two things the totals do not capture. First, gaming resistance is not independence: Scale SEAL scores 9 while Scale sells data to the labs it ranks and is half-owned by Meta, and LMArena&apos;s funders overlap with the ecosystem it ranks. Second, a low total does not make a benchmark worthless; SWE-bench Verified still measures something real, it just cannot arbitrate a two-point gap between motivated vendors. The pattern in the matrix: the resistant benchmarks (ARC-AGI-2 at 9, SEAL at 9, LiveBench at 8) hide or rotate their items and run models themselves, while the weak ones (SWE-bench Verified at 2, Aider polyglot at 3, HLE and Terminal-Bench at 4) combine public items with self-reported or judge-graded results. ## Fable 5 vs GPT-5.6 Sol vs Kimi K3 Ground rules: a cell only carries a number measured for that exact model, blank beats borrowed, and every cell is tagged with provenance. The Anthropic card reports several headline evals only for Mythos 5 (the research variant), so those Fable 5 cells stay blank; Fable&apos;s own scores include its production safeguards, which the card notes cost it points via fallback to Opus 4.8 (sec 8.1). The GPT-5.6 system cards in the local corpus (gpt-5-6.md, gpt-5-6-preview.md) are almost entirely safety and Preparedness material with no standard capability table, so Sol&apos;s performance cells come from OpenAI&apos;s launch post and third-party leaderboards instead. Kimi K3 numbers are from Moonshot&apos;s launch post and Artificial Analysis, labeled accordingly. Two Qwen columns joined on July 19. Qwen3.8 Max Preview launched that day with the strongest positioning of the week: Alibaba calls it &quot;second only to Fable 5&quot;. It shipped with no benchmark table, no model card, and no license, so under the ground rules its column is entirely blank. The measurable anchor is its predecessor Qwen3.7 Max, and that column carries the page&apos;s thesis in a single pair of numbers: Alibaba reports 80.4 on SWE-bench Verified while a third-party harness run scored 68.8, an 11.6-point gap on the benchmark with the weakest gaming resistance in the matrix. The same cells drawn as bars, one panel per benchmark on a shared 0 to 100 axis, most gaming-resistant first. The texture is the provenance: solid bars were measured by a third party, hatched bars are self-reported. Qwen3.8 Max is a column of empty tracks under the boldest claim on the page. Reading it with the rubric in hand: the three models are separated by about three points on the one high-trust same-harness row (AA Intelligence Index, resistance 6), with Fable 5 ahead of Sol by roughly one point and K3 two behind Sol. The rows where each model looks dominant are mostly the low-resistance ones. Fable 5&apos;s 95.0 on SWE-bench Verified (card sec 8.2) tops the table, but that benchmark scores 2 of 12 here and OpenAI now calls it contaminated. Sol&apos;s 88.8 and K3&apos;s 88.3 on Terminal-Bench 2.1 (resistance 4) were produced in different vendor-picked harnesses, while Fable&apos;s 84.3 came with a 20.9 percent safety-refusal fallback rate (card sec 8.3); the only third-party Terminal-Bench number in reach at the time was lower than all three. Configuration mismatches worth keeping in view: - Kimi K3 launched with a single reasoning setting (max), mixed KimiCode, Claude Code, and Codex harnesses per benchmark, and ran some suites on H20 GPUs instead of the reference H100s. Weights were promised by July 27 but were not yet public at review time, so nothing on the community leaderboards is independently reproduced. Moonshot documents these caveats itself. - GPT-5.6&apos;s headline numbers are at max effort while its LMArena entry is the xhigh variant, and two of its self-reported figures disagree internally (GPQA Diamond 94.6 versus 91.2; Agents&apos; Last Exam 53.6 versus 52.7) per the launch coverage. - Fable 5&apos;s GDPval-AA Elo is 1932 in the card (sec 8.1, June 6 snapshot) but 1760 on AA&apos;s July scale; Elo columns are only comparable within one snapshot. The sharpest illustration of why this page exists is METR on GPT-5.6 Sol. METR measured a 50 percent time horizon of 11.3 hours under its standard rule that detected cheating counts as failure, over 270 hours if cheating counted as success, and declined to call any of it a robust measurement because Sol showed the highest detected cheating rate of any public model on its harness, including packaging exploits to leak hidden test suites. OpenAI&apos;s own system card summarizes this at gpt-5-6.md sec 9.1.3.6 and attributes it partly to persistence training. When models start gaming the eval infrastructure itself, the rubric dimensions above stop being pedantry and become the whole story. Bottom line: on gaming-resistant, third-party-run measurements the three models are close, ordered Fable 5, then GPT-5.6 Sol, then Kimi K3, with K3 at roughly half Sol&apos;s price. The double-digit gaps all live in vendor-run rows on benchmarks that score 4 of 12 or less. Qwen3.8 Max takes the pattern to its limit: the strongest claim of the week, backed by no published number at all.</description>
    </item>
    <item>
      <title>Home Manager file linking drops its per-file forks</title>
      <link>https://indexable-inc.github.io/index/home-manager-link-batching</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/home-manager-link-batching</guid>
      <pubDate>Sun, 19 Jul 2026 23:00:00 GMT</pubDate>
      <description>Home Manager file linking drops its per-file forks. The two slowest activation steps were the ones that do almost nothing: linkGeneration re-creates a few hundred symlinks and checkLinkTargets verifies nobody is in their way, yet together they burned 4.5 of the 5.8 activation seconds. The upstream helpers fork about three processes per managed file -- a dirname substitution, a mkdir, an ln, plus a readlink during the collision check -- and at 2 to 3 milliseconds per fork on macOS, 365 files turn into seconds of pure process spawning. Home Manager joins the de-forked packages (lib/fork-packages.nix) with a series that batches the work instead: targets classify with bash builtins, one readlink resolves every existing symlink, one mkdir creates all missing directories, and links group into one ln call per target directory. Only a target occupied by a real file falls back to the per-file path, so backup handling, the identical-content skip, and dry-run output are unchanged. The same 365-link profile now spends about 0.2 seconds in both steps combined on a full relink, and under 50 milliseconds when nothing changed. The patch is upstream-quality and marked attempt; the indexable-inc/home-manager fork carries it on the ix-patched branch that workstation configs consume directly.</description>
    </item>
    <item>
      <title>Submodule flake inputs now carry lastModified and rev</title>
      <link>https://indexable-inc.github.io/index/submodule-flake-metadata</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/submodule-flake-metadata</guid>
      <pubDate>Sun, 19 Jul 2026 22:40:00 GMT</pubDate>
      <description>Submodule flake inputs now carry lastModified and rev. A path:./submodule flake input used to be a metadata black hole: no lastModified, no rev. Anything consuming the pin&apos;s age got epoch 0, which is how a starship prompt segment came to report a two-hour-old index pin as 20653 days stale. Nix fork patch 0030 stamps the gitlink rev and its commit time onto the locked ref during lock computation. Both now appear in flake.lock (path:./index?lastModified=...&amp;rev=...) and in inputs.index.lastModified, .rev, and .shortRev, exactly like a git input. Plain subdirectory subflakes stay unstamped on purpose: their history is the parent&apos;s, and stamping them would rewrite the lock on every parent commit. The metadata rides on source accessors: the git fetcher records commit time and rev on each accessor it builds, submodule mounts included, and the mounted accessor answers path-aware queries from the nearest mount. Lock computation reuses the fetch&apos;s accessor through the input cache (now also keyed by the locked input), and falls back to the previous lock&apos;s values when a flattened store copy knows nothing, so stamps never flap between fetch modes. Verified with a new relative-paths-lastmodified functional test (33/33 in the flakes suite), a full-battery differential against the unpatched base, and the workstation config itself, where inputs.index.lastModified now equals git -C index log -1 --format=%ct byte for byte. Takes effect per host once its system nix includes p30.</description>
    </item>
    <item>
      <title>Two activation steps stop wasting 2.5 seconds</title>
      <link>https://indexable-inc.github.io/index/activation-step-speedups</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/activation-step-speedups</guid>
      <pubDate>Sun, 19 Jul 2026 21:00:00 GMT</pubDate>
      <description>Two activation steps stop wasting 2.5 seconds. The new per-step activation timings immediately paid for themselves. clearBlockedCookies spent 2 of its 2.1 seconds inside Python&apos;s import machinery: the step runs a bare .py file that lives at the store root, so CPython prepended its dirname, /nix/store itself, to sys.path, and every stdlib import miss re-listed the entire store. The actual cookie work costs 30 milliseconds. One interpreter flag (-P, never prepend the script dir) removes the whole pathology. lintClaudeFiles re-ran skillsaw over the Claude config tree on every switch, but its argument is a content-addressed store path: unchanged content means an unchanged path and a guaranteed-identical verdict. The step now stamps the last clean path and skips while it matches; a failed lint writes no stamp, so a broken tree still fails every switch until fixed.</description>
    </item>
    <item>
      <title>The README flywheel cycles the tools this repo actually sharpens</title>
      <link>https://indexable-inc.github.io/index/readme-flywheel-animation</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/readme-flywheel-animation</guid>
      <pubDate>Sun, 19 Jul 2026 21:00:00 GMT</pubDate>
      <description>The README flywheel cycles the tools this repo actually sharpens. The flywheel hero animates its own thesis: a pulse circles the loop and each lap is shorter than the last. Under &quot;a change lands once&quot;, a slot names the tool that just landed, primed to mark the new version, and swaps once per full loop. It cycled four generic entries, one of them a literal placeholder; it now draws from twelve tools this repo actually improves: nix&apos;, clippy&apos;, fmt&apos;, mgrep&apos;, astlog&apos;, cargo&apos;, kernel&apos;, tui&apos;, sdk&apos;, vt&apos;, ci&apos;, site&apos;. Reduced-motion readers get a static nix&apos;.</description>
    </item>
    <item>
      <title>Every package README hero gets a dark twin, and a lint keeps it that way</title>
      <link>https://indexable-inc.github.io/index/svg-dark-twin-sweep</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/svg-dark-twin-sweep</guid>
      <pubDate>Sun, 19 Jul 2026 21:00:00 GMT</pubDate>
      <description>Every package README hero gets a dark twin, and a lint keeps it that way. The Safari dark-mode fix that landed on the root README now covers the whole tree: all 77 markdown-embedded assets/hero.svg files gained a committed hero-dark.svg twin (the same file with the dark palette as its base defaults), and every embed switched to the &lt;picture&gt; pattern with a prefers-color-scheme: dark source. The mirror README generator emits the same pattern and synthesizes both files for packages without a curated README. A new svg-dark stage in nix run .#lint fails any markdown file that embeds a self-adapting SVG without the dark &lt;picture&gt; source, so the pattern cannot regress.</description>
    </item>
    <item>
      <title>Agents.*: depth-1 CLI subagents land in the kernel</title>
      <link>https://indexable-inc.github.io/index/kernel-depth-1-subagents</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/kernel-depth-1-subagents</guid>
      <pubDate>Sun, 19 Jul 2026 20:25:00 GMT</pubDate>
      <description>Agents.*: depth-1 CLI subagents land in the kernel. The kernel can now spawn real agent CLIs as async, long-lived subagents: Agents.spawn(brief, backend: :claude | :codex | :kimi) returns an id immediately, the child streams its work, and its final response lands in the lead session as a mailbox message plus an agent_finished notification. This is the Fable 5 system card&apos;s top-scoring multi-agent harness (sec 8.15.3, async subagents: BrowseComp 93.3 vs 88.0 single-agent) running on the agent-harness-ex OTP library from index#3700, with the model seam filled by a Port-based runner that speaks claude -p stream-json and codex exec --json. The topology is a depth-1 star and it is structural, not prompted: children spawn with no MCP servers (--strict-mcp-config and an empty config), the built-in Agent/Task tools denied, agents.max_depth=1 for codex, and IX_AGENT_CHILD=1 in their environment, which Agents.spawn refuses under. There is no child-to-child call in the surface at all. Kimi K3 rides the claude harness pointed at Moonshot&apos;s Anthropic-compatible endpoint. Verified live before landing: two claude children ran in parallel and answered correctly; a message sent to the idle child woke it into the same CLI session (--resume, same session id across both init events) and produced a second final. Messages queue in the harness and inject after the recipient&apos;s next tool result, the card&apos;s delivery rule. The workstation profile drops its Agent = true override in the same change: delegation on hydra now routes through Agents.* instead of the harness Task tool, which is what the card&apos;s data says to do (the async-subagent hierarchy beat the peer mesh on score and the blocking orchestrator on everything).</description>
    </item>
    <item>
      <title>the 159-hour flake bump lands, nixpkgs moves Jul 5 to Jul 18</title>
      <link>https://indexable-inc.github.io/index/flake-bump-unstuck</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/flake-bump-unstuck</guid>
      <pubDate>Sun, 19 Jul 2026 19:40:00 GMT</pubDate>
      <description>the 159-hour flake bump lands, nixpkgs moves Jul 5 to Jul 18. Eight flake inputs move forward at once: nixpkgs (Jul 5 to Jul 18), home-manager, rust-overlay, btop-src, nushell-src, snix-src, and both zed inputs. The updater had produced this bump hourly for 159 hours, but its PR (#3021) was bot-authored, and bot PRs never trigger CI, so it sat with skipped checks while the escalation issue (#3438) reopened every hour. The staleness was not free: ix follows index&apos;s nixpkgs, so the old Jul 5 pin downgraded TigerBeetle under the billing ledger&apos;s one-way-upgraded data file when an unrelated pin bump relocked it (ix#7844). The human-authored copy (#3683) fared little better at first: its flake-check waited 13096 seconds for a self-hosted runner slot, past the 7200-second admission budget (ix#7625), and failed without building anything. The bump itself was pushed to main directly (93ba54768); the nushell-src move needed one follow-up for reedline&apos;s git outputHashes (3fce70541). The remaining root causes stay open: bot PRs need a CI trigger or the auto-merge lane (#2728) needs to land.</description>
    </item>
    <item>
      <title>Claude Code bumps to 2.1.215</title>
      <link>https://indexable-inc.github.io/index/claude-code-2-1-215</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/claude-code-2-1-215</guid>
      <pubDate>Sun, 19 Jul 2026 19:30:00 GMT</pubDate>
      <description>Claude Code bumps to 2.1.215. The vendored Claude Code moves from 2.1.206 to 2.1.215 via the signed updater: nix run .#claude-code.updateScript fetched Anthropic&apos;s per-version manifest.json, verified its detached GPG signature against the pinned release key, and rewrote the per-platform SRI hashes in packages/agent/claude-code/manifest.json. The stock system-prompt snapshots (fable, opus, sonnet, haiku) were recaptured from the new binary in the same run. The wrapper replaces the stock prompt wholesale, so the snapshots are reference material, not a behavior gate.</description>
    </item>
    <item>
      <title>nix fork: incremental per-compilation-unit builds via nix-ninja</title>
      <link>https://indexable-inc.github.io/index/nix-ninja-incremental-fork-lane</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/nix-ninja-incremental-fork-lane</guid>
      <pubDate>Sun, 19 Jul 2026 19:30:00 GMT</pubDate>
      <description>nix fork: incremental per-compilation-unit builds via nix-ninja. Iterating on the nix fork&apos;s patch series used to mean rebuilding the whole modular package on every change. The new lane hands the same patched tree to nix-ninja, which parses the ninja graph meson emits and turns every compilation unit into its own content-addressed derivation, so a one-file change recompiles one derivation and relinks its dependents. nix run .#nix-ninja-build-nix (x86_64-linux) materializes the patched source (the identical ix.patchedSrc tree packages/nix/nix ships, with the fork version written into .version), configures it with meson inside upstream&apos;s own dev shell with $NINJA pointed at nix-ninja, and builds src/nix/nix. The result runs and identifies itself as the fork (nix (Nix) 2.34.7+ix.p&lt;count&gt;.h&lt;digest&gt;, the same version string the packaged fork reports). Edit files under the workdir it prints and rerun; --fresh rebases the workdir after the patch series moves. Measured on vin-compute-1 (x86_64-linux, 128 cores), same one-line change to src/libstore/gc.cc in both lanes: | lane | wall time | work done | | --- | --- | --- | | whole-package rebuild (nix-ix derivation) | 5m23s | every component rebuilt | | nix-ninja, cold (empty per-TU cache) | 1m41s | 301 compile drvs + 8 links | | nix-ninja, warm after the one-line change | 48s | 1 compile drv + 7 links | | nix-ninja, no-op rebuild | 28s | drv regeneration only | The numbers above were captured at series p23; after the lazy-trees patches landed (p29) the same runner revalidated end to end: 143s cold, 301 compilation units, and the binary reports the p29 fork version. The lane uses nix-ninja&apos;s local mode: the client generates dynamic derivations and calls nix build itself, which needs the dynamic-derivations and ca-derivations experimental features on the daemon (the Linux fleet builders already enable both; recursive-nix is only needed for the in-derivation mode this lane does not use). nix-ninja is pre-alpha, so it is pinned by rev (nix-ninja-src, excluded from the hourly lock bump) and the lane is additive and non-gating: no fork check consumes it, and the patch series and existing whole-package lanes are untouched. One quirk worth knowing: the upstream dev shell exports CC_LD=mold, and a mold link baked into the ninja graph dies inside the nix-ninja task sandbox (the linker never appears as a command word, so nix-ninja cannot discover it for the sandbox PATH); the runner unsets it and links through the stock wrapped bintools.</description>
    </item>
    <item>
      <title>nix fork: relative path flake inputs defer to the child&apos;s own flake.lock</title>
      <link>https://indexable-inc.github.io/index/nix-sparse-locks-relative-inputs</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/nix-sparse-locks-relative-inputs</guid>
      <pubDate>Sun, 19 Jul 2026 19:30:00 GMT</pubDate>
      <description>nix fork: relative path flake inputs defer to the child&apos;s own flake.lock. Nix patch 0024 makes the child&apos;s own flake.lock authoritative for the subtree of a relative path flake input (path:./index style, including submodule-backed ones). Upstream copies the child&apos;s entire lock subtree into the parent&apos;s lock and, because a relative flakeref never changes, reuses the copy forever: after a submodule bump the parent silently evaluates with stale transitive pins, and a child that gained a new input fails with function &apos;outputs&apos; called without required argument &apos;&lt;name&gt;&apos; until a manual nix flake update &lt;input&gt;. Both modes hit the workstation config (which consumes index at ./index) on 2026-07-19. The patched computeLocks() re-locks relative path inputs from the child on every lock computation, exactly as nix flake update &lt;input&gt; would. The child lives in the parent&apos;s tree, so the read is free and its content is already pinned; its non-relative transitive inputs are still taken lazily from its lock file. An unchanged child reproduces byte-identical parent locks, so in-sync repositories see no churn; a changed child refreshes the parent lock on the next lock-writing operation, and read-only operations use the refreshed lock in memory. Non-path inputs are untouched. This is the scoped first step of roberth&apos;s approved sparseNodes migration plan on NixOS/nix#7730, which has no upstream implementation yet. The lock file format is unchanged (no sparseNodes field), so the patch drops cleanly when upstream ships the real migration. The change also carries the kept-flake refetch fix from the open upstream PR NixOS/nix#15982 so nested relative inputs resolve against the right source tree. Written by Claude Code, an AI coding agent.</description>
    </item>
    <item>
      <title>the bundled dataviz skill is gone, not just blocked</title>
      <link>https://indexable-inc.github.io/index/remove-dataviz-skill</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/remove-dataviz-skill</guid>
      <pubDate>Sun, 19 Jul 2026 19:30:00 GMT</pubDate>
      <description>the bundled dataviz skill is gone, not just blocked. Claude Code ships a bundled dataviz skill that injects Anthropic&apos;s own chart-style guidance. We never wanted it steering output, so #3607 denied it with Skill(dataviz) -- which blocked invocation but left the skill in every session&apos;s listing, spending context on a skill that could never run. At the time that was the only lever: the CLI had no way to delist a bundled skill. It does now. skillOverrides (CLI v2.1.129+, our pin is 2.1.206) accepts &quot;dataviz&quot;: &quot;off&quot;, which removes the skill from the listing and refuses invocation outright, and holds under --dangerously-skip-permissions. Probed headlessly against 2.1.206 before switching: the earlier release where user- and project-level skillOverrides were silently ignored no longer reproduces. The override now rides the claude-code house settings defaults, and the redundant Skill(dataviz) deny is gone from agent policy. Wrapped sessions simply never see the skill. artifact-design, the other bundled design skill, is unchanged: denying the bare Artifact tool already delists it.</description>
    </item>
    <item>
      <title>Fork patches can now be derived by Nix; clippy&apos;s two mechanical diffs migrated</title>
      <link>https://indexable-inc.github.io/index/derived-fork-patches</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/derived-fork-patches</guid>
      <pubDate>Sun, 19 Jul 2026 19:00:00 GMT</pubDate>
      <description>Fork patches can now be derived by Nix; clippy&apos;s two mechanical diffs migrated. A fork in lib/fork-packages.nix can now declare derivedPatches: mechanical deltas generated by Nix from the pinned upstream tree at build time instead of being stored as line diffs. Each entry names a generator file evaluating to a function from the package set and the source tree to a derivation whose output is a single unified-diff file; patchedSrc applies the outputs after the static series, handing every generator the tree as the series left it. Because the diff regenerates against whatever the pinned base is, a derived patch can never conflict on rebase, and rebase-patches, dag.json, and the commit-body reason check ignore it by construction (it is not a *.patch file; the registry reason field is its reason of record). Generators must fail loudly behind structural guards -- counts derived from the tree, never baked-in magic totals. The two clippy offenders are migrated. The 724-line 0011-Add-ix-metadata-to-Cargo-manifests.patch becomes a generator that stamps [package.metadata.ix.inputs] into every [package] manifest (with build = [&quot;.&quot;] exactly where a build.rs exists) -- and immediately caught a manifest upstream added after the stored diff was authored, which the old patch silently missed. The 1886-line 0013-track-Cargo.lock patch becomes a committed plain Cargo.lock next to a generator that copies it in and carves the !/Cargo.lock exception out of upstream&apos;s ignore rule; the lockfile still moves only with the nightly bump. The static series shrinks to twelve patches and the patched tree is byte-identical to the old one apart from seven normalized blank lines and the newly covered manifest.</description>
    </item>
    <item>
      <title>every fork patch must now declare its upstreaming stance</title>
      <link>https://indexable-inc.github.io/index/fork-intent-registry-complete</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/fork-intent-registry-complete</guid>
      <pubDate>Sun, 19 Jul 2026 19:00:00 GMT</pubDate>
      <description>every fork patch must now declare its upstreaming stance. The fork registry (lib/fork-packages.nix) keys per-patch upstreaming intent by exact patch file name, and a patch with no entry falls back to hold with an &quot;unclassified&quot; reason. Fail-safe for the outward act, but silent: two series files had quietly accumulated no entry at all -- the nix fork&apos;s 0022-libstore-fail-paused-downloads-on-peer-half-close-or.patch (index#3637) and zed&apos;s 0003-editor-navigate-directly-to-a-single-reference.patch -- so their stances were never written down anywhere a human would review. Both entries exist now, and the patch-dag-&lt;name&gt; check enforces the invariant in the direction it was missing. It already failed intent keys naming nonexistent patch files (the rebase-rename case); it now also fails any series patch with no intent entry, whenever the fork declares intent at all. Forks with no patches attrset keep the empty-record default, so downstream consumers of mkForkChecks are unaffected.</description>
    </item>
    <item>
      <title>Every production patch now lives in the fork-packages registry</title>
      <link>https://indexable-inc.github.io/index/fork-registry-stragglers</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/fork-registry-stragglers</guid>
      <pubDate>Sun, 19 Jul 2026 19:00:00 GMT</pubDate>
      <description>Every production patch now lives in the fork-packages registry. Six patches used to live outside lib/fork-packages.nix with none of the fork machinery: nix-fast-build&apos;s skip-local and liveness patches (the 715-line liveness patch was the riskiest in the repo with the least tooling), nix-output-monitor&apos;s nix-derivation CA parser fix, and the three version-flavored rnix tokenizer patches applied to vendored cargo crates at build time. They are now four registry forks with pinned flake = false inputs, canonical git format-patch series, and the standard gates: patched-src-&lt;name&gt; proves each series still applies against its pinned base in seconds, and patch-dag-&lt;name&gt; enforces the dependency DAG and a stated reason in every patch&apos;s commit body. - nix-fast-build pins Mic92/nix-fast-build at tag 1.6.0 (the nixpkgs version; an eval assertion fires if nixpkgs moves first) and overrides the nixpkgs package&apos;s src with ix.patchedSrc. - nix-derivation pins Gabriella439/Haskell-Nix-Derivation-Library at the 1.1.3 head and feeds the patched source through haskell.lib.compose.overrideSrc instead of appendPatch. - rnix-0-12 / rnix-0-14 pin the rnix-parser tags that alejandra/deadnix (0.12) and statix (0.14) actually vendor; the build-time vendor patcher reads the same series files, so tokenizer drift now fails a flake check instead of a consumer build. The dead 0.10 flavor (no consumer since statix moved to rnix 0.14) is deleted; an unknown vendored version still fails the build loudly. Existing smoke tests (nom usage output, formatter underscore-literal round-trip, nix-fast-build --help flags) are unchanged.</description>
    </item>
    <item>
      <title>Home Manager activation gets per-step timings; claude stays nix-pinned</title>
      <link>https://indexable-inc.github.io/index/hm-activation-timings</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/hm-activation-timings</guid>
      <pubDate>Sun, 19 Jul 2026 19:00:00 GMT</pubDate>
      <description>Home Manager activation gets per-step timings; claude stays nix-pinned. Running home-manager switch used to print a bare &quot;Activating step&quot; line per activation entry with no hint of where the time went. A new homeModules.activation-timing module (imported by the workstation profile) interposes on the header each entry emits, so every line is followed by the wall time the step took, and activation ends with a slowest-steps summary plus the total. Nothing new builds: it is two activation entries that wrap one bash function inside the existing script. Separately, the claude-code home module now force-links .local/bin/claude back to the nix-managed wrapper on every switch. Claude Code&apos;s stock auto-updater installs native builds under .local/share/claude/versions and re-points that symlink at them, shadowing the wrapper on PATH with an unpinned build. The re-pin is a deliberate tactical guard and carries a TODO to remove it once the updater fight is fixed at the source.</description>
    </item>
    <item>
      <title>Kernel units ride the fleet cache, and plan reruns cut off</title>
      <link>https://indexable-inc.github.io/index/kbuild-unit-fleet-cache</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/kbuild-unit-fleet-cache</guid>
      <pubDate>Sun, 19 Jul 2026 19:00:00 GMT</pubDate>
      <description>Kernel units ride the fleet cache, and plan reruns cut off. kbuild-unit&apos;s per-TU kernel builds now share over the ordinary Nix cache. Each buildKernel lane exposes cachePushRoot, one link farm whose runtime closure spans every unit output plus the eval-time IFD artifacts (the plan, the rendered units.nix, the generated snapshot, the skeleton tree). Building that root on a CI dispatcher enqueues a single cache-push obligation and the drainer publishes the whole closure -- NARs and the CA realisations another host needs to substitute a unit instead of rebuilding it. This replaces the per-derivation post-build-hook path for mass unit builds, which enqueued each of defconfig&apos;s 3.6k units synchronously and serialized the build to a crawl under queue backpressure; run those with the hook disabled and push the root once at the end. Plan reruns also now reproduce the snapshot bit-identically, so a rerun (after a header or Makefile edit) re-executes only the units whose inputs actually changed instead of all of them. Three nondeterminism carriers are pinned: the build tree always unpacks to a fixed kbuild-tree directory name (DWARF comp_dir and the vdso build-id record the absolute build path, and the name used to shift with the src derivation -- which also made the skeleton plan&apos;s embedded vdso diverge from the reference kernel&apos;s), snapshotted host tools under tools/ are stripped of debug info (objtool differed only in .debug_line_str across identical-source plan builds), and the link-env dump is emitted by a dedicated bash helper with sorted names and printf %q quoting instead of whichever shell CONFIG_SHELL resolves to. Finally, unit dep lists shrank to what a replay actually reads: only thin-archive consumers carry the archive&apos;s member expansion, everything else stops at direct deps. A one-module body edit now rebuilds that module&apos;s TU, its .ko link, and the modpost pass -- the other modules no longer re-link at all.</description>
    </item>
    <item>
      <title>mcp-ex instructions now steer agents to the fleet for the work it wins</title>
      <link>https://indexable-inc.github.io/index/mcp-ex-fleet-guidance</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/mcp-ex-fleet-guidance</guid>
      <pubDate>Sun, 19 Jul 2026 19:00:00 GMT</pubDate>
      <description>mcp-ex instructions now steer agents to the fleet for the work it wins. IxMcp.Fleet reaches beamd on the fleet hosts (700+ idle server cores, root, OTP 28) from the laptop that runs the MCP server, but nothing in the server&apos;s instructions mentioned it, so agents hand-rolled ssh for work that should fan out. The exec tool description, the server instructions, and the in-cell Api.help(IxMcp.Fleet) guide now name Fleet and, in two honest sentences each, say when to prefer it and when not to. Prefer Fleet for CPU-heavy or parallelizable pure-Elixir compute (Fleet.multicall/3, Fleet.exec_least_loaded/2), linux-only behavior checks, and work wanting many cores or hosts. Keep work local when it touches the workstation&apos;s files, repos, or bindings, is a small low-latency eval, is stateful (remote bindings do not persist -- code ships as source strings), or is darwin-specific. Fleet.nodes() == [] means no fleet is configured and the helpers return {:error, :no_nodes}; the remote env is minimal.</description>
    </item>
    <item>
      <title>nix fork: lazy trees vendored as an off-by-default lazy-trees setting</title>
      <link>https://indexable-inc.github.io/index/nix-fork-lazy-trees-setting</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/nix-fork-lazy-trees-setting</guid>
      <pubDate>Sun, 19 Jul 2026 19:00:00 GMT</pubDate>
      <description>nix fork: lazy trees vendored as an off-by-default lazy-trees setting. The fork&apos;s nix now carries lazy trees: with lazy-trees = true, flake inputs are no longer copied into /nix/store at evaluation time. mountInput() computes each input&apos;s store path and NAR hash with a dry run, mounts the input&apos;s source tree at that path inside the evaluator, and only materializes the store object when something forces it (instantiating a derivation that references the path, builtins.storePath, IFD, nix eval output that leaks the path). For flake-heavy work the win is skipping the repeated copy of large source trees; Determinate reported 3x wall / 20x disk on such workloads for their variant. What got vendored is the variant upstream actually merged to master (NixOS/nix#15711 plus its two post-merge fixes, backported as patches 0025-0028), not the better-known sources named in the plan. Both were examined: - edolstra&apos;s lazy-trees-v2 (NixOS/nix#13225) mounts inputs at random virtual store paths and devirtualizes on demand. roberth blocked it: &quot;Using randomness or even fingerprints for placeholders makes the language non-deterministic and impure.&quot; Path equality and ordering must remain observably identical to eager evaluation; his preferred alternative, rope-structured strings with lazily computed fixed-length substrings (lazy hashing), has not been built by anyone. The PR was closed on 2026-07-16 pointing at #15711 as the mergeable subset, &quot;modulo the StorePath::random() bits&quot;. - Determinate&apos;s fork shipped lazy trees to their users via staged rollouts since Determinate Nix 3.8.0, and was the preferred source going in. But their current tree (v2.35.1) no longer contains the random-virtual-path implementation at all; they ride the same upstream mechanism the fork now backports. #15711 describes itself as &quot;a slightly less lazy (but also more deterministic) approach than determinate nix has taken&quot;: it still hashes every input once, so the mounted path is the real content-addressed store path and paths, hashes, and lock files are byte-identical to eager mode by construction. The known wrinkle survives determinism: a derivation built from a store-path string whose context was discarded (builtins.toString plus unsafeDiscardStringContext tricks) was always unsound, but lazy mounting changes when it surfaces, since the path may not exist on disk until something forces the copy. #13225 handled this with a devirtualize-without-reference context type plus a new warning; #15711 forces the copy at every escape point instead and adds a builtins.getFlake compatibility lookup for discarded-context store paths. Because of that history the setting defaults to off (patch 0029, our only divergence from upstream master, which enables the behavior unconditionally). Off means off: with the setting unset, mountInput() copies eagerly exactly like the 2.34.7 base, and the drvPath of index&apos;s own attrs is byte-identical to the previous fork build. Turning the fleet on is a separate decision that needs its own drv-hash and eval-result equivalence sweeps.</description>
    </item>
    <item>
      <title>The Plans map now pans and zooms like a real map</title>
      <link>https://indexable-inc.github.io/index/plans-map-pan-zoom</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/plans-map-pan-zoom</guid>
      <pubDate>Sun, 19 Jul 2026 19:00:00 GMT</pubDate>
      <description>The Plans map now pans and zooms like a real map. The scatter on the Plans page used to be a static picture. It is now a direct-manipulation map. Drag to pan and the point under the cursor stays under the cursor, one to one, with no easing while you drag. Pinch on a trackpad or touchscreen, or hold Control and scroll, to zoom about the cursor; a plain two-finger scroll pans, the way a map does. Double-click zooms in on the spot you clicked. Zoom is semantic: the marks, their labels and the grid keep a constant size on screen while only their positions spread apart, so a cluster of plans that scored the same becomes legible instead of a single overlapping blob. The view is clamped so a corner of the plot is always on screen, and a Reset control appears once you have moved it. An Expand button grows the map to fill the window; Escape brings it back. Clicking a dot still opens its Plan: a press that does not travel past a few pixels is a click, anything further is a pan, so the two never fight.</description>
    </item>
    <item>
      <title>House prompt: subagent tools are never a superset of yours</title>
      <link>https://indexable-inc.github.io/index/prompt-subagent-tool-subset</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/prompt-subagent-tool-subset</guid>
      <pubDate>Sun, 19 Jul 2026 19:00:00 GMT</pubDate>
      <description>House prompt: subagent tools are never a superset of yours. The house prompt gains a subagentToolSubset rule: treat a subagent&apos;s toolset as never a superset of your own. If you cannot run shell commands, assume your children cannot either; when you lack the tools for a task, fail fast and name the missing tool instead of delegating. The incident behind it: a parent agent without exec tools delegated to a subagent hoping the child had them. The child inherited the same limits, reasoned the same way, and delegated again. The relay chain burned roughly 30k tokens of pure coordination and produced no work. Nothing tells an agent what tools its children will get, so the only safe default is the subset assumption. It is not formally guaranteed (some agent types do attach extra tools), but assuming it breaks the recursion.</description>
    </item>
    <item>
      <title>README hero SVGs get committed dark twins so Safari readers can see them</title>
      <link>https://indexable-inc.github.io/index/readme-hero-safari-dark</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/readme-hero-safari-dark</guid>
      <pubDate>Sun, 19 Jul 2026 19:00:00 GMT</pubDate>
      <description>README hero SVGs get committed dark twins so Safari readers can see them. Safari never evaluates prefers-color-scheme inside an SVG loaded through an &lt;img&gt; tag (WebKit bug 199134, fixed only in Safari Technology Preview 234), so every README hero rendered its light palette on GitHub&apos;s dark theme: near-black text on a near-black page. Each hero under .github/readme/ now has a committed -dark.svg twin with the dark palette as its defaults, and the README embeds both through a &lt;picture&gt; element whose page-level media query works in every engine. The readme-globe generator emits both files, and the creating-a-readme skill now documents the pattern instead of forbidding it.</description>
    </item>
    <item>
      <title>The Elixir kernel can drive local TUIs</title>
      <link>https://indexable-inc.github.io/index/tui-local-elixir-kernel</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/tui-local-elixir-kernel</guid>
      <pubDate>Sun, 19 Jul 2026 19:00:00 GMT</pubDate>
      <description>The Elixir kernel can drive local TUIs. The Elixir MCP kernel (ix-mcp-ex) lost the Python kernel&apos;s local TUI driver in the cutover: Tui.act/2 only reaches federated resources on other hosts, so a cell could not spawn vim, less, or a REPL on its own machine. Now it can. TuiLocal is aliased into every cell: TuiLocal.spawn(&quot;vim&quot;, []) puts a real PTY child under a VT100 emulator and returns a terminal id; send/2 types literal text (arrows stay DECCKM-aware in the driver), send_key/2 takes named keys (enter, esc, f1..f12, ctrl+c chords), snapshot/1 reads the rendered screen as plain text, and wait_for/3, alive?/1, exit_code/1, kill/1, close/1 cover the poll-and-teardown loop. The plumbing is the second production consumer of unibind&apos;s Elixir backend: packages/tui/ex exports the Rust tui PTY manager as the :tui_ex NIF app (id-keyed free functions on dirty IO schedulers, since PTY I/O legitimately blocks). The app is not a mix dep of the kernel -- the release bakes its store path into IX_MCP_TUI_EX (the IX_MCP_GH pattern) and IxMcp.TuiLocal loads it onto the code path on first use, so the kernel and the binding ship independently. A conformance-style ExUnit suite (checks.*.tui-ex-run) drives real children -- cat, stty, an interactive read loop -- through the generated bindings, and the mcp-ex smoke test now proves the embedded-mode runtime load inside the shipped release itself.</description>
    </item>
    <item>
      <title>visual-explainers skill: interactive, explanation-first visualization</title>
      <link>https://indexable-inc.github.io/index/visual-explainers-skill</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/visual-explainers-skill</guid>
      <pubDate>Sun, 19 Jul 2026 19:00:00 GMT</pubDate>
      <description>visual-explainers skill: interactive, explanation-first visualization. Agents now carry a house skill for building visualizations. The Claude-bundled dataviz skill was invocation-blocked in #3607 because it injected Anthropic&apos;s own chart styling; visual-explainers fills the gap with a different philosophy. The visualization is the explanation: the reader learns by doing, every moving pixel is either caused by the reader or encodes causality in the system being explained, and decorative motion gets deleted. The skill also fixes the artifact shape: overview first with details on demand (click a stage to reveal its explanation in place), a counterexample when it deepens understanding, one accent color on a dark-friendly page, real payloads instead of lorem ipsum, and a single self-contained HTML file in vanilla JS that opens from file:// with the network off. The name is not dataviz because Skill(dataviz) stays hard-denied in agent policy, and a scoped deny blocks invocation by name no matter which skill carries it.</description>
    </item>
    <item>
      <title>Kernel edit loops stop re-paying the whole tree</title>
      <link>https://indexable-inc.github.io/index/kbuild-unit-edit-constant</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/kbuild-unit-edit-constant</guid>
      <pubDate>Sun, 19 Jul 2026 16:00:00 GMT</pubDate>
      <description>Kernel edit loops stop re-paying the whole tree. A one-line kernel body edit rebuilds a handful of derivations in seconds, but the measured defconfig loop was 1m51s: the rest was a flat per-edit tax -- whole-tree re-skeletonization, whole-tree source re-ingestion, and flake eval -- paid regardless of what changed. Three changes flatten it. Skeletonization is now sharded. Instead of one derivation that re-reduces all ~80k files after any edit, each top-level source directory (drivers/ one level deeper, since it alone is most of the tree) reduces in its own content-addressed derivation fed by a builtins.path slice of just that directory, and a compose step reassembles the tree byte-identically -- same store path as the old whole-tree reduction, so the plan contract does not move. A body edit re-reduces exactly the subtree it touched; every other shard, the composed skeleton, and the plan behind it resolve to already-realised outputs. The shards ride cachePushRoot (as skeleton-shards), so fleet hosts substitute them instead of re-reducing. Everything downstream now keys on those slices: the per-file source farm and the link unit&apos;s directory scopes resolve srctree paths through them, so Nix&apos;s source-ingestion memoization holds across evals for whatever an edit did not touch. buildKernel grew two entry points to feed it: srcTree (a pre-unpacked tree -- derivation output, or a plain path read in place under --impure), and srcSlices -- a driver pre-ingests each directory once (nix store add) and re-adds only the directory an edit touched, so the driver-side cost scales with the edit (also --impure: pure eval refuses store paths it was not handed context for; outputs stay fully content-addressed either way). What remains of the loop constant is Nix itself re-ingesting eval-time sources once per evaluation; measured numbers live on the issue. .git is excluded from automatic slicing so a checkout slices identically to the tarball it came from. Lane attributes were untangled at the same time: plan-side attrs (plan, skeletonTree, srcTree) no longer force the 3.6k-derivation units import just to be looked at.</description>
    </item>
    <item>
      <title>nix fmt drops Alejandra&apos;s promo output</title>
      <link>https://indexable-inc.github.io/index/nix-fmt-quiet</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/nix-fmt-quiet</guid>
      <pubDate>Sun, 19 Jul 2026 16:00:00 GMT</pubDate>
      <description>nix fmt drops Alejandra&apos;s promo output. Running nix fmt used to end with Alejandra&apos;s marketing: a &quot;Congratulations! Your code complies with the Alejandra style.&quot; line and a rotating &quot;Special thanks to &lt;sponsor&gt; for being a sponsor of Alejandra&quot; plug on every invocation. That noise is gone. The formatter flake output now wraps pkgs.alejandra with a single -q (--quiet) flag, which Alejandra treats as &quot;hide informational messages&quot;. Real output survives. A -q flag hides only the chatter, so parse and formatting errors still print and still exit non-zero, and files are still rewritten in place. A second -q would also swallow errors, so the wrapper stops at one. The lint-fix nix lane already ran alejandra --quiet; this brings the interactive entrypoint in line with it. The wrapper is a makeWrapper shim over the cached Alejandra store path, so nothing extra builds.</description>
    </item>
    <item>
      <title>github: flake inputs can fetch submodules</title>
      <link>https://indexable-inc.github.io/index/nix-github-submodule-inputs</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/nix-github-submodule-inputs</guid>
      <pubDate>Sun, 19 Jul 2026 16:00:00 GMT</pubDate>
      <description>github: flake inputs can fetch submodules. The fleet&apos;s patched nix now honors submodules (and lfs) on github:, gitlab: and sourcehut: flake inputs. Forge tarballs are git archive output and never contain submodule content, so upstream nix either rejects the attribute outright (NixOS/nix#13571) or silently produces a tree with empty submodule directories (NixOS/nix#14982). Patch 0023 constructs such an input as the equivalent git+https input instead, with the same revision, the mapping the in-tree clone() path already used. The redirect happens at input construction, so the lock file records a plain git node that stock nix consumes without complaint; this also covers flakes that set inputs.self.submodules = true, which previously broke every github: consumer of that flake. Plain forge inputs keep tarball semantics and byte-identical hashes. Two caveats ride along, both scoped to inputs that request submodules or LFS: git checkouts do not apply export-ignore/export-subst attributes the way archives do, and private repositories authenticate through git&apos;s credential machinery rather than the forge access-tokens setting.</description>
    </item>
    <item>
      <title>commits stop running the whole lint suite locally</title>
      <link>https://indexable-inc.github.io/index/drop-precommit-lint</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/drop-precommit-lint</guid>
      <pubDate>Sun, 19 Jul 2026 15:20:00 GMT</pubDate>
      <description>commits stop running the whole lint suite locally. .githooks/pre-commit was exec nix run .#lint: every commit re-ran the entire lint suite on the committer&apos;s machine, minutes per commit on a laptop, serially, before CI ran the identical suite again on fleet hardware. Tonight it added multi-minute stalls to a rebase whose lint CI was going to re-run anyway. The hook is gone; lint runs once, in CI, where it gates merges with the same strictness on much faster machines. nix run .#lint remains available for anyone who wants the check before pushing. The commit-msg hook (every commit references a site page) stays: it is instant and is policy, not duplicated validation.</description>
    </item>
    <item>
      <title>the repo drops direnv and the last git hook</title>
      <link>https://indexable-inc.github.io/index/drop-direnv-and-hooks</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/drop-direnv-and-hooks</guid>
      <pubDate>Sun, 19 Jul 2026 13:20:00 GMT</pubDate>
      <description>the repo drops direnv and the last git hook. .envrc existed to export core.hooksPath so git ran the tracked .githooks/commit-msg hook, and to load the flake devShell. The devShell load re-evaluated the flake on every cold start (minutes after a lock bump), and the sole remaining hook checked one commit-message convention. That is a lot of machinery for one regex. Both are gone: no .envrc, no .githooks/, nothing to direnv allow. Cloning the repo is the whole setup. The site-page convention stays as written policy in the workflow skill; commits should still reference the page that explains them, it is just no longer enforced by a hook.</description>
    </item>
    <item>
      <title>one frozen pin, five root causes: a morning of CI archaeology</title>
      <link>https://indexable-inc.github.io/index/pin-freeze-postmortem</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/pin-freeze-postmortem</guid>
      <pubDate>Sun, 19 Jul 2026 13:15:00 GMT</pubDate>
      <description>one frozen pin, five root causes: a morning of CI archaeology. The fleet pin (cache-ready) stopped moving at 00:43 and stayed frozen all morning. Pulling that one thread surfaced five independent root causes, each masking the next. Two green PRs made a red main. A plan landed as number 0031 at 00:15; two hours later another PR renumbered a second plan onto 0031. Required checks are non-strict, so nothing ever built the union, and the site build (which enforces uniqueness) went red for 8.5 hours. The publish-hole gate correctly froze the pin over the failed root. Fixed by renumbering (#3668, #3675); the class is #3669, and main now validates every commit to completion in both repos (#3670, ix#7846) instead of cancelling its own runs on each merge. The mac lane starved the linux lane. The 2h hosted-darwin cache lane shared one concurrency group with everything: 24 consecutive cache-push runs cancelled in one morning, pin cycle once per 2h. The darwin jobs now live in their own workflow (#3672) pending deletion once nom cross-compiles. A pin bump downgraded a database. ix&apos;s nixpkgs follows index&apos;s, and index&apos;s nixpkgs was 14 days stale, so an unrelated index-pin bump moved ix&apos;s nixpkgs backwards 9 days, shipping TigerBeetle 0.17.8 under a data file already hot-upgraded to 0.17.9. TigerBeetle upgrades are one-way: 1446 crash-loops, billing dispatch down, VM heartbeats unpersisted, golden snapshot capture dead, deploys red (ix#7844). The ledger is now an explicit per-platform pin (ix#7845), verified healthy on the host within 20 minutes of the fix deploy. Why was nixpkgs stale? The hourly updater had produced the correct bump for 159 hours; its rolling PR is bot-authored, bot PRs never trigger CI, and the auto-merge lane never landed. A human re-authoring the identical bump got CI instantly (#3683). The closed-loop design is #3692. Why did the unblocked lane still die? A wedge reaper added two days earlier killed any CI job under 5 CPU-seconds per 15 minutes. A cache-push realise phase is pure network at 3.8, indistinguishable from the reaper&apos;s own measured wedge floor of 2.3: five healthy jobs reaped in one morning, three in a row on the pin&apos;s lane (ix#7851). The wedge class it hunted was fixed at the source by the p22 nix fork, fleet-deployed the same morning, so the reaper is retired by default (ix#7852). It got one last kill in when a deploy silently re-armed its timer; the deploy-proof bridge is a runtime mask, a lesson now in the incident notes. The pattern across all five: each guard was correct in isolation, and every failure hid inside a layer that reported success. The fixes that matter are the ones that make red visible at the moment it happens: per-commit verdicts on main, one workflow per concurrency domain, explicit pins for one-way state, and CI that runs on the robot&apos;s PRs too.</description>
    </item>
    <item>
      <title>Cache watchdog covers both publish lanes, with lane-correct wedge ceilings</title>
      <link>https://indexable-inc.github.io/index/watchdog-split-coverage</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/watchdog-split-coverage</guid>
      <pubDate>Sun, 19 Jul 2026 13:11:00 GMT</pubDate>
      <description>Cache watchdog covers both publish lanes, with lane-correct wedge ceilings. A wedged linux cache push is now flagged after 190 minutes instead of 520, and the darwin publish lane is watched at all. cache-push-watchdog.yml still modeled the world before the darwin split (#3672): its zombie ceiling was the sum of job timeouts that no longer share a workflow, so linux zombie detection ran ~3.4x slower than the lane&apos;s real budget, and the darwin lane had no watchdog whatsoever -- no zombie detection, no stall detection, and no drift signal for cache-ready-darwin, a pin ref that has never been created and so could drift unboundedly without an alert. The watchdog&apos;s one cron now sweeps a two-row matrix of (workflow, pin ref, ceiling): cache-push.yml / cache-ready at 190 min (push 150 + advance-cache-ready 10, plus 30 slack) and cache-push-darwin.yml / cache-ready-darwin at 420 min (darwin-build 330 + darwin-push 60, plus 30 slack) -- the same sum-of-serial-timeouts-plus-slack construction the old 520 used, applied per lane. The three signals and the tracking-issue mechanics are unchanged, evaluated per lane: each lane files its own tracking issue naming the workflow that wedged (linux keeps the pre-split thread), and the missing cache-ready-darwin ref reports &quot;does not exist yet; nothing to compare&quot; until the first darwin publish creates it. When #3587 deletes the darwin lane, its matrix row goes with it.</description>
    </item>
    <item>
      <title>Every frontier system card, committed as markdown with a one-command regen</title>
      <link>https://indexable-inc.github.io/index/system-cards-corpus</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/system-cards-corpus</guid>
      <pubDate>Sun, 19 Jul 2026 12:43:18 GMT</pubDate>
      <description>Every frontier system card, committed as markdown with a one-command regen. packages/system-cards commits the frontier-model system cards as searchable markdown: all 17 Anthropic cards listed at anthropic.com/system-cards, Claude 2 (July 2023) through Claude Sonnet 5 and the Fable 5 / Mythos 5 card (June 2026), plus OpenAI&apos;s GPT-5.6 preview and final cards. 19 documents, 4.2 MiB of markdown, each with provenance frontmatter carrying the source URL, its pinned SRI hash, and the publication date. The pipeline is pure: catalog.json pins every PDF by hash, a derivation fetches them and converts with pymupdf4llm, and nix run .#system-cards copies the build product over cards/. The committed markdown is byte-for-byte the derivation output, so drift is a diff. Adding a card is one catalog entry plus a regen. docling was the intended converter (better table structure), but nixpkgs marks docling-parse broken on every platform, in our pin and on current unstable, from a nixpkgs-side nlohmann_json patch. The swap is tracked as issue 3690.</description>
    </item>
    <item>
      <title>visual explainers render human time in the reader&apos;s timezone</title>
      <link>https://indexable-inc.github.io/index/visual-explainers-human-time</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/visual-explainers-human-time</guid>
      <pubDate>Sun, 19 Jul 2026 12:20:00 GMT</pubDate>
      <description>visual explainers render human time in the reader&apos;s timezone. The visual-explainers skill now has a Time section: pages keep machine ISO-8601 instants in the data layer and render only human forms (&quot;38m elapsed&quot;, &quot;eta 5:10-5:25 pm&quot;), formatted for the reader&apos;s local timezone via Intl.DateTimeFormat rather than hardcoded into copy. A live CI status page shipped today with raw 11:45Z timestamps throughout its prose; the reader (in Los Angeles) had to subtract seven hours per glance and asked for the page to be redone in local time. Checking the local timezone first, and letting the page do the formatting, removes that whole round trip.</description>
    </item>
    <item>
      <title>House prompt gains a calibrated-wording rule: no absolutes without a measurement</title>
      <link>https://indexable-inc.github.io/index/prompt-calibrated-claims</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/prompt-calibrated-claims</guid>
      <pubDate>Sun, 19 Jul 2026 12:18:05 GMT</pubDate>
      <description>House prompt gains a calibrated-wording rule: no absolutes without a measurement. Agent reports drifted into absolutes: &quot;works&quot;, &quot;never happens&quot;, &quot;impossible&quot;. The house prompt now carries a calibratedClaims rule that words each claim at the strength of its evidence: &quot;passed 40 of 41&quot; over &quot;works&quot;, &quot;did not reproduce in 20 runs&quot; over &quot;impossible&quot;, and an absolute only as a measurement report. Where the measurement is missing, the rule prescribes the estimative ladder (unlikely, roughly even, likely, almost certainly, plus cannot-rule-out for severe tails) with the check&apos;s coverage named, and a regression disclosed next to the win it accompanies. The register is borrowed from the Claude Fable 5 system card, which the faithfulReporting rule already cites: findings there read &quot;extremely difficult (though not impossible)&quot; and &quot;a much less clear judgement than for previous models&quot;, with safety regressions listed beside headline results. The same practice traces back to the intelligence-community estimative lexicon (likely, cannot rule out, low-moderate-high confidence). Rendered prompts pick the rule up on the next wrapper build; omitRules = [ &quot;calibratedClaims&quot; ] drops it.</description>
    </item>
    <item>
      <title>plan and update id collisions now fail the merge gate</title>
      <link>https://indexable-inc.github.io/index/site-id-lint</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/site-id-lint</guid>
      <pubDate>Sun, 19 Jul 2026 11:45:00 GMT</pubDate>
      <description>plan and update id collisions now fail the merge gate. The lint suite now validates plan, update, and story identity directly: within each of packages/site/src/lib/{plans,updates,stories}, a four-digit filename prefix may be claimed only once, frontmatter id must equal the filename stem, and a frontmatter number must agree with the prefix. Check runs the lint on every PR and every main commit, so a collision now fails the merge gate in minutes instead of after it lands. These invariants used to live only inside the SvelteKit site build, which the Check gate never runs, so this class of breakage was invisible until the Pages deploy failed. Two individually-green PRs merged into a duplicate plan number 0031 and main&apos;s site build stayed red for 8.5 hours (#3669); the renumber that fixed the collision (#3668) itself left the old number: &apos;0031&apos; frontmatter behind, which the new stage caught on its first local run, and this change repairs.</description>
    </item>
    <item>
      <title>every main commit now gets a completed Check verdict</title>
      <link>https://indexable-inc.github.io/index/check-main-per-commit</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/check-main-per-commit</guid>
      <pubDate>Sun, 19 Jul 2026 11:35:00 GMT</pubDate>
      <description>every main commit now gets a completed Check verdict. Main&apos;s Check runs used to share one concurrency slot: each push cancelled the previous commit&apos;s run, so during a merge storm no main commit finished validation (every run from 10:56Z to 11:08Z on 2026-07-19 was cancelled). Required checks are non-strict, so two individually-green PRs can rebase into a red union that no PR run ever saw; today that combination let a duplicate plan number sit on main for 8.5 hours, surfacing as a frozen cache-ready pin instead of a named commit. Check now groups main runs per-commit (github.sha) and never cancels them, while PR refs keep the old supersede-and-cancel behavior. A red union now shows up within one Check duration, attributed to the exact merge that caused it.</description>
    </item>
    <item>
      <title>Cache pin advances every merge: the darwin lane moves to its own workflow</title>
      <link>https://indexable-inc.github.io/index/cache-push-darwin-split</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/cache-push-darwin-split</guid>
      <pubDate>Sun, 19 Jul 2026 11:30:00 GMT</pubDate>
      <description>Cache pin advances every merge: the darwin lane moves to its own workflow. The fleet&apos;s cache-ready pin now advances minutes after every merge instead of once per ~2 hours: the native darwin cache lane (darwin-build and darwin-push) moved out of cache-push.yml into its own cache-push-darwin.yml with its own concurrency group. GitHub keeps one pending run per concurrency group, so each 2h+ hosted-mac run held the shared cache-push group while every merge behind it was superseded-cancelled: 24 consecutive cancelled runs between 08:51Z and 11:08Z on 2026-07-19, while the linux publish the pin actually waits on needs ~30 minutes worst case. The cache-ready-darwin gate changed shape with the move. The darwin workflow has no linux push job whose failed-roots output it can read, so the linux half of the publish-hole gate now reads the cache-ready ref itself: the pin only advances to a commit that cache-ready already contains, because the linux lane fast-forwards that ref exclusively over hole-free runs. Linux lag logs a notice and exits clean; the next darwin run catches up. All of this is interim plumbing: once the last darwin roots are linux-cross-built (#3586), the whole darwin workflow is dead code (#3587).</description>
    </item>
    <item>
      <title>claude-code-rainbow can no longer self-install over the wrapped claude</title>
      <link>https://indexable-inc.github.io/index/claude-rainbow-no-self-install</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/claude-rainbow-no-self-install</guid>
      <pubDate>Sun, 19 Jul 2026 10:30:00 GMT</pubDate>
      <description>claude-code-rainbow can no longer self-install over the wrapped claude. The rainbow POC shipped the raw byte-patched binary at bin/claude, without the DISABLE_UPDATES=1 guard the stock claude-code launch spec sets. Running it directly let Claude Code&apos;s self-installer go to work: with installMethod: native recorded in ~/.claude.json, it downloaded a stock native build into ~/.local/share/claude/versions and wrote a ~/.local/bin/claude launcher. On a machine whose PATH puts ~/.local/bin before ~/.nix-profile/bin, that launcher shadows the wrapped claude, so every new session silently starts the stock binary: no house MCP config (the ix-mcp-ex kernel), no system prompt, no injected flags. The package now installs the patched binary under libexec behind a makeBinaryWrapper shim that pins DISABLE_UPDATES=1, matching the stock package&apos;s guard, so running the rainbow build can never mutate the machine&apos;s claude resolution. The change also clears the lint debt in this package family that made any fresh-worktree commit fail: the byte-swap mappings are typed Nix rules passed to the patcher via passAsFile instead of committed JSON, the manifest import goes through ix.paths, and the unused lambda args in claude-code-debug and claude-code-rainbow-live are gone.</description>
    </item>
    <item>
      <title>The README opens with a spinning ASCII globe, generated by a Nix pipeline</title>
      <link>https://indexable-inc.github.io/index/readme-ascii-globe</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/readme-ascii-globe</guid>
      <pubDate>Sun, 19 Jul 2026 08:30:00 GMT</pubDate>
      <description>The README opens with a spinning ASCII globe, generated by a Nix pipeline. The README hero is now a spinning globe (.github/readme/globe.svg): the whole world rendered as braille text, land in the foreground color, ocean muted, adapting to light and dark mode like every other README graphic. The flywheel diagram moves back to lead the Why section. The globe is not hand-drawn. packages/readme-globe is a Nix derivation that fetches a hash-pinned NASA Blue Marble equirectangular texture from Wikimedia Commons, thresholds it into a land mask, orthographically projects 96 rotation frames onto a braille dot grid (2x4 subdots per cell, so an 80x40 grid renders 160x160 effective dots) where Lambert shading survives as ordered-dither dot density, and emits one animated SVG whose frames cycle with CSS steps(). Regenerate with nix build .#readme-globe and copy the output over the committed file. Why SVG instead of a GIF or webp: text stays crisp at any scale, prefers-color-scheme works (raster formats pick one background), reduced-motion users get a static frame, and 96 frames of braille compress to a fraction of any raster animation.</description>
    </item>
    <item>
      <title>stock Nix now gets an install message instead of a parse error, and the bootstrap seam is enforced</title>
      <link>https://indexable-inc.github.io/index/evaluator-gate-stock-parse</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/evaluator-gate-stock-parse</guid>
      <pubDate>Sun, 19 Jul 2026 08:05:06 GMT</pubDate>
      <description>stock Nix now gets an install message instead of a parse error, and the bootstrap seam is enforced. The repo pins its own Nix evaluator: the fork source lives in-tree (lib/fork-packages.nix, built as nix-ix) and carries language patches such as underscore digit separators (25_576). tests/default.nix already uses that syntax, so anyone evaluating a tests-derived check with stock upstream Nix got a raw syntax error, unexpected invalid token with no hint about the fix. Two additions make the boundary explicit: - ix.evaluatorGate.require wraps the import of each fork-syntax island (today: tests/). Syntax errors happen at parse time, before any version check can run, so the gate sits at the import seam and stays frozen-syntax itself, the same trick as nixpkgs&apos; lib/minver.nix. Stock Nix now gets an actionable error ending in the one command that fixes it: nix profile install github:indexable-inc/index#nix-ix. - checks.&lt;system&gt;.stock-nix-parse runs the nixpkgs-pinned stock nix-instantiate --parse over every tracked .nix outside tests/. External flakes evaluate index with their own evaluator, and the nix-ix bootstrap only works while its import closure parses on stock Nix; CI runs the fork, so nothing else would notice a stray fork-only literal on the shared surface. The check turns &quot;stock Nix can always build the fork&quot; from luck into law. The gate detects the fork through the +ix. suffix in builtins.nixVersion rather than a fork-added builtin: a new builtin would reject every currently deployed evaluator until a nix upgrade reaches the fleet.</description>
    </item>
    <item>
      <title>nom on Macs: cross-compiled from Linux by a patched cross-GHC</title>
      <link>https://indexable-inc.github.io/index/nom-cross-ghc</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/nom-cross-ghc</guid>
      <pubDate>Sun, 19 Jul 2026 06:30:00 GMT</pubDate>
      <description>nom on Macs: cross-compiled from Linux by a patched cross-GHC. packages.aarch64-darwin.nix-output-monitor is now a Linux-cross-built artifact: a Mac substitutes a prebuilt Mach-O arm64 nom from the cache instead of building GHC and the 62-package Haskell closure natively. The verdict in #3584 (&quot;Haskell cross to darwin is infeasible&quot;) only condemned nixpkgs&apos; pkgsCross road, which dies bootstrapping the Apple SDK; building GHC itself as a cross compiler the way upstream&apos;s wasm and mingw lanes do -- ./configure --target=aarch64-apple-darwin + hadrian, with the RFC 0009 clang/ld64.lld/SDK toolchain as target tools -- works. It took three small GHC patches (two host-vs-target CPP confusions in the aarch64 NCG and x18 register reservation, one Cabal osx spelling case in GHC_CONVERT_OS) and a bundled-libffi bump to 3.5.2 for a Mach-O CFI atom rule. Template Haskell splice execution is impossible in this lane (no darwin iserv on Linux); an audit showed nom&apos;s closure runs zero splices, so nothing had to be faked. A cross-darwin-nom-smoke gate asserts the output stays Mach-O arm64 with the by-name alias symlinks intact. Written by an AI agent via Claude Code (Claude Fable 5).</description>
    </item>
    <item>
      <title>writing-style skill learns the AI tells</title>
      <link>https://indexable-inc.github.io/index/writing-style-ai-tells</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/writing-style-ai-tells</guid>
      <pubDate>Sun, 19 Jul 2026 06:00:00 GMT</pubDate>
      <description>writing-style skill learns the AI tells. The agent&apos;s writing-style skill now carries a catalog of the patterns readers use to spot AI prose, with an instruction to never produce them: the rule of three, &quot;not X, but Y&quot; parallelisms, em dashes, the delve/robust/seamless lexicon, &quot;serves as&quot; where the sentence means &quot;is&quot;, synonym cycling, significance inflation (&quot;plays a vital role&quot;), hedging filler (&quot;it&apos;s worth noting&quot;), assistant chirp, bolded-phrase bullets, and closing summaries. The catalog is drawn from Wikipedia&apos;s Signs of AI writing field guide and the vocabulary studies it cites (Juzek and Ward on RLHF word overuse, Kobak on academic abstracts). The skill also ships a paste-ready prompt block for generating or reviewing prose, so the same bans apply when the agent writes for other contexts. The README intro was rewritten under the new rules and repositioned: it now leads with what index is (one shared software universe, nixpkgs-style) and its relation to ix.dev (ix is the runtime, index is the world it runs), after a reader mistook the old intro for a per-team agent product.</description>
    </item>
    <item>
      <title>Patched git: linked worktrees stop re-cloning submodules</title>
      <link>https://indexable-inc.github.io/index/git-worktree-submodule-alternates</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/git-worktree-submodule-alternates</guid>
      <pubDate>Sun, 19 Jul 2026 05:30:00 GMT</pubDate>
      <description>Patched git: linked worktrees stop re-cloning submodules. git joins the de-forked packages (lib/fork-packages.nix): upstream v2.54.0 plus one patch that fixes a twelve-year-old gap in git&apos;s worktree support. Stock git resolves submodule gitdirs to a per-worktree path, so the first git submodule update --init in every new worktree re-clones each submodule from its remote even though the main checkout already holds a complete object store under .git/modules/. Upstream called the reuse &quot;logical&quot; in the 2014 commit that shipped the split (df56607dff2) and never implemented it. The patch teaches prepare_possible_alternates() to add $GIT_COMMON_DIR/modules/&lt;name&gt;/objects as a clone reference with --reference-if-able semantics whenever the resolved gitdir is per-worktree. Immutable objects are shared through alternates; per-checkout state (HEAD, index) stays private. Measured on a 5 MB submodule: a new worktree&apos;s submodule init drops from 5.0 MB of duplicated store plus a full network refetch to 112 KB and no fetch, and it scales linearly with submodule size and count. With the worktree-per-task agent workflow this removes a full submodule re-download from every task start in superprojects that use submodules. The base is pinned by rev to nixpkgs&apos; git version tag (autoUpdate = false) because the package overlays the nixpkgs recipe onto the patched tree; an eval-time assert fails loudly when nixpkgs&apos; git moves so the pin gets advanced deliberately. Upstream policy is recorded as mailing-list-only (prsWelcome = false): git/git on GitHub is a read-only mirror, so upstreaming this series is a manual git send-email submission, held for now.</description>
    </item>
    <item>
      <title>plumb: a shell where every run is a value</title>
      <link>https://indexable-inc.github.io/index/plumb-shell</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/plumb-shell</guid>
      <pubDate>Sun, 19 Jul 2026 02:45:00 GMT</pubDate>
      <description>plumb: a shell where every run is a value. packages/plumb is a bash-subset shell built library-first for LLM agents. Run cargo clippy 2&gt;&amp;1 | tail -n 5 and the run comes back as a value: per-stage argv, status, timing, and bounded captures of every stream, including what each pipe stage fed the next. Every stream stays addressable afterwards (${o[1][0]} is everything tail consumed, negative indexes count from the latest), so a later command can reuse any earlier stream without re-running it. Three crates: plumb-syntax (spanned parser; unsupported bash is a loud error, never a misparse), plumb-core (cloneable Shell over shared state, tee&apos;d capture engine with backpressure, strict semantics: pipefail, unset-var error, failglob, no word splitting), and the plumb CLI (reedline REPL with per-stage summaries and :runs / :json / :out inspection, -c, script and stdin modes, --json). A public read-only mirror is generated at indexable-inc/plumb. The Elixir binding (plumb-ex, via unibind) exposes Plumb.Shell.eval/2 returning the report as JSON, with error variants as atoms and detached runs pollable by id, so the workstation kernel can drive shells in-process. Two boundary gotchas surfaced and are now recorded: the BEAM ignores SIGCHLD (auto-reaping NIF-spawned children into ECHILD; plumb-ex restores the default disposition once), and cargo check on any unibind ex crate false-fails on a duplicate nif_init.</description>
    </item>
    <item>
      <title>The site renders on a TUI character grid and ships as @indexable/site</title>
      <link>https://indexable-inc.github.io/index/site-tui-grid-library</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/site-tui-grid-library</guid>
      <pubDate>Sat, 18 Jul 2026 21:35:00 GMT</pubDate>
      <description>The site renders on a TUI character grid and ships as @indexable/site. This page now renders on a strict TUI character grid, matching the ix.dev design language: one 14px monospace cell with an exact 21px line height, every vertical distance a whole multiple of that cell and every horizontal distance in ch units. There is exactly one font size; hierarchy comes from weight, UPPERCASE, letter-spacing, and inverse-video blocks. Panels draw as box frames with their titles inline in the top rule, chips read as [ label ], and the plan score meters render as block-character gauges. The filter bar on this page is now a Todoist-style search box. Words that match a known tag, or a prefix of one (rus for rust), highlight inline as subtle pills and AND together as tag filters; everything else full-text searches titles and bodies, never an error. A box-drawn autocomplete under the caret lists matching tags with entry counts (arrows to move, Tab or Enter to accept, Esc to close), and &amp;, |, ! survive as power syntax. A pixel QA pass tightened both surfaces: the autocomplete wears the same crisp 1px frame as the focused search panel, and plan flow diagrams no longer clip their last column or push the page off the 21px grid. Wide DAGs now break out of the column, centered, and their scroll pane hides scrollbar chrome so its height stays a whole number of cells in every browser. The content also ships as a raw-source Svelte library, @indexable/site, built by nix build .#site-lib. It exports the updates, plans, stories, and philosophy collections with their loaders, the page-level components, the RSS feed builder (site URL as a parameter), and styles/tokens.css as the single source of design tokens. Each themeable token is one light-dark() declaration under color-scheme: light dark, so a color is defined once rather than in a :root block shadowed by a @media (prefers-color-scheme: dark) override. ix.dev composes its site from this package, so the same entries and components serve both deployments; this site&apos;s routes are thin wrappers over the same sources.</description>
    </item>
    <item>
      <title>Every commit now references the site page that explains it</title>
      <link>https://indexable-inc.github.io/index/commit-site-page-links</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/commit-site-page-links</guid>
      <pubDate>Sat, 18 Jul 2026 19:00:00 GMT</pubDate>
      <description>Every commit now references the site page that explains it. Commit subjects alone rarely explain a change, and long commit bodies are hard to find again. Commits in this repo now point at the site instead: every commit message must include a repo path to a page under packages/site/src/lib/updates/, plans/, or stories/, and the long-form description lives on that page. A new .githooks/commit-msg hook enforces this through the same core.hooksPath activation as the existing pre-commit lint hook. The referenced page must exist in the commit&apos;s staged tree, so adding the page in the same commit satisfies the check. Merge, fixup, squash, and revert commits are exempt because they carry their own context. For now the reference is a plain repo path, not an https link, and there is no dedicated commits section on the site; a commit either adds an update entry like this one or points at the existing page it extends.</description>
    </item>
    <item>
      <title>README leads with the thesis: writing code got cheap, landing it is the bottleneck</title>
      <link>https://indexable-inc.github.io/index/readme-thesis</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/readme-thesis</guid>
      <pubDate>Sat, 18 Jul 2026 19:00:00 GMT</pubDate>
      <description>README leads with the thesis: writing code got cheap, landing it is the bottleneck. The README now opens with why the repo exists instead of what it contains. Agents made writing code cheap; shipping it is still expensive (review queues, maintainer bus factors, projects that refuse AI-written patches), so the bottleneck moved from writing a change to landing it. index is the bet on the other path: every change lands here now, in one graph everyone consumes the same way, and upstream can take it someday. A new animated hero (.github/readme/unblocked.svg) draws the two paths in the house terminal style: the upstream lane queues blocks in front of a review gate, the index lane streams them through main and fans them out to every consumer. All wires are axis-aligned so the diagram reads as a text grid, matching the other README graphics.</description>
    </item>
    <item>
      <title>The prompt rule that survived its own opt-out: omitRules shadowed by an explicit package</title>
      <link>https://indexable-inc.github.io/index/prompt-omit-shadowing-and-kernel-hardening</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/prompt-omit-shadowing-and-kernel-hardening</guid>
      <pubDate>Sat, 18 Jul 2026 04:49:59 GMT</pubDate>
      <description>The prompt rule that survived its own opt-out: omitRules shadowed by an explicit package. A workstation Claude session was still carrying the force-merge prohibition its owner had opted out of. The opt-out was configured correctly: programs.claude-code.systemPrompt.omitRules listed the rule. But the module folded omitRules only into its derived default package, via cfg.basePackage.override, so an explicit package = silently discarded the whole omission list. Permissions said one thing; the rendered system prompt said another (#3537). #3545 fixes it by threading omitRules into the override arguments, and adds a loud eval assertion so the shadowing can never be silent again: it checks options.&lt;ns&gt;.package.highestPrio against (lib.mkDefault null).priority and fails evaluation when omissions are configured on an explicitly set package the module cannot rewrite. The general lesson, that an option folded only into a derived default dies quietly the moment someone sets the option it derives, is now a shared prompt rule, moduleOptionShadowing (#3552). The same debugging session surfaced two kernel bugs in the Elixir MCP server. Binary bytes in a cell&apos;s output killed the MCP connection outright and orphaned the job record (#3538); #3542 sanitizes output to valid UTF-8 at the boundary, makes the transport reply-always so a cell can no longer die without answering, and monitors job history so finished jobs always land a record. And a schema-drifted actions.db crashed the whole server at startup (#3539); #3543 adds PRAGMA user_version migrations and routes crash dumps into the state dir instead of the current working directory. #3550 rounds it out: after a checks-only watcher idled for 35 minutes on a PR that had already merged, the kernel instructions now bake in the two-signal pattern, PrWatch for merge state plus gh pr checks --watch --fail-fast for CI, so neither signal alone can strand a watch. Deployed and verified on the workstation: the rendered system prompt file now drops both stale rules and carries the new one. Written by Claude Code, an AI coding agent.</description>
    </item>
    <item>
      <title>A raw NVMe partition loses to a plain APFS image file for the builder VM</title>
      <link>https://indexable-inc.github.io/index/vm-disk-apfs-bypass</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/vm-disk-apfs-bypass</guid>
      <pubDate>Sat, 18 Jul 2026 04:23:36 GMT</pubDate>
      <description>A raw NVMe partition loses to a plain APFS image file for the builder VM. The aarch64 NixOS builder VM on hydra (a Mac Studio) does all of its disk I/O through an image file sitting on APFS, so every guest write crosses vfkit, the host page cache, and a copy-on-write filesystem. Virtualization.framework can instead attach a raw block device via VZDiskBlockDeviceStorageDeviceAttachment (vfkit type=dev), skipping APFS entirely. The question: does a dedicated raw internal-NVMe partition beat the image file for nix-builder workloads? The A/B ran on hydra&apos;s builder VM under vfkit 0.6.3, a GPT/repart appliance image with an xfs root. A 256 GiB partition was carved out of the live internal NVMe with diskutil apfs resizeContainer and the image was dd&apos;d onto /dev/rdisk0s5 at 5.9 GB/s. In-guest benchmarks: fio 4k randwrite QD8 O_DIRECT, plain and with fsync=1, plus a cold nix copy of the 3.3 GiB ghc closure. | backing | randwrite IOPS | fsync IOPS | fsync p99 | nix copy | | --------------------------- | -------------- | ---------- | --------- | ---------- | | image file (sparse, noatime) | 34,123 | 14,651 | 1.7 ms | 131.6 MiB/s | | partition, sync Full | 12,048 | 1,024 | 16.7 ms | 116.0 MiB/s | | partition, raw node | 14,581 | 524 | 32.4 ms | 70.8 MiB/s | File backing wins every axis on this stack. The builder stays file-backed, and the partition machinery landed dormant behind a UUID-pinned spec knob (nix#143). The mechanism is the synchronization mode, not APFS. VZ block-device attachments offer only synchronizationMode Full or None: Full turns every guest write barrier into F_FULLFSYNC on the physical disk, 13 to 28x slower on registration-shaped fsync paths, while file-backed attachments get the host page cache plus plain-fsync semantics. Next to that, APFS overhead is noise. Two side findings along the way: vfkit rejects macOS raw /dev/rdiskN character nodes for type=dev (an unaligned header read plus an S_IFBLK stat check; a 2-line patch boots fine off rdisk), and VZ propagates the internal SSD&apos;s 4Kn sector geometry into the guest, so images need sectorSize 4096. Prior art is thin: VZDiskBlockDeviceStorageDeviceAttachment shipped in macOS 14, lima tracks the same idea in lima-vm/lima#4866, and vfkit exposes it as type=dev. The open cell in the matrix is partition plus sync None, which the vmkit work in index#3554 makes testable. Written by an AI agent via Claude Code (Claude Opus 4.5).</description>
    </item>
    <item>
      <title>vmkit attaches raw host block devices as VM disks, bypassing APFS</title>
      <link>https://indexable-inc.github.io/index/vmkit-raw-block-devices</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/vmkit-raw-block-devices</guid>
      <pubDate>Sat, 18 Jul 2026 02:10:00 GMT</pubDate>
      <description>vmkit attaches raw host block devices as VM disks, bypassing APFS. vmkit&apos;s Virtualization.framework guests can now use a real disk instead of an image file: any --disk on boot-linux-gui / drive-linux (and a macOS bundle&apos;s disk.img symlinked at a device node) may name /dev/diskN[sM] or /dev/rdiskN[sM], attached via VZDiskBlockDeviceStorageDeviceAttachment (macOS 14+). Guest I/O goes straight to the device, skipping APFS sparse-file fragmentation, COW metadata, and host-side double caching — the costs persistent high-churn guests pay most. Safety first: a device that hosts mounted filesystems — including through APFS physical-store linkage, where the volumes mount under a synthesized container disk — is refused unless --force, and the guest-flush mode is an explicit --disk-sync full|none (default full). A root-owned /dev node is opened by a sudo&apos;d re-exec of vmkit that hands the fd back over a private unix socket, so the VM never runs as root and no sudoers entry is ever installed (the pitfall that got lima&apos;s equivalent feature reverted). The libkrun backend (boot-linux) rejects device paths loudly; it only takes image files.</description>
    </item>
    <item>
      <title>Weave agent memory at scale: all 23 plan tracks executed in one day</title>
      <link>https://indexable-inc.github.io/index/weave-agent-memory-executed</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/weave-agent-memory-executed</guid>
      <pubDate>Sat, 18 Jul 2026 00:11:27 GMT</pubDate>
      <description>Weave agent memory at scale: all 23 plan tracks executed in one day. weave#281 laid out the agent memory plan in docs/weave-agent-memory.html: 23 sub-issues (#284 to #306) spanning storage, query, claims, recall, client surface, and evals. All 23 landed on 2026-07-17, executed by a fleet of parallel AI agents working the tracks concurrently. The measured baseline that motivated the work (linked above) is stark. Cold start scaled roughly x4.4 per x2 facts (40 s at 125k facts, 150 s at 250k, 693 s at 500k, 2484 s at 1m), watch deltas tracked cold start almost 1:1 because every tick re-derived state from the journal, resident memory ran about 470x the journal bytes (18.6 GB RSS over a 39.8 MB journal at 500k facts), and a 1e9 fact ingest was OOM-killed near 4e8 facts with the journal fully resident. The 10m and 100m rungs could not be measured at all under a 2 hour budget. What landed, by track: - Storage and query: EAV/AEV/AVE covering indexes on fjall (weave#315), a tiered T0/T1 query planner with demand transformation (weave#321), incremental view maintenance for watches (weave#329), as-of checkpoints (weave#289), and journal compaction with cold-segment archival (weave#312). - Claims and provenance: claim and observation schema facts plus the epistemic prelude rules (weave#313), provenance envelope stamping at the write path (weave#322), a basis DAG with derived staleness (weave#320), and trust-as-lens example RulePacks (weave#324). - Recall: an embedding sidecar ANN index (weave#314), ANN entry points feeding Datalog graph expansion (weave#323), and auto-surfaced hints of roughly 40 tokens on tool results (weave#331). - Client surface: thin Rust, Python, and TypeScript clients (weave#317), a headless lean server build (weave#310), and weave.claim and weave.recall client verbs (weave#319). - Evals and gates: a nix-built eval sandbox (weave#328), memory-use eval suites on local models (weave#334), and enforced scale perf gates in CI (weave#332). Written by Claude Code, an AI coding agent.</description>
    </item>
    <item>
      <title>Fleet SSH sessions are now reaped at logout</title>
      <link>https://indexable-inc.github.io/index/fleet-logind-reap-on-logout</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/fleet-logind-reap-on-logout</guid>
      <pubDate>Fri, 17 Jul 2026 15:51:34 GMT</pubDate>
      <description>Fleet SSH sessions are now reaped at logout. Three orphaned root du diagnostic probes ground hil-stor-2 at roughly 58% full IO pressure (PSI) for more than 30 hours. Killing them dropped the pressure to 5% within seconds (ix#7615). The probes had been launched over SSH long before, and nothing ever cleaned them up. The root cause is a pair of defaults. sshd does not reap a command&apos;s children after the connection drops, and logind&apos;s default policy leaves session processes running forever; root is even exempt from KillUserProcesses out of the box. Any process forked from an SSH session could outlive it indefinitely, invisibly. ix#7616 changes that on every fleet host: KillUserProcesses=true with KillExcludeUsers= cleared, so everything still inside a session scope dies at logout. This is a behavior change for everyone. nohup, tmux, and ssh host &quot;cmd &amp;&quot; no longer survive disconnect. Intentional survivors must escape the session scope explicitly: systemd-run --unit=&lt;name&gt; --property=RuntimeMaxSec=&lt;s&gt; as root, or loginctl enable-linger plus systemd-run --user for regular users. The same PR removed weave and weave-pack from prod hosts; they are not stable enough yet, and the modules stay in the tree for a later re-enable.</description>
    </item>
    <item>
      <title>The green CI that wasn&apos;t: an embed typecheck hidden behind a truncated log</title>
      <link>https://indexable-inc.github.io/index/mcp-embed-typecheck-hunt</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/mcp-embed-typecheck-hunt</guid>
      <pubDate>Thu, 16 Jul 2026 08:51:49 GMT</pubDate>
      <description>The green CI that wasn&apos;t: an embed typecheck hidden behind a truncated log. Bundling in-process code embeddings into the MCP server (torch/MPS, #3417) should have been a clean package addition. Instead PR #3427 sat red on the required flake-check gate for two sessions, behind three separate red herrings. The durable lesson is not about embeddings: it is that a &quot;mystery&quot; CI failure is usually a real error you have not read yet. ## Symptom flake-check failed on #3427 with no obvious cause. The GitHub Actions step log ended mid-stream in a nix-fast-build progress dump, with no error line at the tail. Three plausible stories competed for the blame. ## The three red herrings - Billing lock. During a GitHub org billing-lock window the required gate had genuinely returned false-green: jobs failed in 2 to 3 seconds with empty steps. Real, but already over by the time #3427 was failing in hundreds of seconds with real work in the log. - Validation clock. The embed deps balloon the base-image OCI closure, and the ci-budget worker enforces a 300s routine deadline. Adding the ci/big-change label (which raises the ceiling to 3h and, via the labeled trigger, starts a fresh run) was the obvious fix. The re-run then failed at 261s, under 300s, disproving the clock theory outright. - The red-merge chain. A sibling commit 0a394ab7 had landed a naive digit-underscore text pass that mangled identifiers (isx86_64 to isx8664) during the same billing window. That broke eval on main, but it was a parallel fire, not #3427&apos;s failure. Each was a coherent narrative. None was the cause. ## Root cause The full error only existed in the runner-side log, not the GitHub step log. index CI writes complete stdout per test-merge SHA at /var/log/ix-ci/runs/&lt;merge_sha&gt;/Check/&lt;runid&gt;-&lt;attempt&gt;.stdout on whichever fleet host ran the ephemeral runner (runners are distributed: run 1 on vin-compute-2, run 2 on hil-compute-3). The GitHub step log had truncated the nix-fast-build failure mid-stream; the runner log carried it in full: embed&apos;s inference runtime (torch, sentence-transformers, huggingface_hub) is darwin-only (MPS). On x86_64-linux, which is exactly what the required gate builds, those modules are genuinely absent, so the strict typecheck fails to resolve the imports. The module was already written correctly for runtime: lazy imports inside try/except ImportError, a TYPE_CHECKING guard, a typed EmbedError. The failure was purely the type checker resolving the imports on a platform where they do not exist. ## The zuban trap The intuitive fix was per-module config in the zuban ini: It did nothing. zuban (0.8.0, mypy-compatible) in zuban check default mode (PyRight-like) does not honor per-module ignore_missing_imports sections. Neither does zuban mypy mode nor a TOML [[tool.mypy.overrides]] block in this setup. Only the global [mypy] ignore_missing_imports = true takes effect, and that is far too broad: it guts strict checking for the whole package. What zuban does honor is an inline suppression on the import line: zuban also does not flag an unused # type: ignore, so the same line is harmless on darwin where the module resolves. The fix was three inline ignores plus deleting the dead per-module config that never worked. ## Verification Guessing was the whole problem, so the fix was validated against the real artifact: the actual x86_64-linux derivation, built on the fleet builder vc1-nix: #3427 then merged on green. The base-image closure growth that first looked like a clock problem is real and tracked separately in #3448: a static costly_paths list cannot detect closure growth from a heavy dependency, so big_change was not auto-detected. ## What to carry forward - A truncated CI log is not a mysterious failure. Read the runner-side log at /var/log/ix-ci/runs/&lt;merge_sha&gt;/Check/ on the fleet host that ran it. - zuban check ignores per-module ignore_missing_imports. For a genuinely platform-only optional import, suppress inline with # type: ignore[import-not-found], not config. - Validate a typecheck fix by building the real target-platform derivation, not by trusting a darwin-local pass. - Three coherent narratives competed for the blame and all three were wrong. The evidence that settled it was one log file no one had opened. Written by Claude Code, an AI coding agent.</description>
    </item>
    <item>
      <title>Postmortem: the codex darwin cross closure that merged green and broke every switch</title>
      <link>https://indexable-inc.github.io/index/codex-cross-closure-postmortem</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/codex-cross-closure-postmortem</guid>
      <pubDate>Tue, 14 Jul 2026 07:55:00 GMT</pubDate>
      <description>Postmortem: the codex darwin cross closure that merged green and broke every switch. PR #2690 put codex-rs on the cargo-unit cross DAG while lib/darwin/apple-sdk-toolchain.nix exported unsuffixed darwin CFLAGS, cc-rs concatenates flags for every target, and linux gcc build-script units died on -iframework. The drv hashed identically at every flake pin, so the cache hole was permanent, and every darwin home-manager switch broke while CI stayed green: flake-check only evaluates, and the post-merge cache push recorded the failed roots yet concluded success. Three fixes, one per lying layer: the apple toolchain env is now target-suffixed only (#3197), a cache-push run that leaves publish holes fails before the consumer pin moves (#3200), and a required pre-merge closure-gate realises .#cachePushRoots.x86_64-linux so a closure that cannot build cannot merge (#3202). Full postmortem in the playbook. Written by an AI agent via Claude Code (Claude Opus 4.5).</description>
    </item>
    <item>
      <title>CVE scans now follow exact identities through the Nix runtime DAG</title>
      <link>https://indexable-inc.github.io/index/nix-runtime-cve-dag</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/nix-runtime-cve-dag</guid>
      <pubDate>Fri, 10 Jul 2026 02:05:00 GMT</pubDate>
      <description>CVE scans now follow exact identities through the Nix runtime DAG. The CVE gate now starts from typed shipped roots, realizes their /nix/store closures, and requires an evidenced PURL or canonical CPE before matching an advisory. It emits deterministic JSON, CycloneDX, SARIF, HTML, and summary artifacts with raw-source hashes, scanner provenance, root-scoped VEX, and exact dependency paths. Pull requests ratchet against the base revision so new actionable findings or identity debt block without hiding existing coverage work. Scheduled scans refresh OSV, NVD, CISA KEV, and EPSS, attest the evidence bundle, upload SARIF, and keep findings separate from scanner-health alerts. Written by Codex, an AI coding agent.</description>
    </item>
    <item>
      <title>HumanLayer remote daemon in an ix VM</title>
      <link>https://indexable-inc.github.io/index/humanlayer-daemon</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/humanlayer-daemon</guid>
      <pubDate>Thu, 18 Jun 2026 16:00:00 GMT</pubDate>
      <description>HumanLayer remote daemon in an ix VM. The humanlayer-daemon image boots an ix VM that runs the HumanLayer (riptide) remote daemon, so a guest can act as a remote HumanLayer host driving coding sessions. It builds on the dev base image, so the agent CLIs and build toolchain are already present. The daemon is the services.humanlayer NixOS module: a systemd unit that runs humanlayer daemon launch. The humanlayer package repackages the published @humanlayer/cli binary (pinned per-platform hashes in manifest.json, refreshed by nix run .#humanlayer.updateScript). Auth follows the runtime-secret idiom: mint a launch token on an authenticated host with humanlayer api auth daemon launch-token create (or copy the launch command from app.humanlayer.com) and drop it at /run/secrets/humanlayer/launch-token. The token is read at service start and is never baked into the image; the daemon exchanges it for its own credentials.</description>
    </item>
    <item>
      <title>fleet.spark() adds big-data SQL to the cluster, alongside Ray</title>
      <link>https://indexable-inc.github.io/index/fleet-spark</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/fleet-spark</guid>
      <pubDate>Sat, 13 Jun 2026 23:30:00 GMT</pubDate>
      <description>fleet.spark() adds big-data SQL to the cluster, alongside Ray. The fleet module now spans two complementary cluster engines: Ray for distributed Python (fleet.run/submit) and Spark for big-data SQL/DataFrames (fleet.spark) — handy for querying logs gathered across every node. fleet.spark() opens a SparkSession over Spark Connect (sc://&lt;master&gt;:15002): the client in the kernel is pure gRPC (no local JVM), heavy work runs on the cluster with the Gluten/Velox native engine, and results stream back as Arrow. The bundled client is pinned to the cluster&apos;s Spark 3.5 with its JVM jars stripped, so only the Connect path&apos;s deps ride along. Deploy with services.ix-spark (a Gluten-accelerated master + Spark Connect server, plus workers over Tailscale), mirroring services.ix-ray. The Ray cluster service was also renamed from services.ix-fleet to services.ix-ray and hardened (pinned inter-node ports, a short /run/ray socket dir, a mappable shared-memory object store).</description>
    </item>
    <item>
      <title>fleet turns the tailnet into one Ray cluster you can compute on</title>
      <link>https://indexable-inc.github.io/index/fleet-cluster</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/fleet-cluster</guid>
      <pubDate>Sat, 13 Jun 2026 20:30:00 GMT</pubDate>
      <description>fleet turns the tailnet into one Ray cluster you can compute on. The fleet module bundled in every ix-mcp kernel now sees and computes on the whole tailnet, not just the local machine. await fleet.nodes() returns one polars frame of every node, its tailscale address, and the Ray resources it advertises. Ship a Python callable to the cluster and the Ray object store carries the data: Ray (bundled in the interpreter) provides the distributed object store, peer-to-peer transfer between nodes, spill-to-disk under memory pressure, and true multi-process, multi-node parallelism, so a CPU-bound job is no longer stuck behind one kernel&apos;s GIL. fleet.in_kernel(node, code) runs a line in another node&apos;s live ix-mcp session (tailnet-gated) to read what it is actually doing, and the original fleet.scan still fans a shell command over SSH for nodes with no Python. Deploy with the services.ix-ray NixOS module: one node is the Ray head, the rest are workers on the tailnet. The trust boundary is the tailnet, the same one Ray&apos;s own data plane relies on, so in_kernel works across the fleet with no secret; set services.ix-ray.tokenFile to additionally require a shared IX_MCP_EXEC_TOKEN. An off-cluster box drives the cluster by pointing IX_FLEET_RAY_ADDRESS at ray://&lt;head&gt;:10001.</description>
    </item>
    <item>
      <title>gcal: Google Calendar as a CLI and as ix-mcp tools, over one Rust crate</title>
      <link>https://indexable-inc.github.io/index/google-calendar-gcal</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/google-calendar-gcal</guid>
      <pubDate>Fri, 05 Jun 2026 05:25:16 GMT</pubDate>
      <description>gcal: Google Calendar as a CLI and as ix-mcp tools, over one Rust crate. nix run .#gcal lists, shows, creates, and cancels Google Calendar events from the shell, and the ix-mcp server now exposes the same capability as calendar_events / calendar_event_create / calendar_event_cancel. Both surfaces are thin: a single google-calendar Rust crate owns the Calendar v3 client, the wire types, and the OAuth flow, the contract RFC 0003 asks of new integrations. Auth is per person and broker-free: a team OAuth client from rbw, one gcal auth consent in a browser (PKCE, loopback redirect, with a paste-the-URL path for SSH/VM sessions), and an offline refresh token in ~/.config/gcal/token.json. Creating or cancelling an event emails attendees by default, matching the Calendar UI; pass --notify none while experimenting.</description>
    </item>
    <item>
      <title>Dashboard shows every Python run and replays the whole session</title>
      <link>https://indexable-inc.github.io/index/dashboard-exec-panes-replay</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/dashboard-exec-panes-replay</guid>
      <pubDate>Tue, 02 Jun 2026 16:00:00 GMT</pubDate>
      <description>Dashboard shows every Python run and replays the whole session. Every python_exec and python_eval the ix MCP runs now appears on the dashboard as its own card: a box of the run&apos;s captured stdout and stderr, with the source behind it (a show source toggle, or the focus view). The worker captures output at the file-descriptor level, so a spawned subprocess counts too: subprocess.run([&quot;echo&quot;, &quot;hi&quot;]) shows hi, not nothing. Moving the JSON-RPC channel off fd 1 in the same change fixes a latent bug where that subprocess output could corrupt the protocol stream. The resource model is now general. A view tells the hub its shape as a set of scalar fields and a set of named text fields, so a new kind (the typed exec view here, and future user-defined resources) is one projection rather than an edit to the reconcile loop. Terminals still render exactly as before; a user-defined resource still rides html or data with no aggregator change. Every card, of any kind, shows when it was created in human time, stamped once by the aggregator with no producer opt-in. The board is also a recording. The hub timestamps every change, the browser keeps the full history, and a timeline bar scrubs to any past moment, plays it back at 1×–8×, or follows the live tail like a livestream. The aggregator persists the recording to disk ($XDG_STATE_HOME/ix-dash/recordings), serves saved sessions at /recordings and /recording/&lt;id&gt;, and the share button copies a deep link to the exact moment. A focus button on any card opens that one resource fullscreen with the same time controls. Verified end to end with Playwright: live ticks, a scrub back into history, playback, and a jump back to live.</description>
    </item>
    <item>
      <title>Agent instructions load progressively as skills</title>
      <link>https://indexable-inc.github.io/index/agent-context-progressive</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/agent-context-progressive</guid>
      <pubDate>Sun, 31 May 2026 04:40:00 GMT</pubDate>
      <description>Agent instructions load progressively as skills. The contributor guide is no longer one always-on file. Each fragment under agent-context/sections/ declares a disclosure tier in its frontmatter: always fragments join a small core every session reads in full, and progressive fragments each become a Claude Code skill whose body loads on demand from its description. There are no committed AGENTS.md / CLAUDE.md files anymore; a SessionStart hook renders the core live and points .claude/skills at a generated link farm. The win is context budget: the always-on core dropped from ~48 KB to ~7 KB, and depth (Nix style, image conventions, Rust rules) loads only when the work calls for it. The size of the always-on tier is a build-time invariant. Marking too much always fails the build with a clear message instead of silently overflowing Claude Code&apos;s per-value context cap. Sibling repos consume index.lib.agentContext for the parsed sections, the asserted always-on document, and mkProgressiveSkills to merge generated section-skills into their own .claude/skills.</description>
    </item>
    <item>
      <title>tui dashboard follows the browser&apos;s light/dark scheme</title>
      <link>https://indexable-inc.github.io/index/tui-dashboard-color-scheme</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/tui-dashboard-color-scheme</guid>
      <pubDate>Sat, 30 May 2026 19:00:00 GMT</pubDate>
      <description>tui dashboard follows the browser&apos;s light/dark scheme. The tui dashboard now tracks the browser or OS color scheme. Each themeable token is a single light-dark(lightValue, darkValue) declaration under color-scheme: light dark, so the ghostty light and dark palettes live in one source of truth per token instead of a :root block shadowed by a @media (prefers-color-scheme: dark) override. A &lt;meta name=&quot;color-scheme&quot; content=&quot;light dark&quot;&gt; in &lt;head&gt; lets the user agent settle on the scheme before CSS parses. That tag is the actual fix for webviews that rendered the light theme even in dark mode: the CSS already flipped correctly under an emulated dark scheme, so the bug was the host not advertising its scheme early enough. Browsers without light-dark() (roughly 15% as of 2026) fall back to the classic dark override inside @supports not (color: light-dark(#000, #fff)). Verified with Playwright: emulated dark resolves --bg to #151515, light to #ececec.</description>
    </item>
    <item>
      <title>ix-mcp playwright instructions default to reusing a browser</title>
      <link>https://indexable-inc.github.io/index/ix-mcp-playwright-reuse</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/ix-mcp-playwright-reuse</guid>
      <pubDate>Sat, 30 May 2026 06:30:00 GMT</pubDate>
      <description>ix-mcp playwright instructions default to reusing a browser. The ix-mcp session instructions now tell the model to reuse a browser rather than launch a fresh Chromium every call. A new launch per turn wastes startup time and discards cookies and logins. Within one session the persistent event loop keeps a launched browser and its state alive, so the recipe launches once and keeps the handle in a module global: To share one browser across separate sessions, the instructions connect over CDP (Chromium only). The connect-vs-launch decision rides on a recorded endpoint file, not a swallowed exception: the first session launches a persistent Chromium on a fixed --remote-debugging-port, records the endpoint, and later sessions read it and connect_over_cdp to the running browser via browser.contexts[0]. A stale endpoint raises on connect rather than silently relaunching, so the failure is visible.</description>
    </item>
    <item>
      <title>ix-mcp Python sessions ship playwright with browsers wired</title>
      <link>https://indexable-inc.github.io/index/ix-mcp-playwright</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/ix-mcp-playwright</guid>
      <pubDate>Sat, 30 May 2026 05:55:00 GMT</pubDate>
      <description>ix-mcp Python sessions ship playwright with browsers wired. playwright is now preinstalled in every ix-mcp Python session, alongside numpy, polars, matplotlib, and asyncssh. The wrapper sets PLAYWRIGHT_BROWSERS_PATH to the playwright-driver browser bundle that matches the patched node driver, so a browser launches with no playwright install step and no network download. Use the async API so it shares the session event loop: Chromium and Firefox are the reliable engines under Nix; WebKit is known to be flaky. The browser bundle is a few hundred MB, so it adds real closure weight to the MCP package.</description>
    </item>
    <item>
      <title>elevenlabs-say speaks a pipe as it streams</title>
      <link>https://indexable-inc.github.io/index/elevenlabs-say-streaming-input</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/elevenlabs-say-streaming-input</guid>
      <pubDate>Sat, 30 May 2026 04:00:00 GMT</pubDate>
      <description>elevenlabs-say speaks a pipe as it streams. elevenlabs-say now sits at the end of a pipe whose producer emits text over time and speaks it as it arrives. Piped stdin streams by default, no flag needed: Piped input goes over the ElevenLabs WebSocket input-streaming endpoint. The CLI reads stdin with os.read rather than line iteration, so a token stream that never emits a newline is still spoken instead of buffering until EOF, and a multi-byte character split across two reads is rejoined. Audio chunks pipe into ffplay as they arrive, so playback starts before synthesis finishes. A positional TEXT argument or --file stays on the batch convert endpoint, where the whole text is in hand and prosody is slightly better. Override either way with --stream or --no-stream: reach for --no-stream when piping a complete document and wanting the batch quality. It honors --voice, --model, --format, and --output (which writes the file as audio streams in). The default eleven_flash_v2_5 model is the low-latency choice and supports the endpoint; eleven_v3 does not.</description>
    </item>
    <item>
      <title>tui-dashboard aggregates many tui producers into one live web grid</title>
      <link>https://indexable-inc.github.io/index/tui-multiprocess-dashboard</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/tui-multiprocess-dashboard</guid>
      <pubDate>Fri, 29 May 2026 22:45:00 GMT</pubDate>
      <description>tui-dashboard aggregates many tui producers into one live web grid. Several processes can now share one tui web dashboard instead of each starting its own server. A producing process calls tui::publish (or tui.publish() in Python) to expose its terminals over a unix socket in a shared discovery directory, holding no HTTP or CRDT dependency of its own. Run the standalone aggregator once to watch all of them: It scans the discovery directory, connects to every producer socket, folds each producer&apos;s stream into one Loro document under its own scope, and serves the grid over Server-Sent Events. No producer owns the server and exactly one process binds a TCP port, so agents come and go behind a single stable URL; a producer&apos;s terminals leave the grid when it disconnects. The in-process tui::serve and the aggregator share one serve_hub, page, and SSE stream, so both render through the same code. The discovery directory resolves to $IX_TUI_DIR, then $XDG_RUNTIME_DIR/ix-tui, then /tmp/ix-tui-&lt;user&gt;, kept short for the macOS 104-byte socket-path limit.</description>
    </item>
    <item>
      <title>ix-mcp Python sessions run on one pinned interpreter</title>
      <link>https://indexable-inc.github.io/index/ix-mcp-pinned-interpreter</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/ix-mcp-pinned-interpreter</guid>
      <pubDate>Fri, 29 May 2026 21:05:00 GMT</pubDate>
      <description>ix-mcp Python sessions run on one pinned interpreter. python_session_create no longer takes a command, and the CLI drops --python. Every session runs on the interpreter the server pins (IX_MCP_PYTHON, set by the Nix wrapper to a fixed python3), inside its own writable venv so pip install still works. Bring-your-own interpreter sounded flexible but bought little. It made each session depend on the host&apos;s ambient Python, and its failure modes (missing interpreter, wrong version, no pip) all read as &quot;your environment is weird.&quot; One pinned interpreter makes every session reproducible and deletes that whole class of failure. cwd stays, so a session can still root itself in a project directory: What you give up: a session can&apos;t use a project&apos;s own resolved dependencies (a uv or poetry env). If that need comes back, the right shape is a typed &quot;project env&quot; door: the caller names a project and the server resolves the interpreter, no raw command vector.</description>
    </item>
    <item>
      <title>ix-tui drops its sync methods for one async surface</title>
      <link>https://indexable-inc.github.io/index/tui-py-async-only</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/tui-py-async-only</guid>
      <pubDate>Fri, 29 May 2026 21:00:00 GMT</pubDate>
      <description>ix-tui drops its sync methods for one async surface. The ix-tui Python bindings are now async-only. Every I/O method is a coroutine with the plain name, no a-prefixed twin and no synchronous variant: Before, each method shipped a blocking version plus an a-prefixed coroutine (enter / aenter, wait_for / await_for, kill / akill). Carrying both doubled the surface for one behavior, and the prefix only existed to dodge the name clash. With the sync twins gone the coroutines take the real names. The dashboard follows the same rule: await serve(...) and await dash.stop(). This is a breaking change. Replace with Tui(...) with async with Tui(...), drop the a/await_ prefixes, and await the calls. Construction and the pure accessors (id, command, size, is_alive, exit_code, Tui.list_all()) stay synchronous, since they read cached state and never touch the PTY. Coroutines bridge real Rust futures into asyncio through pyo3-async-runtimes with no thread-pool hop, so they run on whatever loop is already driving the caller. That includes the ix-mcp Python session, whose single persistent loop now lets you await t.enter(...) straight inside a python_exec call.</description>
    </item>
    <item>
      <title>ix-mcp Python sessions accept top-level await</title>
      <link>https://indexable-inc.github.io/index/ix-mcp-top-level-await</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/ix-mcp-top-level-await</guid>
      <pubDate>Fri, 29 May 2026 20:30:00 GMT</pubDate>
      <description>ix-mcp Python sessions accept top-level await. python_eval and python_exec now run await at the top level, no asyncio.run wrapper. Each session keeps one event loop for its whole life, so an async resource created in one call is still live in the next: This is the loop that asyncio.run cannot give you: it opens and closes a fresh loop per call, so a client or pool created on one turn is bound to a loop that is already closed by the next. The worker compiles every snippet with ast.PyCF_ALLOW_TOP_LEVEL_AWAIT and drives the resulting coroutine on the kept loop, the same mechanism as CPython&apos;s python -m asyncio REPL. Known limitation: a coroutine that never resolves blocks the worker, exactly the way a synchronous while True already does. Bound long awaits yourself with asyncio.wait_for when you need a ceiling.</description>
    </item>
    <item>
      <title>nix-web-monitor shows the whole build tree from the start</title>
      <link>https://indexable-inc.github.io/index/nix-web-monitor-full-tree</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/nix-web-monitor-full-tree</guid>
      <pubDate>Thu, 28 May 2026 22:45:00 GMT</pubDate>
      <description>nix-web-monitor shows the whole build tree from the start. The builds panel now seeds the entire tree the moment Nix prints its plan, so nix build .#thing shows thing at the root with every derivation nested under it before any leaf starts. Nodes appear as planned and light up as builds run, instead of the tree growing bottom-up from whatever happens to be building. Two changes make this work. The parser reads Nix&apos;s &quot;these N derivations will be built:&quot; announcement and seeds one planned row per derivation up front. The edge resolver queries nix-store --query --requisites for each derivation&apos;s full transitive .drv closure rather than only its direct references, so the reduced DAG bridges through cached intermediates: a dependency several hops down still nests under its target even when the derivations between them were substituted and never built. This supersedes the earlier behaviour, where a link bridged entirely through a cached derivation was dropped from the tree.</description>
    </item>
    <item>
      <title>nix-web-monitor builds panel can nest derivations by dependency</title>
      <link>https://indexable-inc.github.io/index/nix-web-monitor-dependency-tree</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/nix-web-monitor-dependency-tree</guid>
      <pubDate>Thu, 28 May 2026 18:30:00 GMT</pubDate>
      <description>nix-web-monitor builds panel can nest derivations by dependency. The builds panel now has a flat / tree toggle. Tree mode nests each built derivation under the derivation that depends on it, so you can read which build is waiting on which instead of scanning a flat list. Nix&apos;s internal-json log stream names every derivation it builds but carries no edges between them, so the monitor learns the structure out of band: for each built derivation it queries nix-store --query --references for the direct input .drv paths, keeps the edges between derivations that actually built this run, and transitively reduces them so a chain a → b → c never also draws the redundant a → c. A dependency bridged entirely through a cached, never-built derivation is intentionally absent from the tree, because the cached node has no build row to attach to. The flat view still lists every build with its host, phase, duration, and log count.</description>
    </item>
    <item>
      <title>file-search ships a Tantivy-backed BM25 indexer with a CLI, library, and bench</title>
      <link>https://indexable-inc.github.io/index/file-search</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/file-search</guid>
      <pubDate>Wed, 27 May 2026 08:30:00 GMT</pubDate>
      <description>file-search ships a Tantivy-backed BM25 indexer with a CLI, library, and bench. file-search indexes a directory tree into a Tantivy index and serves BM25 ranked search over it. The CLI exposes index and search subcommands; the same surface is reachable through the file_search library for callers that want to embed the indexer directly. An in-memory EphemeralSearch variant uses Tantivy&apos;s RamDirectory to rerank a batch of texts without touching disk. The tokenizer lives in its own code-tokenizer crate so other consumers can reuse the same camelCase / snake_case / kebab-case split plus optional English stemming, with ANSI escape sequences stripped before tokenization. A tango-bench suite covers the ephemeral path so changes to the tokenizer or query plan show up as a regression instead of a vibe shift.</description>
    </item>
    <item>
      <title>cargo-unit shares clippy work across repo Rust packages</title>
      <link>https://indexable-inc.github.io/index/cargo-unit-clippy-sharing</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/cargo-unit-clippy-sharing</guid>
      <pubDate>Tue, 26 May 2026 11:22:46 GMT</pubDate>
      <description>cargo-unit shares clippy work across repo Rust packages. The Rust flake checks look busier than the builder really is: most package-level cargoClippy entries are labels for one shared workspace derivation. rust-dag-runner-cargoClippy, rust-mcp-cargoClippy, and the other repo-owned package checks resolve to the same ix-rust-workspace-cargo-clippy.drv. The derivation runs one Cargo command with one target directory: That means a shared dependency such as e in an a, b, c, d workspace rides Cargo&apos;s normal unit sharing inside the single clippy run. The generated unit graph has the same shape for package builds: a fixture where scope-alpha and scope-bravo both depend on itoa and ryu produces one itoa unit and one ryu unit for the exact compile context. Splitting clippy into per-package derivations would make the check list feel more literal, but it would also give each package a separate CARGO_TARGET_DIR and make shared dependencies more likely to rebuild. The useful invariant is the current one: many package labels can fail the same shared policy check, while Nix realizes the workspace clippy derivation once.</description>
    </item>
    <item>
      <title>CAS defaults need a cache plan before they can carry ix builds</title>
      <link>https://indexable-inc.github.io/index/cas-default-blocked</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/cas-default-blocked</guid>
      <pubDate>Tue, 26 May 2026 11:21:18 GMT</pubDate>
      <description>CAS defaults need a cache plan before they can carry ix builds. ix VM images already enable the ca-derivations experimental feature for in-VM Nix commands. The harder default is the nixpkgs switch, config.contentAddressedByDefault = true, which makes stdenv derivations emit floating content-addressed outputs by default. That switch is not ready to carry this repo&apos;s default checks. Against the current pinned nixpkgs, a dry run for a one-line stdenv derivation under contentAddressedByDefault reported 736 local derivations before the leaf package. A live smoke test started rebuilding bootstrap and stdenv objects immediately. The issue is cache shape. Normal nixpkgs substitutes do not satisfy the changed content-addressed stdenv graph, so nix run .#lint and CI would begin by seeding a separate package universe instead of testing the ix change. That cost belongs in a cache rollout, not in every contributor&apos;s first validation run. The useful near-term path is narrower. Keep ca-derivations enabled where ix controls the Nix config, then opt in leaf repo builders whose dependencies stay on the normal cache graph. A repo-wide default can become boring after CI publishes enough content-addressed stdenv closure to make the first check substitute again.</description>
    </item>
    <item>
      <title>ix.buildSvelteSite packages Svelte builds, previews, and dev servers</title>
      <link>https://indexable-inc.github.io/index/svelte-site-builder</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/svelte-site-builder</guid>
      <pubDate>Tue, 26 May 2026 10:50:00 GMT</pubDate>
      <description>ix.buildSvelteSite packages Svelte builds, previews, and dev servers. ix.buildSvelteSite is now the shared builder for the repo&apos;s Svelte/Vite surfaces: the docs site and resource monitor site. It owns the locked production build, a static preview command, and a checkout dev-server command from one Nix call. The build still runs from a filtered, tracked source closure. The preview command serves the immutable output with miniserve, so nix run .#site exercises the same files the package embeds. The dev command runs from the mutable checkout instead: That split is deliberate. Vite&apos;s dev server writes dependency installs, caches, and HMR state, while Nix builds should stay pure and reproducible. The -dev wrappers install node_modules when missing, pass Vite&apos;s --host and --port flags, and leave ignored files in the checkout where editors and file watchers expect them. For OrbStack and VM workflows, the builder stops at the command boundary. OrbStack already provides macOS-to-Linux bind mounts, port forwarding, and faster Linux-side volumes for heavier dependency trees. When an ix shell workspace needs ignored files copied after a tracked source sync, nix run .#ix-shell-sync-ignored -- &lt;vm&gt; &lt;path&gt; streams only the git-ignored paths into /work/ix.</description>
    </item>
    <item>
      <title>Reusable Codex AI review gate for any repo</title>
      <link>https://indexable-inc.github.io/index/ai-review-gate</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/ai-review-gate</guid>
      <pubDate>Tue, 26 May 2026 09:55:00 GMT</pubDate>
      <description>Reusable Codex AI review gate for any repo. indexable-inc/index/.github/workflows/ai-review-gate.yml@main is a workflow_call reviewer that any repo can mount as the default AI review on main. The current implementation runs openai/codex-action against a JSON schema, posts each finding as a GitHub review suggestion with a suggested_replacement, and reports an overall_correctness verdict that becomes the final gate. Same-repo PRs from regular users run the secret-backed reviewer. Fork and Dependabot PRs stay on a no-secret path until a trusted maintainer approves the current head SHA, so a forked PR cannot edit the prompt and reach the OpenAI key. The reviewer instruction files (AGENTS.md and CLAUDE.md) are restored from the trusted base commit before Codex runs, so a PR that rewrites them cannot bend the reviewer&apos;s rules. The structured output keeps each finding mechanically actionable: title, body, confidence_score, priority, code_location.{relative_file_path, line_range}, and a suggested_replacement GitHub renders as a one-click apply. AI_REVIEW_MODEL and AI_REVIEW_EFFORT are repo variables, with xhigh reasoning effort as the default. Direct users can require the ai review approved check. Reusable-workflow callers should require GitHub&apos;s called-job check name, such as ai-review-gate / ai review approved for the example above. The gate only passes when the reviewer returns a well-formed JSON document and votes patch is correct; missing or malformed output fails closed.</description>
    </item>
    <item>
      <title>ix-mcp serves a persistent Python session over MCP</title>
      <link>https://indexable-inc.github.io/index/ix-mcp-python</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/ix-mcp-python</guid>
      <pubDate>Tue, 26 May 2026 09:50:00 GMT</pubDate>
      <description>ix-mcp serves a persistent Python session over MCP. nix run .#mcp speaks MCP over stdio and exposes a Python interpreter as a small set of tools. Sessions live across calls, so an agent can load a dataset once with python_exec and ask python_eval questions against the cached globals on later turns. No notebook server, no kernel manager, just stdin / stdout. Each session boots its own writable venv under a temp dir on one pinned interpreter, so pip install works without mutating the read-only store python. The interpreter is fixed: there is no per-session interpreter choice. Pass cwd to root the session in a project directory: This pairs naturally with nix run .#run. After recording a slow build, the agent can ask the MCP session to load .ix/run/latest/lines.jsonl into polars and rank the longest gaps, without paging tens of MB of log through its own context window: The follow-up python_eval calls reuse slow, so the agent can ask several questions of the same dataframe instead of re-parsing the log each turn. python_reset clears the globals, python_session_close ends the worker. The same binary is also a normal CLI: nix run .#mcp -- repl opens an interactive Python session, nix run .#mcp -- exec &apos;&lt;source&gt;&apos; and nix run .#mcp -- eval &apos;&lt;expression&gt;&apos; run a one-shot through the same worker. The MCP tools and the CLI subcommands share one implementation, so what works at the shell works the same when an agent drives it.</description>
    </item>
    <item>
      <title>nix run .#run records command sessions</title>
      <link>https://indexable-inc.github.io/index/run-recorder</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/run-recorder</guid>
      <pubDate>Tue, 26 May 2026 09:49:00 GMT</pubDate>
      <description>nix run .#run records command sessions. nix run .#run -- &lt;command&gt; ... executes the command in a PTY, prints a bounded head/tail summary to the terminal, and writes the full live stream plus per-line timing under ./.ix/run/latest/. A second shell can tail -f .ix/run/latest/output.log while the original command is still running, useful for slow Nix builds and long test suites. ./.ix/run/latest/replay [divisor] reads timing.log and typescript through scriptreplay to play the session back, and session.cast is a valid asciinema v2 file. lines.jsonl carries one JSON object per completed output line with started_elapsed_ns, ended_elapsed_ns, delta_since_previous_line_ns, and byte_count. Polars reads it with no schema work and keeps the elapsed-time columns as integer nanoseconds, so ranking the lines that gapped longest before printing is one expression: The same shape works for any structured event stream you choose to record. nix build .#someBadAttr --log-format internal-json emits one @nix {...} line per phase, with start / stop pairs keyed on id: Record that through run, drop the @nix prefix, decode the rest, and join starts to stops to surface the slowest phases: pl.read_ndjson replaces the older pandas.read_json(path, lines=True) recipe in the README. The columnar layout keeps the elapsed-time columns as Int64 nanoseconds without coercion, and swapping to pl.scan_ndjson lets the same expression run as a streaming query when a build session grows past memory.</description>
    </item>
    <item>
      <title>ix.buildZigPackage builds build.zig.zon projects with a cached package store</title>
      <link>https://indexable-inc.github.io/index/zig-package-builder</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/zig-package-builder</guid>
      <pubDate>Tue, 26 May 2026 09:27:07 GMT</pubDate>
      <description>ix.buildZigPackage builds build.zig.zon projects with a cached package store. ix.buildZigPackage wraps a build.zig / build.zig.zon project as a normal Nix derivation. Remote build.zig.zon dependencies are materialized once through nixpkgs.zigPackages.&lt;minor&gt;.fetchDeps as a fixed-output derivation, then copied into a writable ZIG_GLOBAL_CACHE_DIR for the package build and every test step, so product builds stay offline and reproducible. Named Zig test steps are exposed as separate passthru.tests.&lt;name&gt; derivations rather than a single rolled-up step, so the flake check scheduler runs them in parallel and a failing case names the step that actually broke. The cache materialization is the one place an __impure updater is acceptable in this repo, because the result is converted to a checked hash-bearing artifact before any product build consumes it. The boundary lives next to the helper and is documented in AGENTS.md.</description>
    </item>
    <item>
      <title>nix run .#site previews the page locally</title>
      <link>https://indexable-inc.github.io/index/nix-run-site</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/nix-run-site</guid>
      <pubDate>Tue, 26 May 2026 08:22:16 GMT</pubDate>
      <description>nix run .#site previews the page locally. The site package now includes the deploy artifact and a small miniserve preview command, so nix build .#site emits the GitHub Pages tree and nix run .#site boots a preview at http://127.0.0.1:8080/index/. The wrapper serves the same SvelteKit build that Pages deploys. ix.buildSvelteSite passes miniserve a route prefix instead of rebuilding with a different BASE_PATH, so local preview exercises the production /index URL layout.</description>
    </item>
    <item>
      <title>cargo-unit caches Rust tests per #[test]</title>
      <link>https://indexable-inc.github.io/index/cargo-unit-per-test</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/cargo-unit-per-test</guid>
      <pubDate>Tue, 26 May 2026 07:19:45 GMT</pubDate>
      <description>cargo-unit caches Rust tests per #[test]. nix-cargo-unit used to build one derivation per test binary; a single flaky case re-ran every test in the file and lost Nix scheduler parallelism. The generated tests.&lt;binary&gt; attrset now exposes all (legacy whole-binary behavior) plus cases.&quot;mod::test_x&quot;, one runCommand per individual test, invoked with --exact. Case enumeration would have been N serial IFDs, one per binary, since Nix walks IFDs single-file. They are collapsed into one testManifestDrv that depends on every test binary and writes per-target .list and .ignored.list files. Touching any cases entry now triggers one workspace-wide build instead of paying N round-trips. The same arc covers doctests, scoped to root targets, and per-binary coverage reports in passthru.coverage.</description>
    </item>
    <item>
      <title>ix-dev-diagnose probes ix.dev reachability</title>
      <link>https://indexable-inc.github.io/index/ix-dev-diagnose</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/ix-dev-diagnose</guid>
      <pubDate>Mon, 25 May 2026 23:42:25 GMT</pubDate>
      <description>ix-dev-diagnose probes ix.dev reachability. nix run .#ix-dev-diagnose reaches https://ix.dev/ from the caller&apos;s network path, prints success or failure, and writes one JSON report capturing system resolver answers, per-address TCP and TLS results, parsed certificate issuers and fingerprints, native and Mozilla-root verification outcomes, response headers, and a bounded body sample. Defaults are sensible: the report lands at ix-dev-diagnose-&lt;host&gt;-&lt;ms&gt;.json in the current directory and the body sample is capped at 64 KiB. Pass --max-body-bytes, --output, --pretty, or --json to override. Intended for cases where the failing client sees different bytes than a working one: SEC_ERROR_UNKNOWN_ISSUER, captive portals, ISP interception, stale DNS, or CDN edge differences. Attach the report to a support ticket instead of describing the symptom.</description>
    </item>
    <item>
      <title>Self-hosted OpenTelemetry stack module</title>
      <link>https://indexable-inc.github.io/index/observability-stack</link>
      <guid isPermaLink="true">https://indexable-inc.github.io/index/observability-stack</guid>
      <pubDate>Sat, 23 May 2026 07:25:53 GMT</pubDate>
      <description>Self-hosted OpenTelemetry stack module. modules/services/observability now wires a complete self-hosted stack: an OpenTelemetry collector, Tempo for traces, Loki for logs, Mimir or Prometheus for metrics, and Grafana with a generated overview dashboard. The dashboard is defined in Nix through dashboards/lib.nix, so panels can be composed and reused instead of pasted as JSON blobs. An examples/observability/stack/ fleet shows the smallest viable deployment. The module is module-tested end-to-end through the existing tests/ harness.</description>
    </item>
  </channel>
</rss>
