Skip to content

Upgrade to Lash main with facade composition and selectable RLM dialect - #4

Merged
SamGalanakis merged 26 commits into
mainfrom
upgrade/lash-main
Aug 26, 2026
Merged

SamGalanakis merged 26 commits into
mainfrom
upgrade/lash-main

Conversation

@SamGalanakis

@SamGalanakis SamGalanakis commented Aug 23, 2026 •

Copy link
Copy Markdown
Owner

Upgrade lash-cli from Lash alpha.104 to Lash main (git pin 8dac97fa5, not a release) and rewrite the runtime composition onto the Lash host facade.

What changed

  • Pin Lash main. All lash* dependencies are git revs on Ascending-AI/lash main (372a95fe9). Upstream now ships its own lash-regress fork, so no [patch.crates-io] block is needed.
  • Facade composition. Session startup builds LashCore::{standard,rlm}_builder with an explicit host policy (host_policy.rs: turn/no-progress/commit budgets, queued-work batching, RLM execution bounds) and installs the store factory, effect host, attachment/process-env stores, process registry, trigger store and trace configuration. Provider configuration is host-owned (ProviderConfig { kind, config }, same on-disk JSON shape as before).
  • Selectable RLM dialect. --rlm-dialect lashlang|typescript (default: config rlm_dialect → LASH_RUNBOOK_DIALECT → lashlang). The dialect is stated on every RLM session open via plugin_option(RLM_PROTOCOL_PLUGIN_ID, RlmCreateExtras), recorded by Lash, and fixed for the session: resuming with a conflicting dialect is refused. /mode and the status bar show rlm · <dialect>; onboarding and Ctrl+P offer both dialects; subagents inherit.
  • Durable turns and queues. Turn execution uses TurnReport, typed TurnInputApplication receipts, TurnInputIngress::{active_turn, NextTurn} for steering. Escape is a durable TurnCancelRequest with an explicit undelivered-input disposition (TurnCancelDisposition::Drop), driven through core.turn_work_driver(); the cancel outcome's affected_inputs restore dropped drafts to the editor. Typed RLM execution bounds (InstructionBound/WallClockBound/MemoryBound). Forking uses LashCore::pin + fork_at.
  • Unified store. Sessions live in ~/.lash/store/durable-core.db (plus processes.db, triggers.db, effects.db, artifacts.db, process-env.db, attachments/). The session catalog is the identity source of truth — enumeration and resume go through LashCore::sessions()/read_session; the host roster (~/.lash/sessions/<id>.{ui.json,ui-activity.jsonl,host.json}) is a display-only sidecar. Startup preflights durable formats (session schema 41, trace 9, remote 46); alpha-era per-session databases are never opened — the picker counts them as not openable and explicit resume refuses them with the detected format stamps. No migration.
  • Plugins and tools. Host-owned update_plan plugin replaces the removed lash-plugin-plan-mode; observational memory (--context-approach, --om-*) is removed with the upstream crate; MCP discovery moves from src/examples to src/mcp_discovery; tool surfaces use the restored lash-tool-support helpers (typed_args/typed_ok/canonicalize_under/object_schema/tool_binding) and boundary-typed plugin directives from the lash::plugins/lash::tools facade homes; lash-export reads stores via SqliteSessionStoreFactory::open_read_only + LashCore::read_session.
  • Clean cutover. Deleted persistence.rs, SessionStreamEvent handling, host-side fork snapshots, legacy projection branches, the interrupt draft-replay machinery (active_turn_drafts et al.), and the unit suites tied to them; net −8k lines.

Verification

cargo fmt --check, cargo clippy --workspace --all-targets --features lash-cli/test-provider,lash-cli/bench,lash-cli/fff-zlob -- -D warnings, cargo test --workspace (444 passed), cargo test -p lash-cli --features test-provider --test cli_e2e (10 passed, including cli_rlm_typescript_status_and_resume_conflict_are_pinned), python3 scripts/check_docs.py, and operator witnesses: rlm · typescript status line, and an Escape-mid-turn witness showing queued input dropped and restored to the editor.

