Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ A Bun workspace (`packages/*`, shared `tsconfig.base.json`). Layering between OL
| `index` | `format` | the SEARCH INDEX and nothing that decides: a trigram table (`bun:sqlite`'s FTS5, so the substring matching olai's grammar promises survives — every word-based library would change what search finds, which [brainstorming/search-index.md](https://github.com/juspay/oss.olai/blob/main/olai/brainstorming/search-index.md) surveys) over the very fold the matcher reads, kept level with the store's revisions by identity rather than by a push, and answering with CANDIDATES: the records and documents a query might select, or "ask the corpus" for a needle under three characters, a query with no word to look up, and a word so common that narrowing costs more than walking. It holds no grammar — which words may narrow a query is `format`'s `narrowableBy`, and what matches is `format`'s one matcher, run over the shortlist exactly as it is run over the corpus. It sits above `format` rather than inside it because the engine is Bun's and `format` is bundled into a browser; the property that makes it safe is a superset, and it is pinned by a differential test over a soak of random writes rather than argued |
| `ops` | `format`, `git`, `index`, `store` (`log` for tests only) | the only writer: the semantic edits, the codec that joins the two below it, the parsed-node queries (search — WHAT matches is `format`'s one matcher, because a second implementation of it is how `is:done` comes to mean two things; what is here is the situating of a hit, the cap it applies and the uncapped total it reports, plus the one table a directory search takes its candidates from, opened per served directory beside the store and spent at that one door (`@olai/index`), plus the OTHER half the same matcher answers, `Query.narrowing`: which nodes of ONE PAGE a query selects, ids and why, for the page filter in a browser that no longer holds the set — a reading on the revision pulse rather than a call, since a filter is a standing view and asking it as one meant a whole-vault walk per page frame — the field weights, the done penalty and the order are `format`'s, one layer down, because the browser ranks a shortlist of its own), what is waiting to be committed and the one verb that commits it, and the closed table of what an agent may do. NOT how that table is spoken: no MCP, no SDK, no wire |
| `surface` | `acp` (its `./wire` subpath only), `format` | the surface spec: an `outlines` collection keyed by file path, a `documents` collection keyed the same way (bodies, read one key at a time), a `heads` collection on those same keys carrying one revision each and no body (batched `deltas`, so a reader watching a file it does not draw from — a previewed `.html` — costs a number rather than the file), a `manifest` cell, an `errors` cell, a `git` cell and a `pending` cell with its `git.commit` and `git.push` procedures, chat's transcript collection, the `saying` collection beside it — the pieces of the row an agent is still saying, so a streaming answer costs the wire the answer once rather than the whole row per token (`transcript-stream-quadratic`) — its state cell and its procedures, and the KEYBOARD's one `edit.apply` procedure over a closed union of intents — plus the contracts both ends spell and neither can import from the other: the `/media` URL, `who.get` and `GET /olai/who` (who is looking — per connection, not a cell), `/_olai/assets/` (the hashed bundle, so a vault file under `assets/` is a page), and what an attached file may BE (the two allowlists, the size cap, the sentence both ends refuse with). How it is cut into frames is deliberately NOT here: that is wire physics, and it lives beside the cap it is derived from in `@kolu/surface/frame-chunking`, which both ends import directly. The serving-side seam that used to sit here beside the declaration is gone: knowing when the last reader of a file lets go was `holding`, a wrap over a per-key `get`'s own subscription behind a subpath of its own, and it is `holders` on a collection's handler deps upstream now (juspay/kolu#2188), handed in where `@olai/server` builds them. So this package is one door and one kind of thing, the DECLARATION, and nothing in it reaches for a server |
| `kolu-client` | `surface` | HOW OLAI REACHES KOLU, and the only package that names padi: the dial (`@kolu/padi-client`'s `connectPadi`), the ONE standing mirror of padi's `terminals` collection and its two attention feeds (`urgency`, `activity`) that feeds every reader, the projection of a terminal record into `surface`'s own `FleetTerminal` — which is kolu's OWN Dock-row prop bag plus an owner, every field folded by kolu's own function, since olai draws kolu's row rather than inventing a second visual language for one fleet — the rendezvous (`$PADI_SOCKET` first, the path algebra second), and the one `screen.text` read the snapshot pane makes. `koluHalf` is what `server` composes, and it returns a HANDLERS SLICE — the four members in the shape `defineSurface` takes — plus one revision hook, so `runtime.ts` names no kolu verb. Four more doors beside the root: `./wire` (the vocabulary and those member declarations, which `surface` spreads into its spec and re-exports, so no consumer rewrote an import), `./detect`, `./testlib` and `./drivers`. A PACKAGE rather than a directory under `server` because the wall makes the direction physics — this cannot depend back on the composition root, where a directory could reach `../runtime.ts` one convenient import at a time. It names `surface` and NOTHING else of olai: the walk over the vault that decides who OWNS a terminal is `server`'s (`claimants.ts`), and what crosses is four strings per claim, so "how olai reaches kolu" never learns what an outline node is |
| `kolu-client` | `surface` | HOW OLAI REACHES KOLU, and the only package that names padi: the dial (`@kolu/padi-client`'s `connectPadi`), the ONE standing mirror of padi's `terminals` collection and its two attention feeds (`urgency`, `activity`) that feeds every reader, the projection of a terminal record into `surface`'s own `FleetTerminal` — which is kolu's OWN Dock-row prop bag plus an owner, every field folded by kolu's own function, since olai draws kolu's row rather than inventing a second visual language for one fleet — the rendezvous (`$PADI_SOCKET` first, the path algebra second), and the one `screen.text` read the snapshot pane makes. `koluHalf` is what `server` composes, and it returns a HANDLERS SLICE — the five members (`kolu` cell, `fleet`, `events`, the live pane's attach stream, `screen.text`) in the shape `defineSurface` takes — plus one revision hook holding the two walks (the claims and the watcher config), so `runtime.ts` names no kolu verb. Four more doors beside the root: `./wire` (the vocabulary and those member declarations, which `surface` spreads into its spec and re-exports, so no consumer rewrote an import), `./detect`, `./testlib` and `./drivers`. A PACKAGE rather than a directory under `server` because the wall makes the direction physics — this cannot depend back on the composition root, where a directory could reach `../runtime.ts` one convenient import at a time. It names `surface` and NOTHING else of olai: the walk over the vault that decides who OWNS a terminal is `server`'s (`claimants.ts`), and what crosses is four strings per claim, so "how olai reaches kolu" never learns what an outline node is |
| `chat` | `acp`, `child`, `format`, `log`, `state`, `surface` | one conversation with one ACP agent: the subprocess, the session, the transcript, the conversation's tmp directory for pasted pictures, the note in the state directory saying which conversation this directory's panel was in, the six verbs, and the domestic reading of what a call CHANGED through the ops layer (olai's own write reply — the protocol's diff blocks are `acp`'s to read). It consumes `acp`'s projections and translates that package's `Refused` into the domain's `UsageFailure` at the seam. The subprocess socket is `@olai/child`'s; the ACP stream and the handshake stay here. NOT `ops` — the agent reaches the ops layer as an MCP client, over a URL and a token, which is why a whole-file write is not expressible |
| `kolu-ui` | `surface` | EVERYTHING BROWSER ABOUT KOLU: the Dock row a `terminal` property draws (kolu's own component, filled from olai's wire), the live pane over one terminal, the re-attach policy that pane runs, the fleet a tab holds once however many rows draw one, and the words the header readout says. Its whole contact with the app is `KoluUi` — the app hands over its composed surface client and a clock, and which members exist, what they are called and how a pane's subscription is bound stay inside. `TerminalBlock` is a renderer that registers nothing: `web` owns its own block table and calls `registerBlock(TERMINAL_KEY, TerminalBlock)` itself. Three doors: the socket, `./testids` (names only, so a scenario asserts without pulling a component into the suite), and `./all.css`, which carries the row's sheet, xterm's and its own `@source`. A PACKAGE rather than a directory under `web` for ruling 2's reason — a directory wall is broken by one convenient import, a package wall is not |
| `kolu-ui` | `surface` | EVERYTHING BROWSER ABOUT KOLU: the Dock row a `terminal` property draws (kolu's own component, filled from olai's wire), the live pane over one terminal, the re-attach policy that pane runs, the fleet a tab holds once however many rows draw one, the EVENTS the padi readout's press fans (`props/EventsFeed.tsx` over `padi/events.ts` — the watcher's log, the frozen draw, the words), and the words the header readout says. Its whole contact with the app is `KoluUi` — the app hands over its composed surface client and a clock, and which members exist, what they are called and how a pane's subscription is bound stay inside. `TerminalBlock` is a renderer that registers nothing: `web` owns its own block table and calls `registerBlock(TERMINAL_KEY, TerminalBlock)` itself. Three doors: the socket, `./testids` (names only, so a scenario asserts without pulling a component into the suite), and `./all.css`, which carries the row's sheet, xterm's and its own `@source`. A PACKAGE rather than a directory under `web` for ruling 2's reason — a directory wall is broken by one convenient import, a package wall is not |
| `server` | `chat`, `format`, `identity`, `kolu-client`, `log`, `ops`, `state`, `store`, `surface` (`child` for tests only) | the composition root: the HTTP + WebSocket server with the MCP route on it, the HTTP `/mcp` a terminal agent posts at, the ONE surface-mcp server behind both faces, the order the layers go in, and the binary that one command is. Product spawn is `chat`'s and `git`'s; `@olai/child` is what the process-boundary tests ride |
| `fonts` | — | the typefaces, and TWO entry points because there are two readers. `@olai/fonts` is what a PAGE reads: the picks (name, label, group, hint and the three stacks) and nothing else. `@olai/fonts/build` is what a client BUILD takes: the `@font-face` / `:root[data-font="…"]` sheet generated from those picks, and the list of woff2 files its `src: url(…)` needs — one entry, because a build that took the sheet and not the files would ship a page whose every face 404s. Beside them, in the same package, the `default.nix` that converts those files in the Nix store — off `src/hosted.json`, the ONE list of faces that nix file and the TypeScript both read, because where a face's bytes come from and what CSS identity they carry is one fact and was two. No dependencies at all: two tables and the pure functions over them. What is NOT here is the pick, which is client state and stays in `web` |
| `web` | `child`, `fonts`, `format`, `surface` | the SolidJS client — outline, journal, agenda and chat panel — its Tailwind stylesheet, and the `Bun.build` that produces both. `@olai/child` is the Tailwind CLI the build script starts, and the browser bundle must never import it |
Expand Down
24 changes: 24 additions & 0 deletions docs/kolu.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,30 @@ Attaching is a write on a shared pty, and kolu's semantic is that every client s

Closing drops the attach. Twelve lanes on a page are twelve rows and **zero** attached terminals until somebody presses one.

## The events feed

Press the `● kolu` readout and a panel opens: **what recently wanted attention**, as a log. olai watches the fleet it already holds and says when a terminal has been sitting in a state only a person can carry — `awaiting` your answer, or `waiting` on input — past the moment it is worth saying so. While a pressed Terminal waits on a machine's other half somewhere, the watch itself stays the same economy as the fleet drawer it sits on: the one daemon, the one subscriber, the map the server already held.

Every row is a **frozen draw of the moment the event fired** — which pip it wore, which label it had, how long it had already held. The row is a fact about the past, not a current affairs teller: a terminal that found its answer ten minutes ago still shows the ask it was, and the headline age runs off the same clock as the pips.

The watch is the SERVER's — one watcher, one ring of the last roughly two hundred events, and every browser is a reader of it. The ring itself is **attention only** — heartbeats are nowhere on it; a feed of migrated rows would be the same distraction the usernames they fold back to would be.

The liveness lives on the PILL instead, which is where the feed's own bar already was: `watcher pulse 2m ago` in the hover while it is healthy (the fold's first register, `fresh`), and — crossed past **2 × the vault's `heartbeat` knob** — the pill's loud amber face: the chip's dot hollow, its border and ring warming, and its words naming it as `watcher quiet 47m` until the next beat. That double-cadence is the pill's own margin: the fold counts the beat's stamp against `everyMs` right beside the pulse's record, so a tab answering on its own clock need never guess the vault's cadence. A padi link flapping under olai fires nothing: a fleet emptied because the socket went is a fleet PAUSED, not a closing one — the holds keep their own clocks through it, and a `since` survives a reconnect the way padi's own daemon runs it.

Where a terminal asks is the only thing a held state cannot tell: you WANT it to ask somewhere. **Mute the fleet one at a time** in `_olai/Kolu.olai`, the same file the watch reads its cadence from:

```
{"id":"watch","ord":"a0","title":"watch","custom":{"held-for":"60s"}}
{"id":"mutes","ord":"a1","title":"mutes"}
{"id":"one","parent":"mutes","ord":"a0","title":"a side shell nobody watches","custom":{"terminal":"5c5824d5"}}
```

The file is normal outline records. The watch node carries the knobs — `held-for` (the pause that holds a terminal's state out of the feed), `nag` (how often an unanswered one is said again) and `heartbeat` — written as `<n>s`, `<n>m` or `<n>h`; an absent value defaults, and a malformed one defaults **and is said on the server's console at warning level**, so a typing mistake is never silent. The grammar is padi's own: `held-for` may be `0` — the report the INSTANT a state lands, which padi's watch flags also allow — but the other two never are, because a nag every 0 ms is the spin padi's own schema refuses, and both cap at the ~24.8-day timer bound. The mutes are children of a `mutes` node holding one `terminal` property each — full id or a unique prefix of one. Edits land live: the watch reads its knobs on every vault revision, so the file a person is *editing* is the file a person is **already being watched by**.

An ambiguous prefix — one that names two open terminals — silences neither, and says so: write more of the id.

The file is yours, not installed: without it the watch runs its defaults (sixty seconds, ten minutes, half an hour).

## When there is nothing to see

A machine not running kolu is the ordinary case, not a fault. There is no row, and in its place a **sentence** — never a grey row, which would claim the terminal is sitting there doing nothing, and that is a different and wrong fact:
Expand Down
5 changes: 3 additions & 2 deletions packages/kolu-client/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @olai/kolu-client — how olai reaches kolu

One package holds the dial, the standing mirror, the projection into olai's own vocabulary, and the one screen read. What leaves is [`@olai/surface`](../surface/README.md)'s shapes — a `KoluLink`, a `FleetTerminal`, a `Snapshot` — so a change to padi's contract is a change **here** and stops.
One package holds the dial, the standing mirror, the projection into olai's own vocabulary, the one screen read, and the **watch** that turns a held fleet state into an event the board can read. What leaves is [`@olai/surface`](../surface/README.md)'s shapes — a `KoluLink`, a `FleetTerminal`, a `Snapshot`, a `KoluEvent` — so a change to padi's contract is a change **here** and stops.

**Olai works on top of kolu and never launches agents itself.** Every process with a model in it is a kolu terminal (`https://github.com/juspay/oss.olai/blob/main/olai/brainstorming/orchestrator.md`); what olai does is *read* the fleet those terminals make and put it where the fact already is. This package is the whole of the reading.

Expand All @@ -11,9 +11,10 @@ The terminal door's two rungs (roadmap: `terminal-door`). A lane step carries `t
- **`link.ts`** — the dial and the **one standing mirror**. `connectPadi` over the unix socket, `mirrorRemoteSurface` of padi's `terminals` collection plus the two attention feeds (the `urgency` cell and the `activity` stream), and a five-second re-dial that never gives up. Ten tabs are ten subscriptions to olai's own `fleet` collection and *one* connection to padi, because the link is forked by the `kolu` cell's connector when the surface **binds** — the git sweep's arrangement applied to a socket. The invariant is structural at bind; `mirror.test.ts` counts it as well.
- **`mirror.ts`** — the three clocks joined. padi's records move on one, its attention partition on two more (an agent transition; kaval's byte edge), and olai's ownership overlay on a fourth — the vault's revision stream. Each publishes only the rows it actually moved.
- **`fleet.ts`** — one padi record projected to one row of kolu's own prop bag, joined with olai's ownership overlay. Every field is kolu's own fold: `bindStatePip`, `paintDockRow`, `rowSubline`, `activePr`, `annotationLine`, `identityColor`. Nothing here decides what a state means or what colour it paints — that was `face.ts`, and the fifth Löwy sitting deleted it by ratification.
- **`watch.ts`** — the ATTENTION WATCH: a local reading of the mirror the server already holds, not a second ask of padi. A fleet row folded into a held bucket starts a hold; held past `held-for` the watcher emits a `transition`; still held after `nag`, the `nag`s; and the **pulse** lives off that ring — it lands on the `pulse` cell instead (the wire's `pulse` member, which the pill folds), so a quiet feed and a dead one read differently and the drawer eats attention rows alone. The knobs are the SERVER's (`@olai/server`'s `koluConfig.ts` reads `_olai/Kolu.olai`), and what the watch owns is one ring of `WATCH_RING` events — the same economy as the mirror's own rows.
- **`socket.ts`** — where padi is: `$PADI_SOCKET` first, the rendezvous path algebra second. kolu's own README asks a client to be *given* the socket, because the correcting read-back stayed with the daemon.
- **`screen.ts`** — one `screen.text` read, tailed here.
- **`index.ts`** — `koluHalf`, which is what a server composes: three surface members and one revision hook.
- **`index.ts`** — `koluHalf`, which is what a server composes: five surface members and one revision hook carrying the two walks (the claims and the knobs of the watch).

## Absent is a state, not a failure

Expand Down
Loading