Skip to content

style: use 2-space indentation for Rust - #6

Merged
srid merged 2 commits into
masterfrom
claude/youthful-rubin
Mar 20, 2026
Merged

style: use 2-space indentation for Rust#6
srid merged 2 commits into
masterfrom
claude/youthful-rubin

Conversation

@srid

@srid srid commented Mar 20, 2026

Copy link
Copy Markdown
Member

Summary

  • Add rustfmt.toml with tab_spaces = 2 and reformat all Rust files
  • Add nix run github:juspay/vira ci as first step in just ci

Closes #3

srid added 2 commits March 19, 2026 20:55
Add rustfmt.toml with tab_spaces=2 and reformat all Rust files.
Also add `nix run github:juspay/vira ci` as first step in `just ci`.

Closes #3
@srid
srid marked this pull request as ready for review March 20, 2026 00:58
@srid
srid merged commit d9060b2 into master Mar 20, 2026
5 checks passed
@srid
srid deleted the claude/youthful-rubin branch March 20, 2026 00:59
srid added a commit that referenced this pull request May 19, 2026
Reviewer #6 on PR #929. Two related correctness gaps:

1. `sendRequest` had no per-request deadline — a hung helper left
   pending Promises forever. Add a 60s ceiling (covers cold nix-run
   builds; live RPCs return in ms) that rejects the pending entry,
   logs at `warn`, and cleans the timer on both resolve and reject
   paths.

2. The `readyTimeout` rejected the connect Promise but left `child`
   bound to the unready ssh process. The next `ensureConnected` then
   treated the half-open child as connected and hung again behind the
   same broken pipe. On timeout: kill the ssh child, null `child` +
   `connectPromise` so the next call starts a fresh connect attempt.
srid added a commit that referenced this pull request May 27, 2026
`SystemSchema` carried a `state: "copying"|"connecting"|"connected"|"disconnected"`
field that the agent hardcoded to "connected" and the parent unconditionally
overrode on every write — two independent volatility axes (OS reporting,
parent↔agent link lifecycle) sharing one schema. Extract `ConnectionSchema`
into its own cell on the shared surface; the agent serves the default value
(it has no visibility into the link from the inside, lesson #6) and the
parent owns the live `connection` updates from its `HostSession.onState`.
Browser's overlay reads `connection.state` instead of `system.state`.

Hickey F-3; Lowy Finding 3.
srid added a commit that referenced this pull request May 27, 2026
Codex review findings #6 and #7:

- `build:agent` in `package.json` pointed at `tsconfig.agent.json`
  which doesn't exist (and the agent runs through `tsx`, no `tsc` emit
  step anywhere in the flow). Removed.
- `packages/surface/README.md` said to pass `implementSurface(...).router`
  directly to `serveOverStdio` — that's the exact double-prefix
  footgun the regression test pins against. Updated the snippet's
  comment and added a "Router wrapping (footgun)" paragraph explaining
  why `implement(contract).router({...fragment.router})` is required.
- `packages/surface/example/remote-process-monitor/README.md` still
  described the old v1 closure-copy model (`AGENT_PATH`,
  `nix copy --to ssh://`). Replaced with the `.drv`-copy + remote
  realise flow that the code actually does (`KOLU_AGENT_DRV` env,
  `nix copy --derivation`, `nix-store --realise` on the remote),
  including the single-binary `nix run .#process-monitor-monitor`
  entrypoint and the `trusted-users` requirement on the remote.