Release-Notes

  • Build against Lash main (372a95fe9); existing alpha-era ~/.lash/sessions/*.db sessions cannot be opened and are reported as such. Use --reset to start a fresh store.
  • New --rlm-dialect lashlang|typescript, fixed per session and shown in /mode and the status bar.
  • Escape now cancels durably and returns any undelivered queued input to the editor instead of replaying it into the next turn.
  • Removed --context-approach and --om-* options (observational memory retired upstream).
  • CI and release workflows build with Zig 0.16.0 (required by zlob 1.6.1, pulled in by the alpha.104 fff-search update).

Update: re-pin to 8dac97fa5 (2026-08-26)

  • Pin moved 372a95fe9 → 8dac97fa5 (55 upstream commits). Upstream renamed every internal crate to lash-internal-* (only lash-runtime is a supported dependency); the workspace consumes internals via explicit package = aliases and drops lash-core from lash-cli entirely. Version stamps are all derived (session schema 44, trace 10, remote protocol 47). Adopted landed fixes for FIG-2109/2110/2112/2114/2138 and deleted the superseded workarounds; lash-export now reads session metadata from read-only surfaces (SessionSummary::durable_relation).
  • Fix /clear leaks the previous session's activity rows into the new session's timeline #11 — /clear and /resume now reset the UI activity journal, and App::clear() is an exhaustive field destructure (no ..): a new session-scoped field that isn't classified fails compilation.
  • Fix Queued next-turn input executes headlessly via the post-turn catalog sync drain — user row and response never render #12 — interactive cores disable the automatic queued-work driver; the CLI is the sole executor of visible next-turn input, and admin catalog refreshes drain exactly their own batch (fail-closed if a model turn runs). A PTY e2e witnesses the full queue-during-turn flow.

Review outcome

Independent fresh review of the arc-3 diff (7ec1e23..2652454) confirmed the queued-turn ownership design (selected-batch drains structurally cannot claim turn inputs; idempotent re-drain; no double-render in reconciliation; honest e2e witness; clean re-pin). One deciding risk was verified real and deliberately deferred: with the automatic driver disabled, idle process-wake queued work has no interactive executor until the next user turn. No wake producer is reachable in this CLI today (no process-engine plugin; the test provider cannot create durable processes), and the pre-existing behavior ran wake turns invisibly — the same defect class #12 removed. Tracked with a fix sketch in #13; noted in the #12 commit body.

Move the Lash SDK dependency graph to the crates.io release and adapt the CLI to the alpha.104 runtime contracts.

Release-Notes:
- Update lash-cli to Lash v0.1.0-alpha.104.
lash-typescript and the session-pinned RLM dialect only exist on Lash main;
the last published release (alpha.113) predates them. Drop the removed
lash-plugin-plan-mode dependency.

Release-Notes: Track Lash main 1d33a2c7c instead of alpha.104.
Use the current tool outcome and cross-dialect binding APIs, remove per-tool scheduling, and resolve paths without the deleted file-tool helper.

Release-Notes: Port local search tools to the current Lash tool contract.
Match the current shared prose-delta representation so extension event handling remains type-correct against the Lash main turn surface.

Release-Notes: Keep TUI extensions compatible with current Lash turn events.
Adopt unified plugin and tool outcomes, current cross-dialect bindings, facade turn types, and the progress-free attempt execution contract.

Release-Notes: Port autoresearch to the current Lash plugin and tool APIs.
Load sessions from the Lash main durable-core catalog through the current persistence facade, with schema preflight and typed refusal before open.

Replace alpha-era projection and attachment shapes, derive model metadata and display titles from persisted current data, and drop unavailable created-at and cwd columns. Require a host-owned session roster for tree exports instead of scanning per-session databases.

Cover JSON, single-session HTML, and tree HTML with sessions produced by a real current Lash runtime, and pin trace schema 8 plus old-schema refusal behavior.

Release-Notes: Port lash-export to current Lash stores and export formats.
Replace alpha-era persistence, provider factories, event handling, and plugin plumbing with facade-owned composition and typed durable inputs. Pin session policy and RLM dialects while keeping provider config and the session roster host-owned.

Release-Notes: Move the CLI to the Lash facade with durable unified storage and selectable RLM dialects.
Keep the operator, end-to-end harness, and user documentation aligned with unified storage and dialect-pinned RLM sessions. Resolve the built binary through Cargo metadata so witness runs work with shared target directories.

Release-Notes: Document RLM dialect selection and add TypeScript, resume-conflict, and operator coverage.
Semantic MCP indexing only needs the complete public signature rendering, which already includes return fields. Avoid the upstream test-private return renderer so the optional search feature compiles against Lash main.

Release-Notes: Keep semantic MCP tool indexing compatible with the Lash facade.
Track the latest Lash main commit rather than a published release so the CLI consumes the current facade, dialect, and durable-format contracts. The regress patch moves with the same revision.

Release-Notes: Build against Lash main bc28978d2.
zlob 1.6.1, pulled in by the fff-search update, declares a minimum Zig of 0.16.0; the workflows still installed 0.15.2 and failed compiling its Zig sources.

Release-Notes: CI and release builds use Zig 0.16.0.
Cover App::handle_turn_activity at its semantic event boundary so UI state transitions do not rely solely on render and PTY coverage.

Release-Notes: Internal: unit coverage for turn-event dispatch.
@SamGalanakis
SamGalanakis marked this pull request as ready for review August 23, 2026 17:14
SamGalanakis and others added 4 commits August 25, 2026 11:26
Repin the workspace and cut over catalog discovery, typed helpers, facade homes, read-only export, and durable Escape reconciliation so the CLI follows the current runtime contracts without compatibility shims.

Release-Notes: Upgrade the Lash runtime and restore cancelled steer text to the editor instead of replaying it.
Align operator guidance, format diagnostics, export notes, and runbooks with catalog-backed sessions, the current durable versions, and id-or-name resume behavior.

Release-Notes: Document session schema 41, trace schema 9, remote protocol 46, and catalog-based resume behavior.
Remove dropped active-turn inputs from the UI snapshot as soon as durable cancellation settles so restored editor text never masquerades as queued next-turn work.

Release-Notes: Prevent cancelled steer text from briefly appearing as queued work after Escape.
Track the physical Lash turn identity for direct and queued work, submit cancellation without waiting in the TUI, and restore dropped input from the settled turn report. Remove the misleading recovery sidecar and derive the displayed session schema from one documented compatibility constant.

Release-Notes: Keep Escape responsive during durable cancellation and restore dropped queued input after settlement.
SamGalanakis and others added 9 commits August 25, 2026 12:29
Subagent spawns now surface as transient dock rows backed by processes.db;
the runbook premise still described the pre-upgrade dock that hid them.

Release-Notes: none (runbook text only).
Two defects made RLM code blocks effectively undiscoverable.

`set_expand_level` applied only while the view followed output, so
Alt+O and Ctrl+O were silent no-ops once the reader had scrolled up —
exactly when they go looking for the code behind earlier activity. The
gate is gone; in manual-scroll mode the viewport is re-anchored to the
block that occupied its top row before re-layout (offset into that block
preserved, then clamped to the recomputed max scroll), and follow mode is
left untouched.

Below full expansion a `LashlangCode` item rendered nothing at all, so
nothing hinted that code existed or which key revealed it. Levels 0 and 1
now render a single dim marker row — `◇ <dialect> · <N> lines — Alt+O to
expand`, using the session's RLM dialect when known and the neutral word
`code` otherwise. Level 2 renders the full source as before.

`/expand [compact|normal|full]` (numeric `0|1|2` accepted) sets the level
directly and reports it with no argument, going through the same ungated
path, so terminals that swallow Alt chords still have a way in.

Release-Notes: Ctrl+O/Alt+O expansion toggles now work while scrolled up,
hidden RLM code shows a dim `◇ <dialect> · <N> lines — Alt+O to expand`
stub row, and the new `/expand compact|normal|full` command sets the
expansion level directly.

Closes #10
Latch automatic post-turn refresh after an observed failure so later turns remain quiet while explicit refresh requests continue to report errors. Reset the latch whenever the interactive app installs or clears a session.

Closes #9

Release-Notes: Report a failed automatic tool-catalog refresh once per session instead of after every turn.
/clear left the previous session's UI activity journal in memory. Journal
records are keyed by (turn_ordinal, lashlang_block_ordinal), and both
ordinals restart in the new session, so the old session's activity rows
(commands it ran, with their outputs) were spliced into the new session's
timeline at matching positions — asserting executions that never happened
in that session. The durable record and on-disk sidecars were unaffected;
the leak was purely in-process rendering.

Reset the journal, pending records, active turn ordinal, block ordinal,
and block anchors in App::clear, with a regression test.

Fixes #11.

Release-Notes: /clear no longer leaks the previous session's activity rows into the new session's timeline.
App::clear() was a hand-maintained field list, so every new per-session
field silently survived /clear unless someone remembered to add it —
the exact mechanism behind the cross-session activity-row leak. Rewrite
clear() around an exhaustive destructure of App with no rest pattern:
every field must be explicitly classified as carried (process-scoped)
or reset (session-scoped), and adding a field fails compilation until
it is classified.

Newly reset as a result: iteration, live stream chunks and timeline
anchor, history top pad, queue suppression ids and pending option
prompt responses, context window, execution mode and dialect labels,
session identity, text selection, toast, and the mouse-up latch.

Call clear() at session install time on both switch paths: handle_clear
now clears before the new session's identity is applied (previously the
setters ran first, which the exhaustive reset would have wiped), and
activate_opened_session — which never cleared at all, leaking activity
state, processes, plan dock, and overlays across /resume — clears
before the resumed state is loaded.

Release-Notes: session switches now reset all per-session UI state; resuming a session no longer carries over stray state from the previous one.
Re-pin all Lash git dependencies from 372a95fe9 to 8dac97fa5, naming the
renamed lash-internal-* packages now that lash-runtime is the sole
supported package (the facade alias and lash:: import paths are
unchanged).

Adopt the fixes we filed from the previous round:

- FIG-2109: delete the hand-mirrored session schema constant; all three
  compatibility stamps (session schema 44, trace schema 10, remote
  protocol 47) now derive from public upstream constants.
- FIG-2112: lash-export reads session relations from the read-only
  SessionSummary/SessionReadView surfaces; the mutable metadata open is
  deleted and the loaded types narrowed to a host-owned
  {session_id, relation} projection.
- FIG-2114: the remaining lash_core imports move to their facade homes
  (SessionNodeRecordFacadeOps is now lash::persistence::
  SessionNodeProjection); lash-cli drops its lash-core dependency
  entirely.
- FIG-2138: the dialect-loss bug is fixed upstream; the catalog-sync
  failure e2e that depended on inducing it is replaced by a success-path
  e2e proving typescript sessions sync their tool catalog after turns.
  The sync-failure latch stays as generic degradation.

Range adaptations: LlmResponse.full_text is a derived method (test
provider literals updated), SessionRelation::ObserverIntent is removed
from the export tree, and PluginHost::build_session lost its second
parameter. The queued-turn path deliberately keeps drain_id and the
ActiveTurnTargetSink: the new turn_id builder is mutually exclusive with
drain idempotency and FIG-2110's start notification did not land.

Release-Notes: update to Lash main 8dac97fa5; typescript RLM sessions no longer fail post-turn tool-catalog sync; session store schema 44, trace 10, remote protocol 47.
A message queued for the next turn could be claimed by the runtime's
automatic broad queued-work drain when the post-turn tool-catalog sync
ran: the turn executed headlessly with no UI sink, so neither the user
row nor the response ever rendered, while both committed durably —
silently prefixing the visible transcript. The queue preview vanished
via the pending-input snapshot refresh, making the message look
swallowed.

Interactive cores now disable the automatic queued-work driver, making
the CLI the sole executor of user-visible next-turn input; there is no
enqueue-time driver wake left to race. Catalog refreshes (bootstrap,
post-turn, /clear, /resume) enqueue the admin command and drain exactly
its returned batch id — a selected-batch drain structurally cannot
claim pending turn input, and the helper fails closed if a model turn
is unexpectedly reported. Post-turn reconciliation reads the
authoritative session read view and durable turn_input_applications
instead of the foreground TurnReport, and the live injected-input path
is shared with durable reconciliation.

The standard-slow-echo fixture now selects its reply from the request's
last visible user text so retained history cannot mask a missing turn;
a PTY e2e drives the full queue-during-turn flow and asserts request
order, both rendered rows, durable completion, and catalog-sync health.

Known limitation: with the automatic driver gone, idle process-wake
queued work has no interactive executor until the next user turn. No
wake producer is reachable in this CLI today (no process-engine
plugin); tracked as #13 with a fix sketch over existing facade
surfaces.

Fixes #12.

Release-Notes: messages queued for the next turn now always render and run visibly; previously they could execute invisibly when the post-turn tool-catalog sync claimed them.
- Refresh the quickstart embedding API sketch.
- Require fresh committed markers for queued-turn delivery.
- Clear stale capture before the cancellation idle gate.
- Match the current Session identity table in the resume picker.
- Align subagent dock wording and gate on settled output.

Release-Notes: none (runbook documentation only)
The Session/id gates otherwise match pre-overlay capture such as the
footer Idle stamp; a fresh capture confines them to the /info frame.

Release-Notes: none (runbook documentation only)
@SamGalanakis
SamGalanakis merged commit e184af1 into main Aug 26, 2026
4 checks passed
@SamGalanakis
SamGalanakis deleted the upgrade/lash-main branch August 26, 2026 20:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant