---
name: flmux
description: "Write dart_monty Python scripts that drive the flmux terminal multiplexer via the fl_* host functions — open/split panes, stream text into them, read their screens, show source, and REACT to live UI notifications (pane splits/closes/focus, agent status). Trigger when a Monty plan should render sub-agent output in live flmux panes or respond to what the user does in the flmux UI."
metadata:
  package: pi_agent
  namespace: fl
  backends: [ffi]
  source: packages/pi_agent/lib/src/flmux_extension.dart
---

# flmux (fl_*)

Drive a running **flmux** (Flutter terminal multiplexer) from a Monty plan over
its Unix-domain JSON-RPC control plane (`FLMUX_SOCKET_PATH`, default
`/tmp/flmux.sock`). The host owns the socket; the script just calls verbs.

**FFI only** — `dart:io` sockets have no WASM equivalent, so these functions are
absent on the WASM backend.

## Host setup (Dart)

```dart
import 'package:dart_monty/dart_monty.dart';
import 'package:pi_agent/pi_agent.dart';

final runtime = MontyRuntime(extensions: [FlmuxExtension()]);
```

## Capability check (always emit first)

```python
requires([
    "fl_split", "fl_send", "fl_read", "fl_focus", "fl_code",
    "fl_subscribe", "fl_events", "fl_wait",
])
```

## Drive verbs

### `fl_split(direction: str = 'column', kind: str = 'shell', surface_id: int = None) -> int`
Open a new pane and return its integer `surface_id`. `direction` is `'column'`
(stacked) or `'row'` (side by side). `kind` is `'shell'` (real PTY) or
`'monty'` (in-process REPL). Pass `surface_id` to split a specific pane instead
of the focused one. Give each sub-agent its own pane so their output streams
side by side.

### `fl_send(surface_id: int, text: str) -> bool`
Type `text` into a pane as if typed at the keyboard. **To submit, end with `\r`
(carriage return — what the Enter key sends), not `\n`.** A shell accepts either,
but a TUI (e.g. `pi`) treats `\n` as a *literal newline* in its editor and only
submits on `\r` — so `\n` will pile your input up unsubmitted. Streaming output
appears live; `\r`-terminated sends are recorded in the pane's durable **History**.

### `fl_read(surface_id: int) -> str`
Return the pane's current visible screen as plain text.

### `fl_focus(surface_id: int) -> bool`
Focus a pane.

### `fl_code(title: str, source: str, direction: str = 'row', surface_id: int = None) -> int`
Open a pane showing `source` as syntax-highlighted Python (via `bat`) — use it
to SHOW the Monty script a level is executing. Returns the new `surface_id`.

## React verbs (full-duplex)

flmux pushes notifications; subscribe once, then poll. Two shapes:
- `{"kind": "state", "tree": {…}}` — on split / close / focus / maximize.
- `{"kind": "event", "type": "status", "surface_id": int, "status"?: str,
   "progress"?: float, "label"?: str}` — on a status update.

### `fl_subscribe() -> bool`
Open the persistent subscription. Call once before polling (the poll verbs
auto-subscribe too).

### `fl_events() -> list[dict]`
Return and clear every notification received since the last call (non-blocking;
`[]` if none).

### `fl_wait(timeout_ms: int = 2000) -> dict | None`
Block up to `timeout_ms` for the next notification and return it, or `None` on
timeout. Use to react without busy-looping.

## Patterns

Stream an agent's work into its own pane:

```python
requires(["fl_split", "fl_send"])
sid = fl_split(direction="column")
fl_send(sid, 'printf "\\033]0;search\\007"\r')   # title the pane (\r submits)
fl_send(sid, "echo searching kagi...\r")
# Submitting to a pi/TUI pane: end with \r, NOT \n —
fl_send(sid, "What is the flmux skill?\r")
```

React to the UI / agent status (no busy-loop):

```python
requires(["fl_subscribe", "fl_wait"])
fl_subscribe()
while True:
    ev = fl_wait(3000)
    if ev is None:
        continue
    if ev["kind"] == "event" and ev.get("status") == "done":
        print("pane", ev["surface_id"], "finished:", ev.get("label"))
        break
    if ev["kind"] == "state":
        pass  # tree changed (split/close/focus)
```

See the **pi-agent** skill to combine these: one `pi` sub-agent per pane,
streaming its output with `fl_send`.