srid added a commit that referenced this pull request May 30, 2026
Constraints #7 (session backup) and #8 (preferences storm) split out as
GitHub issues #1040 and #1041. Scorecard + constraints list now link them,
and a 'Decision — salvage, in progress' note records that #5 shipped while
#6, #1-3, and #4 are being implemented onto this PR.
srid added a commit that referenced this pull request May 30, 2026
…nary (R4c #6)

`currentBuildId()` keyed on the `/nix/store/<hash>-kolu-stamped` path — the
entire kolu build — so every deploy (even pty-host-untouched, server/client-
only ones) flipped `outdated` and fired the "update pending" nudge, whose only
action is a terminal-losing restart that gains nothing.

Now keyed on a build-time content hash of just the @kolu/pty-host source,
baked by nix as KOLU_PTY_HOST_BUILD_ID and inherited identically by server and
daemon. Precedence: KOLU_BUILD_ID_OVERRIDE (test seam) → KOLU_PTY_HOST_BUILD_ID
(prod) → deriveBuildId(argv[1]) (dev fallback, unset in dev so no spurious
nudge). The separate wire-incompatible contract path (PTY_HOST_CONTRACT_VERSION)
is untouched — a breaking change still force-restarts.

- default.nix: hash pty-host src (*.ts minus tests + package.json), inject env
- buildId.ts: pure resolveBuildId() + new precedence; +unit tests
- client copy reframed "kolu build" → "terminal host" (chip, command, confirm)
- surface.ts / ptyHostSurface.ts / supervisor.ts: doc corrections
- daemon-update.feature/steps: narrative only (override seam still highest)

See docs/plans/remote-terminals.pty-daemon.html.
srid added a commit that referenced this pull request May 30, 2026
…1047

Deployed #1034 to the production box and exercised it live: the deploy
reattached 3 terminals from the surviving daemon and correctly flagged the
pre-#6 daemon stale (the one-time staleness-regime cutover); the user restart
then cleared it (outdated:false) in ~600ms with no kill-then-pray hang, and
restore brought the session back — no empty-canvas lie. Scorecard, phase
table, and decision note updated to prod-verified; flagged the open
noImportCycles fix as pre-merge. Filed #1047 (always-visible build-id readout).
srid added a commit that referenced this pull request May 30, 2026
Wire #5/#6/#1-3/#4 in the scorecard + decision note to their commits
(383a26a / 9d45adb / a427891 / 2e1d23c), and the prod-verify section to
the deployed build edb3fc6.
srid added a commit that referenced this pull request Jun 2, 2026
Paulg-method rewrite of the 14 panel-flagged passages. 11 applied
after an adversarial skeptic confirmed each cleared the reader's
stumble while preserving meaning, facts, the physics metaphors, and
voice. 4 of the 11 are the skeptic's own endorsed fix (the rewriter's
version was rejected and replaced).

Headline fixes:
- #1 "the invariant" now named ("the code's analogue of that
  frame-invariant interval") — was undefined-on-a-definite-article.
- #6 "structural review has." -> "has become that." — elliptical
  gapping after a negative had inverted the meaning.
- #4 lede "volatilities that got bound" -> "fast-moving and
  slow-moving parts coupled" — one reader had read "bound" as the
  opposite (good) meaning.
- #12 moved "space-like" off the observer onto the slice/reading.
- #3, #7, #8, #9, #10, #11, #14: see optimization log.

3 deliberate keeps (over-fix guard): #5 (the physics is fine as-is),
#13 (gloss would steal the next paragraph's reveal), #2 (deferred to
pass 2 — pass-1 rewrite contradicted the post's thesis).

Behaviour gate: `just website::build` PASS.
srid added a commit that referenced this pull request Jun 6, 2026
Coherent axis (escape-sequences→typed events) fused into pty-host's
process lifecycle. Inert since extraction, no external consumer, one
entanglement (OSC 633 drives foreground sampling). Defer — a Löwy axis
whose volatility hasn't fired yet.
srid added a commit that referenced this pull request Jun 13, 2026
Address both codex findings on the shell-carried commit stamp branch.

F1 (fixed) — injectShellCommit's `/<head[^>]*>/i` also matched `<header>`,
so a shell with no real <head> but a body <header> would build and inject
the commit script at the wrong place, defeating the fail-loud contract.
Tighten to `/<head(?:\s[^>]*)?>/i` (tag-name boundary) and add regression
tests: a `<header>`-only shell now throws, and a `<head lang="en">` still
matches.

