@vibelord/lanes 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/lanes/README.md +4 -3
- package/payload/lanes/VERSION +1 -1
- package/payload/lanes/bin/lanes +790 -31
- package/payload/lanes/container/hooks/grok-cli-hooks.json +8 -0
- package/payload/lanes/container/hooks/grok-hooks.json +12 -0
- package/payload/lanes/container/hooks/kimi-hooks.toml +49 -0
- package/payload/lanes/container/lane +13 -7
- package/payload/lanes/container/lane-mcp +56 -17
- package/payload/lanes/container/lane-signal +86 -23
- package/payload/lanes/container/lane_core.py +346 -36
- package/payload/lanes/container/throttle-pulse +456 -0
- package/payload/lanes/docs/ARCHITECTURE.md +165 -14
- package/payload/lanes/docs/DESIGN.md +7 -0
- package/payload/lanes/docs/INSTALL.md +23 -3
- package/payload/lanes/docs/RUNBOOK.md +392 -4
- package/payload/lanes/docs/USERGUIDE.md +222 -23
- package/payload/lanes/libexec/agent_state.py +604 -12
- package/payload/lanes/libexec/economy_poll.py +6 -2
- package/payload/lanes/libexec/harness_grok.py +742 -0
- package/payload/lanes/libexec/lanes_lib.py +143 -20
- package/payload/lanes/libexec/sidebar.py +522 -14
- package/payload/lanes/libexec/statusd.py +434 -28
- package/payload/lanes/libexec/tokens.py +12 -1
- package/payload/lanes/libexec/update.py +78 -5
- package/payload/lanes/libexec/users.py +31 -3
- package/payload/lanes/site/site.example.conf +4 -0
- package/payload/manifest.json +4 -3
|
@@ -604,6 +604,10 @@ would put the bad release back, is refused (install it by hand with `npx @vibelo
|
|
|
604
604
|
client folder afterwards. A question without an answer (no server, a busy one, or a server from before
|
|
605
605
|
1.2.0, which takes the flag for a device name) is only logged (`self-update:` lines in `launch-<target>.log`): the
|
|
606
606
|
connection that follows says what is wrong with the path.
|
|
607
|
+
- A client from before 1.2.0 does not update itself: its `lanes-launch` never asks `lanes hello --client-version`. Run
|
|
608
|
+
`npx @vibelord/lanes@1.5.0` once on that machine (your remembered options are given again); from then on its windows
|
|
609
|
+
follow the server by themselves. A 1.4.0 window asking a 1.5.0 server, for example, is answered ` update=1.5.0` and
|
|
610
|
+
installs exactly that release.
|
|
607
611
|
- A first install with an invite is the server's release too (W9 K3): the npm installer asks the enrolment service's
|
|
608
612
|
`/enrol/site` for the server's release before it installs anything and, when that is newer, runs `npx --yes
|
|
609
613
|
@vibelord/lanes@<release>` instead, which redeems the code ([Join with an invite](#join-with-an-invite)). The phone's
|
|
@@ -700,6 +704,7 @@ browser view). `lanes user show <login>` prints it.
|
|
|
700
704
|
| The lanes you opened, so a Done lane opened on the phone leaves Done on the Mac, and the last lane you opened | The device's own record of what it opened (`clients/<device>.seen.json`: an older release reads it) |
|
|
701
705
|
| Board order and hidden boards (`lanes workspaces order\|hide\|show`) | The layout (desktop or phone), viewports, the machine a window belongs to, its owner |
|
|
702
706
|
| Pinned and hidden lanes (`lanes lane pin\|unpin\|hide\|show`) | |
|
|
707
|
+
| The fleet block folded or not (the sidebar's `i`: `look.fleet_collapsed`) | A device nobody owns: that sidebar's own, until it restarts |
|
|
703
708
|
| Your own quiet window for pushes, `notify.quiet_s` (section 8; no verb sets it yet) | |
|
|
704
709
|
|
|
705
710
|
- **Per user or per device.** A look flag on a device with an owner goes to your profile, and every sidebar of every
|
|
@@ -823,7 +828,11 @@ Top to bottom, a desktop sidebar is:
|
|
|
823
828
|
would leave the pane. Then `zmx lanes (N)` when any exist, then **Unregistered (N)**
|
|
824
829
|
with its `+ New shell` row. A list longer than the pane ends in `⋮ N more`; moving onto that row or clicking it
|
|
825
830
|
scrolls instead of opening.
|
|
826
|
-
3. **
|
|
831
|
+
3. **Fleet block** (panes of 18 rows or more, once the server's statusd publishes the fleet facts), pinned above the
|
|
832
|
+
upload strip; the lane list scrolls in the rows left. Throttle in one row, a bar per provider, the host and its
|
|
833
|
+
biggest containers; `i` folds it to one line. What each row says, its colours and where the figures come from:
|
|
834
|
+
[Fleet](#fleet).
|
|
835
|
+
4. **Upload strip** (panes of 14 rows or more): ` ↑ uploads · click, then drop (U)` (section 5). Once this
|
|
827
836
|
workspace's lanes have published documents (the docs cockpit) it reads ` ↑ uploads · ▤ docs 3 (U)` — the count
|
|
828
837
|
is every document of its lanes, whoever published it — and the whole strip is a link to the docs page,
|
|
829
838
|
`https://<server>:8443/lanes-docs/` (⌘-click in Ghostty and iTerm2, Ctrl-click elsewhere); a plain click still
|
|
@@ -831,9 +840,12 @@ Top to bottom, a desktop sidebar is:
|
|
|
831
840
|
(`/lanes-docs/?ws=<workspace>&lane=<lane id>`) while it has any, and `d` shows the link in the footer. The links
|
|
832
841
|
need the server's Tailscale name (statusd learns it as the enrolment service does): without it the strip still
|
|
833
842
|
counts and `d` shows the path alone. What the page holds and who sees what: section 4, [Docs](#docs).
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
843
|
+
5. **Strip row** (panes of 18 rows or more): the fleet economy line when the opt-in poller runs (section 8) and the
|
|
844
|
+
fleet block's Throttle row does not show the overflow money and every part after it (no block, the block folded,
|
|
845
|
+
an untokened ` up …` or a ` down` Throttle row, or a row that dropped `spill` or `429` to fit the pane: the economy
|
|
846
|
+
line keeps its place), otherwise the other workspaces that need you, e.g. ` ◆1 Worlds · ◇2 Code`;
|
|
847
|
+
blank when there is nothing to say.
|
|
848
|
+
6. A rule, then the **footer**: one line, by priority.
|
|
837
849
|
|
|
838
850
|
A workspace whose lanes live on another SSH host (`sshWorkspaces` with `layout: "lanes"`) is attach-only: it has no
|
|
839
851
|
Unregistered section, no upload strip and no `+`, and its footer has no `x close`.
|
|
@@ -854,7 +866,8 @@ Unregistered section, no upload strip and no `+`, and its footer has no `x close
|
|
|
854
866
|
|
|
855
867
|
Marks and their ASCII twins: `▌` `|` cursor · `›` `=` viewing · `²` `2` / `³` `3` other viewport · `▲` `^` pinned · `·` `.` viewer ·
|
|
856
868
|
`…` `~` clipped · `×` `x` close · `+` `+` new / add · `⋮` `:` more rows · `↑` `^` uploads · `↓` `v` drop now · `▤` `#` docs ·
|
|
857
|
-
`⚠` `!` warning or starving · `↓` `v` downgraded · `$` `$` paid
|
|
869
|
+
`⚠` `!` warning or starving · `↓` `v` downgraded · `$` `$` paid · the fleet block's `⚙` `@` Throttle, `◆` `*` provider,
|
|
870
|
+
`▣` `[` host, and its bar `▰` `=` usable, `▱` `~` cooling, `▪` `:` benched, `·` `.` dead. The full tables, colours and mockups are in
|
|
858
871
|
[DESIGN.md](DESIGN.md).
|
|
859
872
|
|
|
860
873
|
**Footer**, highest priority first: the open prompt — the sidebar asks one thing at a time, in one of two shapes. A
|
|
@@ -871,7 +884,7 @@ draws the same footer (its picker says ` Choose a workspace · Tap Cancel`, and
|
|
|
871
884
|
idle). An empty workspace shows `no lanes declared · + New shell` on its New shell row.
|
|
872
885
|
|
|
873
886
|
**Sidebar keys** (`?` shows the short form of this table in place of the list until the next key; that key only closes
|
|
874
|
-
the sheet). Its first row says which lanes: `lanes <installed version> · view <the version this sidebar runs>`. The sheet puts companion keys on one line (`J K`, `Tab S-Tab`, `+ U d`, `u z`, `p h`, `H o`, `r ?`), leaves `PageUp` / `PageDown`
|
|
887
|
+
the sheet). Its first row says which lanes: `lanes <installed version> · view <the version this sidebar runs>`. The sheet puts companion keys on one line (`J K`, `Tab S-Tab`, `+ U d`, `u z`, `p h`, `H o`, `r i ?`), leaves `PageUp` / `PageDown`
|
|
875
888
|
to this table and ends with `Ctrl-Alt-l` — not a sidebar key but the chord that brings the keyboard here from a lane
|
|
876
889
|
(section [Keybindings](#keybindings)). Every key below does something in the sidebar, and no other key does
|
|
877
890
|
(`tests/unit/test_sidebar.py` `DocumentedKeys` presses each one and every other key a keyboard sends):
|
|
@@ -898,6 +911,7 @@ to this table and ends with `Ctrl-Alt-l` — not a sidebar key but the chord tha
|
|
|
898
911
|
| `h` | Hide the cursor lane on every device you own (` Hid ‘name’ · o shows it`); on a hidden lane `o` shows, show it again |
|
|
899
912
|
| `H` | Hide this workspace on every device you own: `y` arms, `Enter` hides — its tab closes, its lanes keep running (the last workspace shown stays) |
|
|
900
913
|
| `o` | Show / hide again the lanes you hid (shown faint) |
|
|
914
|
+
| `i` | Fold the fleet block to one line, or unfold it, on every device you own (` fleet folded · i unfolds`); then the keyboard goes back to the lane |
|
|
901
915
|
| `U` | Arm the upload strip |
|
|
902
916
|
| `d` | Show the cursor lane's docs link in the footer for 3 s: `https://<server>:8443/lanes-docs/?ws=<workspace>&lane=<lane id>`, as much as the row holds, the whole line one link to all of it (⌘/Ctrl-click, or your terminal's Copy Link); then the keyboard goes back to the lane. ` no docs in ‘name’` on a lane without documents |
|
|
903
917
|
| `r` | Reload and repaint; while the footer says `update ready · reload (r)`, rebuild this view on the installed release (the window reconnects, agents are untouched); the keyboard goes back to the lane |
|
|
@@ -1642,7 +1656,10 @@ Names must be non-empty and free of control characters. The id, resolve file and
|
|
|
1642
1656
|
each container running, the statusd heartbeat, all registry lanes observed and present (stale lanes counted as
|
|
1643
1657
|
"shown with their last known state"), the process table, no host-side tmux servers, coexistence mode, Zellij
|
|
1644
1658
|
reachable, phone picker panes current, and the host not under critical memory pressure.
|
|
1645
|
-
- Information:
|
|
1659
|
+
- Information: the harness mix (1.5.0, [Harnesses](#harnesses)): `lanes: claude 50 · codex 20 · kimi 3 · grok 0 ·
|
|
1660
|
+
grok-cli 0 · shell 0`, from each lane's `agent.harness` (`unknown` only when a lane has it; a lane with no agent is
|
|
1661
|
+
not counted; `shell` stays 0 in practice: a lane whose harness ended into its shell is published under that
|
|
1662
|
+
harness's name with the status `shell`); lanes per create-recipe class (`idempotent`, `attach-only`, `null`, `external`, `retired-path`), how many
|
|
1646
1663
|
create with their identity, where the `lane` contract is installed, zmx-held lanes, the preview renderer, the live
|
|
1647
1664
|
view sessions, and the pressure summary.
|
|
1648
1665
|
- Warnings (`⚠`, never a `✗`): lanes that `lane open` cannot recreate (fix: `lanes cursor-sync --canonicalize`),
|
|
@@ -1661,6 +1678,14 @@ Names must be non-empty and free of control characters. The id, resolve file and
|
|
|
1661
1678
|
header check is pinned`), the store (documents, bytes, `docs.log`'s size), the pinned Mermaid (`✓`, `⚠` not installed
|
|
1662
1679
|
— `install/enable-docs.sh`, which every server install and update runs —, `✗` a file that is not the pinned build) and
|
|
1663
1680
|
the `age` switch (`✗` when it is set without an `age` binary); RUNBOOK "Docs service" has every line.
|
|
1681
|
+
- The fleet (W11, [Fleet](#fleet)): `throttle pulse: 4 s old (container bw-tools), daemon up, tokened: no — lanes
|
|
1682
|
+
throttle adopt-token bw-tools` (`✓`; `⚠` when the daemon does not answer, when the pulse is more than 90 s old — it
|
|
1683
|
+
stopped — or when status.json has none yet, with `lanes install-container <container>`, which starts it), `throttle
|
|
1684
|
+
pools: claude 12/19 · codex 3/5 · kimi 0/0 usable` (1.5.0: the three pools the pulse reports; `?` for one it did not,
|
|
1685
|
+
`⚠` when a configured pool has no usable account), `fleet: cpu
|
|
1686
|
+
42% · mem 61% · psi 3% · containers 7` (psi is the highest PSI avg10; `⚠` when stale or missing — a statusd from
|
|
1687
|
+
before 1.4.0), and `fleet dashboard: LANES_FLEET_DASHBOARD_URL unset`, or the URL, or `⚠ … ignored — not an https
|
|
1688
|
+
URL`. None of them is ever a `✗`: Lanes runs without Throttle.
|
|
1664
1689
|
- The release that runs, against `~/.local/share/lanes/current` (the tree an install makes current; without the link,
|
|
1665
1690
|
a `⚠` and this `lanes`'s own tree): the link names a tree that exists, whose `VERSION` agrees with its
|
|
1666
1691
|
`pkg/<version>` name and its npm stamp, and that this `lanes` runs (`✗` with the fix: run the current one, or re-run
|
|
@@ -1698,11 +1723,15 @@ anything, picks the first signal that applies:
|
|
|
1698
1723
|
turn keep it, unless the report was `working`.
|
|
1699
1724
|
2. A **Claude Code session file** `~/.claude/sessions/<pid>.json` naming the pane, for a live pid (recycled pids are
|
|
1700
1725
|
rejected).
|
|
1701
|
-
3.
|
|
1702
|
-
|
|
1703
|
-
4. The **
|
|
1726
|
+
3. **Kimi Code's session files**, then **Grok Build's** (which session the pane's `grok` runs, its event log, else its
|
|
1727
|
+
pane title), for a pane whose process is that harness ([Harnesses](#harnesses)).
|
|
1728
|
+
4. The **Codex pane title**, only where Codex can run: `[ ! ] Action Required` is needs approval, a braille spinner is
|
|
1729
|
+
working, a task title is idle.
|
|
1730
|
+
5. The **harness process itself**: nothing above claimed the pane, so it is the harness the pane runs, idle.
|
|
1731
|
+
6. The **window bell**, throughout: an idle lane or a finished turn plus a bell is done.
|
|
1704
1732
|
|
|
1705
|
-
A Codex title left on a bare shell with no child process
|
|
1733
|
+
A Codex title left on a bare shell with no child process, or a Grok Build title over a pane that runs only shells, is
|
|
1734
|
+
`shell` (the harness ended). Since 1.5.0 the harness itself is observed, not guessed: see [Harnesses](#harnesses).
|
|
1706
1735
|
|
|
1707
1736
|
What the sidebar shows is not that word itself but a debounced one. The sections rank Needs you > Working > Done > the
|
|
1708
1737
|
workspace's other lanes: a status that moves the lane up is shown at once, one that moves it down only after it has
|
|
@@ -1715,6 +1744,107 @@ starts signals Working (`PreToolUse`); that hook fires before the permission dia
|
|
|
1715
1744
|
A Codex approval read from its pane title (a lane without hooks) keeps the same 8 s dwell once approved. The full rules:
|
|
1716
1745
|
[Attention queue](ARCHITECTURE.md#attention-queue).
|
|
1717
1746
|
|
|
1747
|
+
### Harnesses
|
|
1748
|
+
|
|
1749
|
+
Lanes 1.5.0 (W12, [spec](../../docs/superpowers/specs/2026-10-15-lanes-harness-observers-design.md)) reads five agent
|
|
1750
|
+
harnesses: Claude Code, Codex, Kimi Code, Grok Build (xAI's official `grok`) and grok-cli (the community `grok`).
|
|
1751
|
+
Which one runs in a lane is an **observed fact**: statusd walks the pane's process tree (plain `/proc` reads, no new
|
|
1752
|
+
process) and publishes `agent.harness` as `claude`, `codex`, `kimi` (Kimi Code), `grok` (Grok Build), `grok-cli` or
|
|
1753
|
+
`unknown`; a pane that runs only shells is `shell` to the walk and shows no agent of its own (or the harness that ended
|
|
1754
|
+
there, with the status shell). A pane title that looks like Codex's is read as Codex only where Codex runs: before 1.5.0 any
|
|
1755
|
+
title of two or more words read as an idle Codex, so a Kimi lane showed `codex / idle`. `lanes doctor` prints the mix:
|
|
1756
|
+
`lanes: claude 50 · codex 20 · kimi 3 · grok 0 · grok-cli 0 · shell 0`. Each harness gets the lane-signal hooks, the
|
|
1757
|
+
Lanes MCP server and the Throttle trace its own files allow:
|
|
1758
|
+
|
|
1759
|
+
| Harness | What Lanes reads with no hooks (observed) | Hooks (`lanes hooks install --agent …`, a dry run until `--write`) | MCP server |
|
|
1760
|
+
| --- | --- | --- | --- |
|
|
1761
|
+
| Claude Code | `~/.claude/sessions/<pid>.json`, the window bell | `--agent claude`: `~/.claude/settings.json` ("Hooks" below) | `~/.claude.json` ("Agent tools (MCP)" below) |
|
|
1762
|
+
| Codex | the pane title, the window bell | `--agent codex`: `~/.codex/hooks.json` and `notify` | `~/.codex/config.toml` |
|
|
1763
|
+
| Kimi Code 2.1.1 | its session files: `~/.kimi-code/sessions/wd_*/session_<uuid>/state.json` and the last 64 KiB of `agents/main/wire.jsonl` | `--agent kimi`: a `# >>> lanes hooks >>>` block of `[[hooks]]` in `~/.kimi-code/config.toml` | `~/.kimi-code/mcp.json` |
|
|
1764
|
+
| Grok Build 1.0.52 | `$GROK_HOME/active_sessions.json` (which pid runs which session), the session's `events.jsonl` and `summary.json` (its title), the pane title | `--agent grok`: `~/.grok-build/hooks/lanes.json` plus blocks in `~/.grok-build/config.toml` | a `# >>> lanes mcp >>>` block in `~/.grok-build/config.toml` |
|
|
1765
|
+
| grok-cli 1.1.7 | nothing: it sets no title and rings no bell | `--agent grok-cli`: `hooks` in `~/.grok/user-settings.json` | the `mcp.servers` item `lanes` in `~/.grok/user-settings.json` |
|
|
1766
|
+
|
|
1767
|
+
Per container, on the server (each a dry run that prints its diff until `--write`):
|
|
1768
|
+
|
|
1769
|
+
```sh
|
|
1770
|
+
lanes hooks install --agent kimi --container <name> --write # Kimi Code: ~/.kimi-code/config.toml
|
|
1771
|
+
lanes hooks install --agent grok --container <name> --write # Grok Build: ~/.grok-build/hooks/lanes.json, config.toml
|
|
1772
|
+
lanes hooks install --agent grok-cli --container <name> --write # grok-cli: ~/.grok/user-settings.json
|
|
1773
|
+
```
|
|
1774
|
+
|
|
1775
|
+
What the hooks of the three newer harnesses give the boards (Claude Code's and Codex's: "Hooks" below):
|
|
1776
|
+
|
|
1777
|
+
| Harness | Event | Status |
|
|
1778
|
+
| --- | --- | --- |
|
|
1779
|
+
| Kimi Code | `UserPromptSubmit`, `TurnStarted`, `PreToolUse`, `PostToolUse`, `PermissionResult` | working |
|
|
1780
|
+
| Kimi Code | `PermissionRequest` | needs_approval |
|
|
1781
|
+
| Kimi Code | `Stop`, `Interrupt` | needs_input |
|
|
1782
|
+
| Kimi Code | `StopFailure` | errored |
|
|
1783
|
+
| Kimi Code | `SessionStart` / `SessionEnd` | idle / shell |
|
|
1784
|
+
| Grok Build | `UserPromptSubmit`, `PreToolUse`, `PostToolUse` | working |
|
|
1785
|
+
| Grok Build | `Notification` (permission prompt) | needs_approval |
|
|
1786
|
+
| Grok Build | `Stop`, `StopCancelled`, `Notification` (idle prompt) | needs_input |
|
|
1787
|
+
| Grok Build | `StopFailure` | errored |
|
|
1788
|
+
| Grok Build | `SessionStart` / `SessionEnd` | idle / shell |
|
|
1789
|
+
| grok-cli | `UserPromptSubmit`, `PreToolUse`, `PostToolUse` | working |
|
|
1790
|
+
| grok-cli | `Stop` / `StopFailure` / `SessionEnd` | needs_input / errored / shell |
|
|
1791
|
+
|
|
1792
|
+
**Kimi Code** comes from its own installer (`~/.kimi-code`). Without hooks, a Kimi session is joined to its lane by
|
|
1793
|
+
working folder: the lane's Kimi process whose folder is the session's `cwd`, newest `updatedAt` first. Two Kimi lanes
|
|
1794
|
+
in one folder are ambiguous, and then the lane falls back to hooks and the title. From `wire.jsonl`: a turn that
|
|
1795
|
+
starts, a prompt or a tool call is working; an approval request not yet resolved is needs approval; a finished turn is
|
|
1796
|
+
needs input (done when the window rang); a failed turn is errored. A session file more than an hour old says nothing.
|
|
1797
|
+
The hooks make it event-driven. Lanes writes only its own marked block into `~/.kimi-code/config.toml` and never
|
|
1798
|
+
rewrites the rest of the file; it never touches Kimi's device identity files. Kimi rings the terminal bell only when
|
|
1799
|
+
told to: `lanes onboard`'s optional `--step kimi-notify` sets `notification_condition = "always"` in
|
|
1800
|
+
`~/.kimi-code/tui.toml`, so Kimi Code rings the bell focused or not (the operator decides, per container). Kimi's own
|
|
1801
|
+
default file already has the `[notifications]` table: that one line is edited and its old value kept in a comment.
|
|
1802
|
+
|
|
1803
|
+
**Grok Build lives in `GROK_HOME=~/.grok-build`, never `~/.grok`.** Its own installer would replace grok-cli's
|
|
1804
|
+
`~/.grok/bin/grok` with a link to itself, so every lane recipe sets `GROK_HOME=/home/coder/.grok-build`, and
|
|
1805
|
+
`lanes onboard <login> --container <name> --step grok-build --write` installs it there from the stable channel
|
|
1806
|
+
(`https://x.ai/cli/stable`, the binary and its `bin/grok` link, `[cli] installer = "internal"`). That channel publishes
|
|
1807
|
+
no checksum, so the receipt records the download's size and sha256 as unverified for you to check. grok-cli keeps
|
|
1808
|
+
`~/.grok`.
|
|
1809
|
+
|
|
1810
|
+
**Grok Build without hooks.** From `events.jsonl`: a permission prompt is needs approval; waiting for the model,
|
|
1811
|
+
streaming or running a tool is working; a completed turn is needs input (done when the window rang), an error
|
|
1812
|
+
errored, a cancelled or interrupted turn needs input. The title says the same: `⚠ Action Required …` is needs approval
|
|
1813
|
+
(it blinks while the pane is not focused, so a busy title right after it still reads needs approval), a braille spinner
|
|
1814
|
+
is working, `grok` or `<name> - grok` is idle; a Grok title left over a pane that runs only shells is shell. Grok
|
|
1815
|
+
Build's leader mode (`use_leader = true`) is not read: such a lane shows no agent state until its hooks speak.
|
|
1816
|
+
|
|
1817
|
+
**Grok Build's hooks install** writes the hooks file (the events above; it runs Claude Code's hooks by default, which
|
|
1818
|
+
would report `claude`) and two options in `~/.grok-build/config.toml`: `[compat.claude] hooks = false, mcps = false`,
|
|
1819
|
+
so Claude Code's hooks and its `lane-mcp` entry do not also run inside Grok, and `[ui.notifications] condition =
|
|
1820
|
+
"always", method = "bel"`, so every turn end, approval and error rings the pane's bell. If the file already has a
|
|
1821
|
+
`[compat.claude]` or `[ui.notifications]` table of yours, the install changes nothing and asks you to set those keys
|
|
1822
|
+
there.
|
|
1823
|
+
|
|
1824
|
+
**grok-cli** comes from its own installer (`~/.grok`) and has hooks only, with exact-string matchers. It has no
|
|
1825
|
+
approval signal and no title or bell: its lane shows working, needs input, errored or shell, never needs approval.
|
|
1826
|
+
|
|
1827
|
+
**Agent tools (MCP).** `~/.lz/bin/lane mcp install` (which `lanes install-container` runs, or `lane mcp install
|
|
1828
|
+
--agent kimi|grok|grok-cli`) writes the Lanes server into each harness's file in the table above: Kimi Code's
|
|
1829
|
+
`~/.kimi-code/mcp.json` (the Claude `mcpServers` shape), Grok Build's `[mcp_servers.lanes]` as a marked block (its
|
|
1830
|
+
`[compat.claude] mcps = false` keeps `lane-mcp` from starting twice; Grok Build passes its session id to the server),
|
|
1831
|
+
and grok-cli's `mcp.servers`. grok-cli starts MCP servers with a stripped environment, so `lanes_whoami` reads the
|
|
1832
|
+
lane's identity from the environment of the grok-cli process that started it.
|
|
1833
|
+
|
|
1834
|
+
**The Throttle trace.** Kimi Code blanks `ANTHROPIC_CUSTOM_HEADERS`, so every recipe that sets it also sets
|
|
1835
|
+
`KIMI_CODE_CUSTOM_HEADERS='x-throttle-trace: lanes/<ws>/<id>'`. Grok Build's global extra headers would trace every
|
|
1836
|
+
lane alike, so recipes export `LANES_TRACE` and its hooks install writes `env_http_headers = {"x-throttle-trace" =
|
|
1837
|
+
"LANES_TRACE"}` for its built-in models `grok-4.6` and `grok-4.5`. grok-cli sends no custom headers: its requests carry
|
|
1838
|
+
no trace. A trace only matters for a harness attached to a Throttle pool: Kimi Code when `throttle setup
|
|
1839
|
+
--install-clients` attached it, Grok not at all (Throttle has no xAI pool).
|
|
1840
|
+
|
|
1841
|
+
**Files Lanes changes.** Each is kept as `<file>.bak-<UTC>` first. In a TOML file Lanes changes only its marked block,
|
|
1842
|
+
so to undo, delete the block. The diff a dry run prints shows a credential's value (grok-cli's `apiKey`) as `…`.
|
|
1843
|
+
`lane-signal` without `--harness` takes the harness from `LANE_HARNESS`, else from the agent process above it, else
|
|
1844
|
+
records `unknown`.
|
|
1845
|
+
|
|
1846
|
+
Operator steps per container: [RUNBOOK "Harnesses"](RUNBOOK.md#harnesses).
|
|
1847
|
+
|
|
1718
1848
|
### A user's agents in one step: `lanes onboard`
|
|
1719
1849
|
|
|
1720
1850
|
On the server, one command gives a new user's workspace container what its agents need. Without `--write` it prints
|
|
@@ -1730,13 +1860,14 @@ lanes onboard anna --container ws-anna --workspace anna-tools --repo /home/coder
|
|
|
1730
1860
|
| Board | A workspace tab for the repository (`lanes workspaces add`, section 6) on every device's view |
|
|
1731
1861
|
| Contract | `lane`, `lane-run`, `lane-signal`, `lane-edit`, the quick-edit `vimrc`, `lane-mcp` and the hook definitions in the container's `~/.lz` (`lanes install-container`) |
|
|
1732
1862
|
| Agent tools | The Lanes MCP server in Claude Code's and Codex's configuration, so an agent can see the lanes around it ("Agent tools (MCP)" below) |
|
|
1733
|
-
| Hooks | The lane-signal hooks for Claude Code and Codex ("Hooks" below), so the sidebar's agent glyphs are event-driven |
|
|
1863
|
+
| Hooks | The lane-signal hooks for Claude Code and Codex ("Hooks" below), and for Kimi Code, Grok Build and grok-cli where they are installed ([Harnesses](#harnesses)), so the sidebar's agent glyphs are event-driven |
|
|
1734
1864
|
| Throttle | `throttle setup --install-clients` inside the container when Throttle is installed and configured there, so the agents' requests go through the pool; otherwise the one-time setup to run on a terminal is printed (a first setup signs in, which needs a person; Lanes runs without Throttle) |
|
|
1735
1865
|
| Recipes | `lanes cursor-sync <id> --canonicalize --write` when the repository has a `.vscode/terminals.json`: each lane's create recipe gives it its identity and trace ("Lane identity" below) |
|
|
1736
1866
|
| Sync and doctor | The registry knows the new lanes; `lanes doctor`'s result is reported, never fatal |
|
|
1737
1867
|
|
|
1738
1868
|
The receipt is `~/.local/state/lanes/onboard/<login>-<UTC>.json`, and running the command again changes nothing that
|
|
1739
|
-
is already done. `--no-mcp` leaves out `lane-mcp` and its entries in the agents' configuration.
|
|
1869
|
+
is already done. `--no-mcp` leaves out `lane-mcp` and its entries in the agents' configuration. Two steps run only
|
|
1870
|
+
when named: `--step kimi-notify` and `--step grok-build` ([Harnesses](#harnesses)). Every step
|
|
1740
1871
|
and status: [install guide](INSTALL.md#onboard-a-users-container).
|
|
1741
1872
|
|
|
1742
1873
|
**Laptop agents on the pool (operator-gated).** Agents a user runs on their own laptop, outside any lane, can use the
|
|
@@ -1757,10 +1888,11 @@ lanes hooks install --agent claude --container <name> --write # applies it (b
|
|
|
1757
1888
|
lanes hooks install --agent codex --container <name> --write # ~/.codex/hooks.json (+ notify in config.toml)
|
|
1758
1889
|
```
|
|
1759
1890
|
|
|
1760
|
-
`lanes hooks install --agent claude|codex (--container <name> | --home <dir>) [--dry-run | --write]
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1891
|
+
`lanes hooks install --agent claude|codex|kimi|grok|grok-cli (--container <name> | --home <dir>) [--dry-run | --write]`
|
|
1892
|
+
is a dry run unless `--write` (Kimi Code, Grok Build and grok-cli: [Harnesses](#harnesses)); your own hooks and
|
|
1893
|
+
keys stay, and an earlier lane-signal install is replaced, not duplicated. An install that is already current is
|
|
1894
|
+
left where it is, even when another tool (Throttle's `setup --install-clients`) has put its own group before or
|
|
1895
|
+
after it or re-sorted the file's keys, so running it again writes nothing. For
|
|
1764
1896
|
Codex it also adds a top-level `notify` to `~/.codex/config.toml` only when none exists. Codex asks you to review and
|
|
1765
1897
|
trust new hooks; Lanes never writes that trust. Expect hooks to reach agent sessions started after the install
|
|
1766
1898
|
(inferred from how both harnesses read their configuration, not measured). Undo:
|
|
@@ -1799,7 +1931,7 @@ verbs, so the holder stays swappable.
|
|
|
1799
1931
|
| `lane kill <id> --yes [--force]` | End the lane (the only verb that ends an agent); refuses while the hook sidecar says `working` unless `--force` |
|
|
1800
1932
|
| `lane new <id> [--ws W] [--title T] [--cwd DIR] [--icon I] [--color C] -- <command…>` | Create a zmx-held lane |
|
|
1801
1933
|
| `lane gc --sockets` | Unlink this container's tmux socket files that tmux is refused on (no server listens); a live, empty or silent server keeps its file |
|
|
1802
|
-
| `lane mcp install [--agent claude\|codex\|all] [--write]` | Add the Lanes MCP server to `~/.claude.json` and `~/.codex/config.toml
|
|
1934
|
+
| `lane mcp install [--agent claude\|codex\|kimi\|grok\|grok-cli\|all] [--write]` | Add the Lanes MCP server to `~/.claude.json` and `~/.codex/config.toml`, and to Kimi Code's, Grok Build's and grok-cli's files where they are installed ("Agent tools (MCP)" below); a dry run until `--write` |
|
|
1803
1935
|
| `lane publish <file> [--title T] [--for <login>] [--kind md\|html\|svg\|file]` | Put a file of this container on the docs page from the lane it runs in ("Publish to the docs page" below); prints the request as JSON |
|
|
1804
1936
|
|
|
1805
1937
|
Every tmux client these verbs start runs with `-N`, so none of them can start a tmux server: an attach to a lane
|
|
@@ -1842,7 +1974,7 @@ only read:
|
|
|
1842
1974
|
| Tool | Answers |
|
|
1843
1975
|
| --- | --- |
|
|
1844
1976
|
| `lanes_whoami` | Which lane the agent runs in: id, board, name, kind (declared, registered or unregistered), tmux socket and session. It comes from `LANE_ID`, else from the agent's own tmux pane. Outside a lane the answer is `lane: null` with the reason |
|
|
1845
|
-
| `lanes_list` | Every lane in this container with its state and its agent (harness, status, task). Filters: `workspace`, `needs_you: true` (needs approval, errored or needs input, the sidebar's "Needs you" section) and `agent` (`any`, `none`, `claude`, `codex`). `status.fresh` is false once the host's observation is more than 60 s old |
|
|
1977
|
+
| `lanes_list` | Every lane in this container with its state and its agent (harness, status, task). Filters: `workspace`, `needs_you: true` (needs approval, errored or needs input, the sidebar's "Needs you" section) and `agent` (`any`, `none`, `claude`, `codex`, `kimi`, `grok` for Grok Build, `grok-cli`). `status.fresh` is false once the host's observation is more than 60 s old |
|
|
1846
1978
|
| `lanes_status` | One lane in full: what the registry says, the observed state and agent, whether its tmux session is alive now (`null` when tmux does not answer in 5 s) and its hook sidecar |
|
|
1847
1979
|
| `lanes_capture` | The last `lines` (1 to 500, default 100) non-blank lines of a lane's screen and scrollback, as plain text. Any lane of the container can be read |
|
|
1848
1980
|
|
|
@@ -1924,13 +2056,17 @@ are in the [runbook](RUNBOOK.md#install-check-uninstall). What to tell your agen
|
|
|
1924
2056
|
### Lane identity and the canonical recipe
|
|
1925
2057
|
|
|
1926
2058
|
A lane's identity is its environment: `LANE_ID` (`<ws>--<slug>`), `LANE_WS` (`<ws>`), and the Throttle trace
|
|
1927
|
-
`lanes/<ws>/<id>` as `THROTTLE_TRACE
|
|
2059
|
+
`lanes/<ws>/<id>` as `THROTTLE_TRACE`, as `LANES_TRACE` (Grok Build's `env_http_headers` read it), and as
|
|
2060
|
+
`ANTHROPIC_CUSTOM_HEADERS` and `KIMI_CODE_CUSTOM_HEADERS` (`'x-throttle-trace: lanes/<ws>/<id>'`; Kimi Code ignores the
|
|
2061
|
+
Anthropic one). The recipe also sets `GROK_HOME=/home/coder/.grok-build` ("Harnesses" above). A tmux
|
|
1928
2062
|
lane gets it only from the `-e` flags of its create recipe; `lane open` deliberately exports none (the client that
|
|
1929
2063
|
starts a shared tmux server would hand its environment to every later session there). The canonical recipe is:
|
|
1930
2064
|
|
|
1931
2065
|
```sh
|
|
1932
2066
|
tmux -L <socket> new -ADs <session> -c <folder> -e LANE_ID=<id> -e LANE_WS=<ws> \
|
|
1933
|
-
-e ANTHROPIC_CUSTOM_HEADERS='x-throttle-trace: lanes/<ws>/<id>' -e THROTTLE_TRACE=lanes/<ws>/<id>
|
|
2067
|
+
-e ANTHROPIC_CUSTOM_HEADERS='x-throttle-trace: lanes/<ws>/<id>' -e THROTTLE_TRACE=lanes/<ws>/<id> \
|
|
2068
|
+
-e KIMI_CODE_CUSTOM_HEADERS='x-throttle-trace: lanes/<ws>/<id>' -e LANES_TRACE=lanes/<ws>/<id> \
|
|
2069
|
+
-e GROK_HOME=/home/coder/.grok-build bash -l
|
|
1934
2070
|
```
|
|
1935
2071
|
|
|
1936
2072
|
`-A` attaches when the session exists, so the identity lands only on a real create. A running lane gains it when it is
|
|
@@ -1944,6 +2080,69 @@ zmx lane's agent state yet (DEF-1573). Gate results: [ZMX-RING0.md](ZMX-RING0.md
|
|
|
1944
2080
|
|
|
1945
2081
|
## 8. Economy and notifications
|
|
1946
2082
|
|
|
2083
|
+
### Fleet
|
|
2084
|
+
|
|
2085
|
+
The sidebar's free rows hold a pinned health block (Lanes 1.4.0, W11): on panes of 18 rows or more, once the server's
|
|
2086
|
+
statusd publishes the fleet facts, it sits above the upload strip and the lane list scrolls in the rows left. It is
|
|
2087
|
+
live (statusd rewrites it at most every 10 s), local, and needs no collector. At 25 rows or more it is a rule and six
|
|
2088
|
+
rows (1.5.0 added the Kimi pool's row; 1.4.0 drew five from 24 rows):
|
|
2089
|
+
|
|
2090
|
+
```
|
|
2091
|
+
── fleet ──────────────────────────
|
|
2092
|
+
⚙ throttle $8.5k/16k 53% · 429 +4 · spill 76%
|
|
2093
|
+
◆ anthropic ▰▰▰▰▰▰▱▱▱▱ 12/19 · wk 61% · reset 2h
|
|
2094
|
+
◆ openai ▰▰▰▱▱▱▱▱▱▱ 3/5 · 5h 28%
|
|
2095
|
+
◆ moonshot ▰▰▰▰▰▰▱▪▪· 5/8 · wk 44% · 5h 12%
|
|
2096
|
+
▣ productbox cpu 42% · mem 61% · load 37 · psi 3%
|
|
2097
|
+
▣ bw-tools 15G · worlds 9G · sakio 4G
|
|
2098
|
+
```
|
|
2099
|
+
|
|
2100
|
+
| Row | What it shows |
|
|
2101
|
+
| --- | --- |
|
|
2102
|
+
| `⚙ throttle` | One line for the Throttle pool: with the opt-in token, overflow spend today against the daily cap, new 429s and the spill fraction — one pool's figure, never a sum: the Anthropic and OpenAI overflow budgets are separate money, so the row shows the pool nearest its cap, one at its alert level first (Kimi has no paid overflow, so its pool never gives the money); without the token ` ⚙ throttle up · spill 76% · 429 +4`, and ` down` in red when the daemon does not answer |
|
|
2103
|
+
| `◆ anthropic`, `◆ openai`, `◆ moonshot` | One availability bar per provider pool — Claude, Codex and (1.5.0) Kimi Code's, whose vendor is Moonshot AI: ten cells of usable `▰`, cooling `▱`, benched `▪` and dead `·` accounts (ASCII `=` `~` `:` `.`), then the usable count of the configured ones (` ◆ anthropic ▰▰▰▰▰▰▱▪·· 12/19`), then — with the token — the worst weekly or 5-hour burn and the soonest reset. A daemon with no Kimi account shows ` ◆ moonshot ·········· 0/0`, which is no alarm; `no data` means the pulse did not report the pool (a pulse from before 1.5.0 still running: [RUNBOOK](RUNBOOK.md#harnesses)). There is no Grok row: Throttle has no xAI pool |
|
|
2104
|
+
| `▣ productbox` | The host: CPU and memory utilization, the 1-minute load, the highest pressure (PSI) figure |
|
|
2105
|
+
| `▣ <container> …` | The three workspace containers using the most memory |
|
|
2106
|
+
|
|
2107
|
+
At 18–24 rows the block is three rows — Throttle, the provider with the smallest usable share (a pool with nothing
|
|
2108
|
+
configured is never that one), the host — and smaller panes show none. Parts that do not fit are dropped from the end. A row turns amber when overflow spend reaches its alert
|
|
2109
|
+
level, a provider has no usable account, memory is fully stalled, memory use reaches 90 % or the CPU 95 % (its lead
|
|
2110
|
+
mark turns amber even when that part was dropped); red when the daemon does not answer. The whole block is dim, with a
|
|
2111
|
+
`?` after `throttle`, when its figures are frozen because statusd or the Throttle pulse stopped: the host's more than a
|
|
2112
|
+
minute old, the pulse's more than 90 s (it rewrites every 60 s and is read up to about 84 s later).
|
|
2113
|
+
|
|
2114
|
+
**`i`** folds the block to one line, ` fleet ✓ · throttle 53% · cpu 42% · mem 61%` (`⚠` something amber, `✗` Throttle
|
|
2115
|
+
down), and unfolds it again; the choice is kept in your profile (`look.fleet_collapsed`), so every device you own
|
|
2116
|
+
follows it. The `?` key sheet takes the block's rows while it shows. On the phone the same block is drawn above the
|
|
2117
|
+
footer.
|
|
2118
|
+
|
|
2119
|
+
The strip row keeps its economy line and gives way only when the block already shows the same facts: while the
|
|
2120
|
+
unfolded block's Throttle row shows the overflow money and every part after it, the strip row leaves the economy line
|
|
2121
|
+
to it. At the default 36 columns that row drops `spill` to fit, so the strip keeps the line; a wider pane shows the
|
|
2122
|
+
whole row.
|
|
2123
|
+
|
|
2124
|
+
Where the figures come from: the host's own `/proc` (statusd, skipped while the host is under critical pressure) and a
|
|
2125
|
+
small pulse that runs inside the container where the Throttle daemon runs — the daemon listens only on that container's
|
|
2126
|
+
loopback, so the host cannot ask it itself. `lanes install-container` starts that pulse in the daemon's container (the
|
|
2127
|
+
one `LANES_THROTTLE_CONTAINER` in the server's `~/.config/lanes/site.conf` names, else the container of the Throttle
|
|
2128
|
+
tab) and every server update ships it again. The pulse only reads (GET), never changes the daemon, and writes one small
|
|
2129
|
+
file.
|
|
2130
|
+
|
|
2131
|
+
**Money and burn are opt-in.** Without a token the pulse reads only the daemon's open routes (readiness and metrics):
|
|
2132
|
+
no spend, no burn figures. The operator turns them on once, per daemon container: `lanes throttle adopt-token
|
|
2133
|
+
<container>` copies the container's own local Throttle client token (the local proxy bearer in its
|
|
2134
|
+
`~/.claude/settings.json`, never an Anthropic credential) into the pulse's token file (`~/.lz/throttle-token`, mode
|
|
2135
|
+
0600). Nothing does it automatically.
|
|
2136
|
+
|
|
2137
|
+
**The dashboard link is off until it is safe.** History (days, weeks) belongs to the fleet-management Grafana, not to
|
|
2138
|
+
the cockpit. When the server's `~/.config/lanes/site.conf` names `LANES_FLEET_DASHBOARD_URL=https://…`, the
|
|
2139
|
+
`▣ productbox` row becomes a link to it and the phone's menu gains a **Fleet** row. It stays unset until an
|
|
2140
|
+
authenticated Grafana deployment exists; only an `https://host[:port][/path]` URL is accepted, and no Lanes command
|
|
2141
|
+
ever writes it. `lanes doctor` says which it is.
|
|
2142
|
+
|
|
2143
|
+
The host the block names is the Lanes server itself: the Hetzner machine `productbox` (tailnet address 100.91.241.52,
|
|
2144
|
+
the address the bundled `beyondwork` site profile connects to), not `devbox3`.
|
|
2145
|
+
|
|
1947
2146
|
### Economy projection (opt-in, operator gate)
|
|
1948
2147
|
|
|
1949
2148
|
`lanes-economy.service` polls the Throttle daemon's control port (`GET /status` and `/sessions`, default
|
|
@@ -1959,7 +2158,7 @@ systemctl --user enable --now lanes-economy.service
|
|
|
1959
2158
|
|
|
1960
2159
|
`lanes economy --once [--dry-run] [--status-file F --sessions-file F]` runs one poll; stderr names each source it
|
|
1961
2160
|
could not read. Settings: `LANES_THROTTLE_CONTROL` (base URL, or `none` for no request), `LANES_THROTTLE_TOKEN_FILE`
|
|
1962
|
-
(a bearer in a file), `LANES_THROTTLE_CONTAINER` (read the log through that container's home volume),
|
|
2161
|
+
(a bearer in a file — `/status` and `/sessions` need it on loopback too, else they answer `HTTP 401`), `LANES_THROTTLE_CONTAINER` (read the log through that container's home volume),
|
|
1963
2162
|
`LANES_THROTTLE_LOG_DIR` (where `proxy.log` lives). A daemon bound to loopback inside its container is not reachable
|
|
1964
2163
|
from the host: use `LANES_THROTTLE_CONTROL=none` there (the log fold still works). Details:
|
|
1965
2164
|
[RUNBOOK](RUNBOOK.md#economy-projection-opt-in).
|
|
@@ -2188,7 +2387,7 @@ Clients also get `lanes-helper.service` and `lanes-upload.path`/`.service` (Linu
|
|
|
2188
2387
|
| Editor | `lanes cursor-commit [ws…] [--push]` | Section 6 |
|
|
2189
2388
|
| Containers | `lanes install-container <name> [--no-mcp]` | Install the `lane` contract (section 2) and the agents' MCP entries for `lane-mcp` (section 7, "Agent tools (MCP)"); `--no-mcp` leaves both out |
|
|
2190
2389
|
| Containers | `lanes onboard <login> --container <name> [--workspace <id> --repo <path> [--title T]] [--write] [--no-mcp]` | One user's container in one command: board, contract, Claude and Codex hooks, Throttle attachment, canonical recipes, sync, doctor, and a receipt (a dry run until `--write`; section 2) |
|
|
2191
|
-
| Containers | `lanes hooks install --agent claude\|codex (--container <name> \| --home <dir>) [--dry-run \| --write]` | Section 7 |
|
|
2390
|
+
| Containers | `lanes hooks install --agent claude\|codex\|kimi\|grok\|grok-cli (--container <name> \| --home <dir>) [--dry-run \| --write]` | Section 7 ([Harnesses](#harnesses)) |
|
|
2192
2391
|
| Uploads | `lanes upload <ws> \| --active <device> [--paste]` | Read a tar stream into that workspace's `uploads/` |
|
|
2193
2392
|
| Uploads | `lanes docs push <id>` | Print one docs-store document's receipt (any login's: you are the operator) and exit 2 `not configured`: the hook for the data service's publication surface, not built yet ([Docs](#docs)) |
|
|
2194
2393
|
| Client | `lanes hello [device] [--client-version V]` | Prove path, key and server version; ` update=<version>` when V is older |
|