F2 (partial) — refresh the Atlas note to the shell-global model. Updated the
current-looking invariants (#2 build identity rides the no-store shell via
window.__SURFACE_APP_COMMIT__, read by shellCommit(), never a hashed-asset
define; #6 commit propagation) and the pieces table; rebuilt docs/atlas/dist.
Left the `clientCommit={__SURFACE_APP_COMMIT__}` snippet at line ~245 as-is:
it lives inside the "## The API — historical (merge era)" section, which is
explicitly banner-flagged as a removed/superseded API (composeSurfaces /
implementSurfaceApp), so rewriting just its commit line would misrepresent
design history rather than current contract.

Also folded in `just fmt`'s reformat of two sibling test files on this branch.
srid added a commit that referenced this pull request Jun 15, 2026
Address codex's review of the P2.7 warm fast-path.

F1 (minor, agreed/fixed): the speculative warm probe forwarded all its
stderr through the user-visible onProgress ring. On a cold host the
probe is *expected* to fail (the .drv isn't there yet) and nix emits a
real `error: …` line, so a clean first-time provision read as if it had
errored. Add a probe-specific callback `onProbeProgress` that still
scans each line for the network classification (a transport failure on
the probe must keep flipping `sawNetworkError` so the fall-through's
`causeFor` calls an unreachable host "network" — deliberate decision #6)
but does NOT echo the line to `opts.onProgress`. The real copy/realise
path still reports its own errors verbatim if provisioning ultimately
fails. Two tests lock both halves in: a cold probe-miss `error:` line is
swallowed from the progress lines; a network-looking probe line still
yields `cause: "network"`.

No other findings — codex approved the core Nix semantics (realises the
drv, returns the realised out-path, skips `nix copy` only on a
realisable remote closure) and the unchanged cold path.
srid added a commit that referenced this pull request Jun 30, 2026
…view gauntlet)

Hardens the S1+S2 cutover against the 4-reviewer architecture-first-principles
+ perfection-review debate (debates/pr1626-review). Every residual was in the
shell around the fold; the fold spine was credited sound.

- #2/#7 — the restore target is now a fold-produced discriminated value:
  `RestoreTarget = none | { exact, command, agent } | { legacyMostRecent, command }`
  (`restoreTargetOf`, consumed by `resumeFormFor`), replacing the bare optional
  `resumeAgent` identity. Absence can no longer be read as "resume most-recent":
  quit-to-shell → `none` → bare shell by construction (#1492) for new records;
  migrated pre-1.29 records keep most-recent as a NAMED `legacyMostRecent`. The
  derivation moved out of the emit shell into the fold (closes #7). The client
  restore card + dormant tile require an actual `exact`/`legacyMostRecent` target.
- #5 — the authored collection no longer re-publishes on the ~150 ms observation
  firehose: the emit loop computes deltas once and gates each arm by its own
  (observed publish on an observed change, authored publish on an authored-fact
  change, disk autosave on a restore-relevant change).
- #1 — the recency frame phase is decided BY VALUE (compare the re-resolved agent
  identity to the saved restore target), deleting the 1.5 s wall-clock window and
  its restamp race.
- #8 — the schema-module docstring is rewritten to the Observation/AgentMemory/
  KoluAwareness world; the deleted AwarenessValue/AwarenessSink paragraphs are gone.

Deferral caveats made honest (not implemented): #3 the sleep drain stays deferred,
and the docs stop claiming the freeze "holds by construction" (a sub-second
launch-then-sleep now yields a false BARE shell under model B — fail-safe); #6
adds a fold.test.ts pin that a structurally-equal-but-new-ref git/pr still compares
equal at the autosave fence.

AgentIdentity + RestoreTarget move down to anyagent/schemas (the resume vocabulary
layer), re-exported through terminal-workspace. SCHEMA_VERSION migration reshaped
to synthesize the discriminated target. Docs (README, 5 Atlas notes, changelog)
reconciled to the shipped behavior.
srid added a commit that referenced this pull request Jun 30, 2026
## Awareness — the producer observes, kolu remembers (S1 + S2 → R9.0)

Implements the merged plan
[`awareness-derive-store.mdx`](docs/atlas/src/content/atlas/awareness-derive-store.mdx)
(#1621) as **one coordinated change** — S1 (the memoryless producer) and
S2 (kolu's fold) are coupled, so they ship together — then **hardened
across two rounds of a 4-reviewer architecture-first-principles +
perfection-review gauntlet** (`debates/pr1626-review`,
`debates/pr1626-rereview`). The reviewers credited the fold spine
**sound**; every residual was in the shell around it — the first round's
four must-fixes (below), then two relocations codex caught on re-review
(F1/F2).

### The cut

The per-terminal awareness engine used to **derive** awareness by
**mutating a host record it also read back as memory**, through an
`AwarenessSink` two homes (kolu + `pulam`) each had to wire. This splits
it:

- **S1 — memoryless producer.** `startAwareness` → `startSensors`: it
*emits* per-field `TerminalEvent`s and takes no seed, no sink, no
record. Deleted: the `AwarenessSink` interface, **both**
`makeAwarenessSink` impls (kolu's + pulam's `hooks.ts`),
`AwarenessRecord`, the apply-and-publish read-back.
- **S2 — kolu's fold.** A pure `fold(cur, o, ctx) → TerminalState`
(`{snapshot: TerminalSnapshot, memory: AgentMemory}`). The producer's
emit type **cannot spell** a memory fact, so the old "two narrowed
mutators" write-fence is now the **type**.
- **R9.0 — local cutover.** kolu runs the producer in-process and folds
the stream, publishing the snapshot half to `snapshots` and the memory
half to `kolu.authored`.

### Review-gauntlet hardening (the four must-fixes)

| # | Finding | Fix |
| --- | --- | --- |
| **#2 / #7** | The restore target was a bare optional `resumeAgent`
identity read alongside `lastAgentCommand`, so `(command set, identity
absent)` meant **both** "quit, restore nothing" *and* "no id captured,
resume most-recent" — and the derivation lived in the emit shell, not
the fold. | The fold now produces a **discriminated `RestoreTarget`** —
`none \| { exact, command, agent } \| { legacyMostRecent, command }`
(`restoreTargetOf`, consumed by `resumeFormFor`). Absence can no longer
be read as most-recent: **quit-to-shell → `none` → bare shell by
construction** (#1492) for new records; a migrated pre-1.29 record keeps
most-recent as a *named* `legacyMostRecent`. The client restore card +
dormant tile require an `exact`/`legacyMostRecent` target. |
| **#5** | The authored *collection* re-published on **every**
observation tick (~150 ms) — only the disk arm was fenced. | The emit
loop computes deltas once and gates each arm by **its own** delta:
snapshot publish on a snapshot change, **authored publish on an
authored-fact change**, disk autosave on a restore-relevant change. |
| **#1** | Recency leaned on a 1.5 s wall-clock window; a slow agent
resolution crossing it could restamp saved recency on restore. | The
frame phase is decided **by value** — compare the re-resolved agent
identity to the saved restore target. The timer is deleted. |
| **#8** | The schema module's docstring still taught the deleted
`AwarenessValue` / `AwarenessSink` world. | Rewritten to
`TerminalSnapshot` / `AgentMemory` / `TerminalState` / the emit type. |

`AgentIdentity` + `RestoreTarget` moved down to `anyagent/schemas` (the
resume-vocabulary layer), re-exported through `terminal-workspace` — the
fold *produces* the target, `resumeFormFor` *consumes* it, one owned
type. The serial lens → codex → simplify → code-police pass then
collapsed the vestigial `AgentSessionRef` into `AgentIdentity`, shared
one `resumableCommand` projection across the client surfaces,
value-gated the emit fences, and hardened the migration against corrupt
input.

### Second re-review — two relocations codex caught (the panel earned
its keep again)

A second 4-reviewer pass on the hardened HEAD credited the four
must-fixes **closed by construction** — but codex (1 of 4) found two
real defects relocated *inside* the new shapes, both verified against
the diff:

| # | Finding | Fix |
| --- | --- | --- |
| **F1** | `restoreTargetOf` paired `lastAgentCommand` with
`snapshot.agent` **without checking they agree on agent kind**, so a
stale-command/new-agent race could build `exact{ command:"opencode …",
agent:{kind:"claude-code"} }` — which `resumeAgentCommand` silently
downgrades to opencode's most-recent: the wrong-agent resume #2 makes
unspellable, re-expressed in the `exact` arm. | One constructor,
**`exactRestoreTarget(command, agent)`** (anyagent), builds `exact`
*only when* `agentKindFromCommand(command) === agent.kind`, else `null`.
**Both** production sites go through it — the fold (→ `none` on
mismatch) and the migration (→ `legacyMostRecent` on mismatch) — so
`resumeFormFor` always takes the same-agent path (exact-by-id, or
refuse), never the downgrade. |
| **F2** | The funnel `guardedEmit` swallowed a thrown emit **after**
the producer advanced its dedup baseline, so "emitted" ≠ "accepted": the
fold never got the value while the baseline advanced → a later equal
value deduped → silent permanent divergence. The guard sat at the wrong
seam. | Error handling moved to where the fallible work is — the host's
**publish boundary**.
`commitSnapshot`/`updateMemory`/`emitTerminalsDirty` (kolu) and pulam's
snapshots `upsert` each **fold-accept first, then guard their own
publish** (logged, self-heals on the next change). With the publishes
infallible, `emit` is infallible — and the wrong-seam funnel guard is
**removed**. |

### Type-naming cleanup

A follow-up **mechanical, compiler-checked rename** gives the awareness
types **one root per shape** — `TerminalSnapshot` (the whole value a
host emits) · `TerminalEvent` (a per-field delta) · `TerminalState` (the
`{ snapshot, memory }` composite kolu folds) · `Known<T>` (resolution
status). The `Terminal` prefix scopes the wire-facing family, and a
field repeats its type's root (`entry.observed` → `entry.snapshot`). The
served collection key bumps `awareness` → `snapshots`
(`TERMINAL_WORKSPACE_CONTRACT_VERSION` `2.0` → `3.0` — breaking, the
wire path changed). "Observation"/"awareness" survive only as
feature-word prose; the deleted `AwarenessSink`/`AwarenessValue` keep
their old names where the text describes what was removed. The
schema-module docstring, the [design
note](docs/atlas/src/content/atlas/awareness-derive-store.mdx) (types,
prose, and the foldflow/homes diagrams), and the unit-test fixtures all
move with the code.

### Honoring the design philosophy

- **Fail-fast / no knobs** — the write-fence is the emit type; the
restore decision is a discriminated value, not a runtime fallback. No
override path.
- **Volatility boundary** — producer/fold/`RestoreTarget` stay in the
`@kolu/*` packages; kolu and pulam are consumers, the dependency arrow
points out.
- **Reuse the source of truth** — `foldSnapshot` is shared by kolu's
fold and pulam's accumulator; `restoreTargetOf` / `exactRestoreTarget`
are the *one* place a restore target is decided; the migration composes
the existing backfill ladder.

### Deferred refinements (documented honestly, with pins)

- **Active drain-at-sleep (#3).** `beginSleep` freezes the last
fold-written target and does **not** drain a final settle, so a
sub-second launch-then-sleep freezes a stale `none` and wakes to a
**false bare shell**. The docs no longer claim the freeze "holds by
construction"; under model B the consequence is fail-*safe* (a bare
shell, never the wrong conversation). The active drain is an
async-`beginSleep` follow-up.
- **`git`/`pr` reference equality (#6).** `restoreRelevantEqual`
compares `git`/`pr` by reference (correct today — upstream
`gitInfoEqual`/`prResultEqual` dedup before emit). A `fold.test.ts` pin
asserts a structurally-equal-but-new-ref `git`/`pr` still compares equal
at the fence, so a future fold copy can't silently break it.
- **R9.3 scope** — the framed `TerminalFrame` *wire* stream and
cross-host keying stay R9.3; R9.0 keys by `TerminalId` and folds
in-process.

### Migration

`SCHEMA_VERSION` 1.28.0 → 1.29.0: `backfillSnapshotCutover` backfills
`pr: { kind: "absent" }` and **synthesizes the discriminated
`restoreTarget`** from what a pre-cutover record remembered — a valid
`agentSession` whose kind matches the command → `exact`, a command alone
(or a kind mismatch) → `legacyMostRecent`, neither → absent (`none`);
corrupt `agentSession` value-types fall through safely rather than
producing a parse-rejecting record.

### Tests & CI

The pure `fold` / `restoreTargetOf` / `exactRestoreTarget` /
`resumeFormFor` are unit-tested (the discriminated switch, the
kind-mismatch refusal, the ref-stability pin, the publish-boundary
infallibility). The sleep/wake, adopt, reconcile, metadata,
session-transfer, and restore-card tests track the reshaped persistence;
the e2e session-restore + sleeping-terminals fixtures were fixed to
stamp the now-required `pr`. All **unit tests** green across the touched
packages, `just check` (tsc + biome) clean, and the **Atlas sync gate
idempotent** — re-confirmed after the type-naming cleanup. **Full CI
green on both `x86_64-linux` and `aarch64-darwin`** (every recipe × both
platforms) on the naming commit, including `ci::e2e` on each.

_Generated by [`/be`](https://github.com/srid/agency) on Claude Code
(model `claude-opus-4-8`)._
srid added a commit that referenced this pull request Jul 7, 2026
…the known class's last consumers)

Re-sweep #6's 3 confirmed defects (single-editor). No new classes — the last consumers of the
host-scoping theme (a leg reading a LOCAL/host-independent fact against a REMOTE active host).

MAJOR — the LOCAL padiLink leg false-warmed a REMOTE active host. `padiLink` is kolu-server's
binding to its OWN local padi (host-independent), but downState()/daemonWarming() folded it
UNCONDITIONALLY, so a local-padi drop (spontaneous reconnect, or daemon.restart → drainBoundPadi
→ renew — host-independent) while a REMOTE host was active replaced the remote's LIVE terminals
with "Restarting kaval…", locked ⌘T, MASKED a genuine remote-kaval death, and read the remote
inventory "unavailable" — a cross-host honesty regression, wrong in exactly the direction W4
sought. Fix: `activePadiLink()` = `localPadiLinkOnly(activeHost(), padiLinkState(), LOCAL_HOST)`
(extracted pure) collapses the local leg to the "connected" no-op sentinel for a REMOTE active
host, so the decision falls through to the host-scoped kaval state + activeEntryConnected (which
cover a real remote drain via the remote's OWN daemonStatus); routed into downState, daemonWarming,
and boundHostInventoryLive. LOCAL_HOST keeps the #1034 local-restart-drain fold verbatim. Pin
(both directions): local drop + local active ⇒ warm (drain UI survives); local drop + remote
active ⇒ NOT warm + the remote's own death still surfaces.

MINOR — kaval memory tooltip floored on the ws leg only (useMemoryUsage). The kaval RSS renders on
the HOST-SCOPED Kaval chip (dot/state/uptime floor on daemonChannelLive), so its display folds the
SAME entry leg: a dead active REMOTE entry hides the stale RSS instead of a figure beside an
"unknown" dot. The a340322 rationale (processMemory is a host-independent local-stack diagnostic)
stands for the VALUE; the DISPLAY floors on the host-scoped channel like every other rail consumer.

MINOR — the reattach-toast dedup used ONE shared localStorage high-water mark over RAW foreign
adoptedAt epochs (per-host clocks, deliberately unreprojected — a monotonic dedup key). Cross-host
clocks are not mutually monotonic, so a remote ahead-clock toast suppressed a genuine later LOCAL
re-adoption (the foreign-clock class, storage edition; shipped #1365). Fix (shape per ruling): keep
ONE key, make the VALUE a per-host record {[host]: mark} — the honest per-host-clock model, no
persistedPref-name-at-init friction. Pin: remote ahead-clock toast, switch-back local re-adoption
still fires.

Green: client 583 (+4 pins), tsc, biome. (#6 convergence: no new classes.)
srid added a commit that referenced this pull request Jul 8, 2026
…panel bit

Move UI state that parameterizes a VIEW OF a host's content into the per-host
`scopedByEntry` owner (`hostScope/createViewState`) — per-host by construction,
the "maximized pattern": fact born in the owner, facade re-points at
`activeScope()?.view.<fact>`, removal race floors to the default. THE RULE now
lives in `canvasBoundaryGuard.test.ts`: state that parameterizes a VIEW OF
per-host content is per-host; state that parameterizes the VIEWER (density taste)
is per-tab.

1. canvasMaximized — dropped the `kolu-canvas-maximized` boolPref; now a plain
   per-host in-memory signal in `createViewState` (default false; reload → tiled,
   matching the camera tier). `useViewState` re-points + floors to false.
2. activityWindow — was one global localStorage singleton; now a per-host
   persisted signal in the owner, keyed `kolu-activityWindow:<encodeHostKey(host)>`
   so each host's dock filter survives reload without colliding. `activityWindow.ts`
   became the facade (consumers unchanged).
3. showSleeping — same shape as (2), keyed `kolu-showSleeping:<encodeHostKey(host)>`.
   `showSleeping.ts` became the facade.
4. right-panel collapsed bit — was the global server pref
   `preferences.rightPanel.collapsed`. CHOICE: in-memory per-host signal in the
   owner, SEEDED from the existing global pref (`?? preferences().rightPanel.collapsed`),
   NOT a per-host field on the server schema — the lower-risk option (no schema
   widening; reload re-inherits the global). The right-panel ACTIVE TAB was already
   per-TERMINAL (server session via chrome.setRightPanel), i.e. strictly finer than
   per-host, so it is already host-scoped and needs no change. `panelSize`/
   `codeTabTreeSize` stay global (viewer density taste, per-tab by THE RULE).
5. minimap-expanded — NO SUCH STATE EXISTS. Exhaustive search found no
   `minimapExpanded` / minimap open-collapse toggle; `CanvasMinimap` is simply
   shown in tiled posture and hidden when maximized. "minimap-expanded" appears
   only as an ILLUSTRATIVE example in `docs/atlas/.../padi.mdx` (alongside
   canvas-maximized), never built. The one real sibling posture is the dock's
   rail↔cards `dockMode` (`kolu-dock-mode`), which is density taste → correctly
   per-TAB by THE RULE, so it is deliberately NOT moved. Nothing to implement.
6. reattachAnnouncedAt — already correct: the dedup high-water mark is a
   `Record<encodeHostKey(host), ts>` keyed by the canonical host string, committed
   per active host; cross-host announcements are independent. Verified against the
   existing `reattachAnnounce.test.ts` "re-run #6" cross-host case. No change.

Note: `terminal/activityWindow.ts`'s facade reads the owner via `activeScope`,
and the owner reads that module's `DEFAULT_ACTIVITY_WINDOW`/`isActivityWindow` —
a benign import cycle (call-time only on both sides; documented inline).

Tests: new `perHostViewState.test.ts` — one acceptance per moved item over the
REAL owner (set on A → B sees default → back to A restored), plus the per-host
storage-key assertion for the two persisted filters (real `persistedPref` over
happy-dom localStorage). perHostCanvas.test.ts's 3 pinned it() blocks are
byte-identical; only its `./persistedPref` mock grew a `persistedPref` stub.
srid added a commit that referenced this pull request Jul 14, 2026
…1812)

## What

The `/kolu` skill warns that a paste can fold into a `[Pasted text +N
lines]`
placeholder and fail to submit — but it frames that limit as
**"multi-KB" / "large"**
and calls anything smaller a **"normal-size"** prompt safe for the
three-step submit.
That mental model is wrong, and it bit the driving agent repeatedly.

The fold is triggered by **line count, not byte size**. A multi-line
status report
folds well under 1 KB. This PR reframes the callout around line count
and makes the
file-pointer workaround the *default* for any multi-line message, not a
special case
for a "big brief."

## Why — the evidence

Mined from the XKIT-PR2 `/be` run (session `fdc62b24`), where this
session drove a
coordinator terminal over `kaval-tui send`. Its reports kept folding
into placeholders
that would not submit, each forcing a *clear + short-pointer resend*
cycle:

| Observed fold | Size | The agent's own note |
| --- | --- | --- |
| `[Pasted text #13 +15 lines]` | 1807 B / 15 lines | "the known
large-paste limitation" |
| `[Pasted text #6]` | ~2414 B | "an Enter now would leave it staged" |
| `[Pasted text #12]` | ~909 B | "clear it and send a tight venue
question" |
| (another) | ~1011 B | **"Folded again (the fold threshold is low)."**
|

Two of these folded at **under 1.1 KB** — nowhere near "multi-KB." The
agent had to
*discover empirically* that "the fold threshold is low" and switch to
the
file-pointer workaround, which the skill had already documented but
mis-scoped as a
big-brief-only path.

## The fix (per-edit ledger)

`agents/.apm/skills/kolu/SKILL.md` (the source; generated `.claude/` +
`.agents/`
copies + `apm.lock.yaml` regenerated via `just ai::apm`, same commit):

1. **Callout framing** — "LARGE pastes (multi-KB) ... normal-size
prompts" →
"MULTI-LINE pastes ... a short prompt (a line or two)", stating the
trigger is
**line count, not byte size**, folding **well under 1 KB** (~900 B / ~a
dozen
lines), and that *any* multi-line message (report, ruling, verdict) is
fold-prone.
2. **Workaround scope** — "Workaround for a big brief" → "for any
multi-line message",
with an explicit steer to reach for the file-pointer **by default** past
a couple
   of lines rather than after getting bitten by a fold-and-resend cycle.

## Not shipped (observations only — did not meet the durability bar)

- **Browser pointer/hover media emulation** (the e2e-gate took three
escalations to
learn Chromium welds `(hover)` to touch capability): a one-time domain
discovery,
self-corrected by the review gauntlet + `/perfection-review`, and
already encoded in
  the shipped code comments. Not a recurring skill gap.
- **`ci/pu/lease.sh` granting the quarantined `kolu-ci-1`** (forcing a
two-lease
dance): a transient infra quarantine; the real fix is a lease-script
exclusion flag,
which is out of `/self-improve`'s `.apm/skills/*` scope. Single
incident.

## Verification

- `just fmt` — clean (no reformats).
- `just check` — green (exit 0): all typechecks + biome lint pass.
- `just ai::apm` regen confirmed to land in `.claude/` and `.agents/`
(hashes match in
  `apm.lock.yaml`); no unrelated skill drifted.

Provenance: `/self-improve` micro-loop over session
`fdc62b24-b5c8-4c0b-ad4c-577f8ab87b08`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
srid added a commit that referenced this pull request Jul 28, 2026
…cet vocabulary

`OsfactsReading` stood for both `Snapshot` and `HostSnapshot`, so eleven of its
eighteen fields were structurally always-empty at every call site and the
parser accepted either grammar in either output. It splits along the split Rust
already has.

- lowy F3 ≡ hickey #6 — one type for two verbs; now `SnapshotReading` /
  `HostReading` with `parseSnapshotOutput` / `parseHostOutput`, each refusing
  the other's row tags loudly instead of returning a silently empty field.
  `HostReading.memory` can finally be named `memory`, matching the JSON face
- lowy F4 ≡ hickey #7 — `mem` meant process RSS and host RAM in one flat union;
  the verb split takes the facet unions with it
- lowy F5 ≡ hickey #9 — the flag→wire-name map is not mechanical (`procs` names
  `proc`; `ports` names three), so every consumer re-derived tool knowledge by
  hand; `snapshotFacetNames` exports it. It states fact, not policy: which
  named facet counts as blindness stays with the consumer
- lowy F8 — `processIdentity` put the daemon supervisor's identity-gate policy
  inside the explicitly policy-free client, with zero consumers (verified by
  grep) and shaped for the OSF8 consumer this PR deliberately parked. Deleted,
  with the sync spawn that existed only to serve it
- lowy F1 ≡ hickey #1 — `facets.test.ts` pins the three unions to
  `osfacts/facets.json`, out of `describeDaemon` so the fast local loop catches
  the drift too
- lowy F10 — the zero-value reading was written out twice with nothing
  requiring the copies to agree
- lowy F12 — `isTcpPort` is no longer exported; it is the parser's own guard

Raised by the lowy ∥ hickey lens review. Not pushed or merged.
srid added a commit that referenced this pull request Aug 1, 2026
- hickey #2 — rule 2's `seen` membership test now covers every entry reaching
  `overlay`, not just the top-level filter: a duplicated path makes
  `pathDiffOperations` emit two adds for one row, Pierre throws, and the
  recovery discards every hand-expanded folder.
- hickey #6 — the rule-4 queue walk becomes one self-contained recursive
  `emit`. Fixes `lazyDirs.push` sitting above the visited check (a directory
  reached twice was listed twice) and `queue.unshift(...children)`, a
  `RangeError` on the six-figure flat level this feature targets. The
  loaded-but-empty directory keeps its own row exactly as before.
- hickey #5 — `ignored` now names a loaded directory's own key as well. Pierre
  still paints that row from its children's prefixes, so dropping it un-dimmed
  the folder at the moment the user opened it, children dimmed below.
- hickey #10 — `diffInventory` is the one constructor for the no-overlay case,
  so a new `BrowseInventory` field can't be forgotten at CodeTab's literal.

Raised by the lowy ∥ hickey lens review. Not pushed or merged.
srid added a commit that referenced this pull request Aug 1, 2026
- hickey #3 — `openLazyDirs` carried three facts and two broke. A row that has
  no node right now (a search projection hid it) no longer retires the record,
  so a filter keystroke stops erasing the user's expansion; and a key the host
  no longer declares lazy is pruned, so an eye-toggle round trip reports afresh
  instead of showing an arbitrarily old cached level with no refetch path.
- lowy #2 — a `lazyEpoch` prop clears the record when the host's loaded levels
  stop describing this tree (a repo / host switch), the wrapper's half of the
  invalidation the host already performs on its children cache.
- lowy #6 — `onExpandLazyDirectory` may return a promise; on rejection the
  wrapper forgets the expansion, so a transient read failure no longer wedges
  the folder open-and-empty for the mount.
- hickey #7 — the `tree.subscribe` callback runs under `safeApply`, so a throw
  can't escape into Pierre's emit loop and take other subscribers with it.
- hickey #9 — "which directories should be open" is spelled once
  (`desiredExpandedPaths`) and used at both the constructor and `toOpen`.
- hickey #8 — `expandPaths`' JSDoc no longer asserts an invariant
  `openLazyDirs` has made false.

Raised by the lowy ∥ hickey lens review. Not pushed or merged.
srid added a commit that referenced this pull request Aug 1, 2026
- hickey #4 — one `AbortController` per directory replaces the hand-rolled
  `loadGeneration` + captured-`issuedSlot` pair, so "is this response still
  wanted" is ONE fact both callbacks read rather than two conditions each has
  to repeat. Fixes the real asymmetry where `.catch` checked only the slot and
  could toast a failure over a folder showing correct contents, and stops the
  superseded readdir server-side instead of racing it.
- lowy #2 — pass `lazyEpoch={slotKey()}` beside the existing clear, so the
  tree's record of open lazy directories is invalidated by the same signal.
- lowy #6 — the handler returns its promise and re-throws after toasting, so
  the tree drops its record and a retry costs one re-expand.

Raised by the lowy ∥ hickey lens review. Not pushed or merged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rust: use 2-space indentation

1 participant