Skip to content
Open
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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,24 @@ A host's `port` is the loopback port on this machine through which its own hub i
}
```

`terminalMemory` and a preset's `memory` set memory limits for its terminals: `high` (throttled above it, `MemoryHigh`), `max` (killed above it, `MemoryMax`) and `swapMax` (`MemorySwapMax`), each a size such as `512M`, `16G` or `17179869184`, or a percentage of RAM such as `50%`; `swapMax` may also be `0`. A preset's `memory` replaces `terminalMemory` key by key. Without swap limits, `max` swaps before it kills. Limits apply only while terminal scopes are on (see [Memory and out-of-memory kills](#memory-and-out-of-memory-kills)), and the page does not edit them.

```json
{
"terminalMemory": { "high": "16G", "max": "32G" },
"presets": [
{ "name": "shell", "command": null },
{ "name": "tests", "command": "pytest -n auto", "memory": { "max": "24G", "swapMax": "0" } }
]
}
```

## Memory and out-of-memory kills

With a systemd user manager that outlives your session (the process runs under `user@<uid>.service`, as anything started from a desktop session does, or lingering is on), every process worktree-term starts gets a cgroup of its own in the slice `app-wtd.slice`: the daemon `wtd-<h>-daemon-<r>.scope`, the hub `wtd-<h>-hub-<r>.scope`, and each terminal, with everything it starts, `wtd-<h>-term-<instance>-<id>.scope`, where `<h>` hashes the state directory. systemd-oomd and the kernel then kill only the terminal that ran out of memory, never the daemon, the hub or another terminal; its tab says `killed: out of memory`, and `memory.log` gets an `oom-kill` line and each terminal's cgroup figures. A daemon run as `worktree-term-daemon.service` stays in that unit. Terminal shells also get an `oom_score_adj` of at least 500, so a system-wide OOM picks a terminal before the daemon or hub.

Without such a user manager (no systemd, an SSH session without lingering, `systemd-run` failing), everything runs in the cgroup it was started in, as before, and memory limits are ignored. The daemon says which on its standard error at start: `terminal scopes: on`, or `terminal scopes: off (<reason>)`.

## Remote hosts

A remote host needs only Linux, Node 20 or later and an SSH alias that logs in without a prompt (keys or an agent: the hub runs SSH with `BatchMode=yes`). Install wtd there with
Expand Down
3 changes: 3 additions & 0 deletions architecture/model.c4
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ model {
dialer = component 'Dialer'
install = component 'Install'
memory = component 'Memory'
scopes = component 'Scopes'
}

daemon = node 'Daemon' {
Expand Down Expand Up @@ -81,6 +82,7 @@ model {
daemon.main -> daemon.terminals
daemon.main -> daemon.state
daemon.main -> platform.memory
daemon.main -> platform.scopes
daemon.server -> daemon.worktrees
daemon.server -> daemon.terminals
daemon.server -> daemon.state
Expand All @@ -90,6 +92,7 @@ model {
hub.config -> platform.files
hub.links -> platform.dialer
hub.links -> platform.install
hub.links -> platform.scopes
hub.main -> hub.server
hub.main -> hub.router
hub.main -> hub.links
Expand Down
5 changes: 4 additions & 1 deletion architecture/system.arc42.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ flowchart TD
platform__dialer["Dialer"]
platform__install["Install"]
platform__memory["Memory"]
platform__scopes["Scopes"]
end
subgraph daemon["Daemon"]
daemon__worktrees["Worktrees"]
Expand Down Expand Up @@ -52,6 +53,7 @@ flowchart TD
daemon__main --> daemon__terminals
daemon__main --> daemon__state
daemon__main --> platform__memory
daemon__main --> platform__scopes
daemon__server --> daemon__worktrees
daemon__server --> daemon__terminals
daemon__server --> daemon__state
Expand All @@ -60,6 +62,7 @@ flowchart TD
hub__config --> platform__files
hub__links --> platform__dialer
hub__links --> platform__install
hub__links --> platform__scopes
hub__main --> hub__server
hub__main --> hub__router
hub__main --> hub__links
Expand All @@ -83,7 +86,7 @@ flowchart TD
web__ui --> web__layout
web__terminals --> web__client
classDef leaf fill:none;
class protocol,platform__files,platform__dialer,platform__install,platform__memory,daemon__worktrees,daemon__terminals,daemon__state,daemon__server,daemon__main,hub__server,hub__links,hub__config,hub__router,hub__main,cli,web__client,web__terminals,web__layout,web__ui,web__main leaf;
class protocol,platform__files,platform__dialer,platform__install,platform__memory,platform__scopes,daemon__worktrees,daemon__terminals,daemon__state,daemon__server,daemon__main,hub__server,hub__links,hub__config,hub__router,hub__main,cli,web__client,web__terminals,web__layout,web__ui,web__main leaf;
```
<!-- /likec4:system -->

Expand Down
5 changes: 4 additions & 1 deletion architecture/typescript.daemon.arc42.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ flowchart TD
subgraph platform["Platform"]
platform__files["Files"]
platform__memory["Memory"]
platform__scopes["Scopes"]
end
subgraph daemon["Daemon"]
daemon__worktrees["Worktrees"]
Expand All @@ -32,12 +33,13 @@ flowchart TD
daemon__main --> daemon__terminals
daemon__main --> daemon__state
daemon__main --> platform__memory
daemon__main --> platform__scopes
daemon__server --> daemon__worktrees
daemon__server --> daemon__terminals
daemon__server --> daemon__state
cli --> daemon__main
classDef leaf fill:none;
class protocol,platform__files,platform__memory,daemon__worktrees,daemon__terminals,daemon__state,daemon__server,daemon__main,cli leaf;
class protocol,platform__files,platform__memory,platform__scopes,daemon__worktrees,daemon__terminals,daemon__state,daemon__server,daemon__main,cli leaf;
```
<!-- /likec4:daemon -->

Expand All @@ -50,3 +52,4 @@ flowchart TD
5. An attach yields a snapshot and then live output with no gap and no duplicate byte. [enforced: test:test/process/daemon-terminals-output.test.ts]
6. Worktree changes are observed by events, never by polling. [review]
7. Persisted state references no worktree that is gone and has no terminals. [enforced: test:src/daemon/state/store.test.ts]
8. Each terminal runs in a scope of its own when a lasting user manager (≡ one the daemon runs under, or lingering) is reachable. [enforced: test:test/process/daemon-terminals-scopes.test.ts]
4 changes: 3 additions & 1 deletion architecture/typescript.hub.arc42.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ flowchart TD
platform__dialer["Dialer"]
platform__install["Install"]
platform__memory["Memory"]
platform__scopes["Scopes"]
end
subgraph hub["Hub"]
hub__server["Server"]
Expand All @@ -32,6 +33,7 @@ flowchart TD
hub__config --> platform__files
hub__links --> platform__dialer
hub__links --> platform__install
hub__links --> platform__scopes
hub__main --> hub__server
hub__main --> hub__router
hub__main --> hub__links
Expand All @@ -42,7 +44,7 @@ flowchart TD
hub__router --> hub__config
cli --> hub__main
classDef leaf fill:none;
class protocol,platform__files,platform__dialer,platform__install,platform__memory,hub__server,hub__links,hub__config,hub__router,hub__main,cli leaf;
class protocol,platform__files,platform__dialer,platform__install,platform__memory,platform__scopes,hub__server,hub__links,hub__config,hub__router,hub__main,cli leaf;
```
<!-- /likec4:hub -->

Expand Down
7 changes: 5 additions & 2 deletions architecture/typescript.platform.arc42.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Purpose & context

Node-only infrastructure shared by daemon, hub and CLI: XDG paths and file I/O (`files`); reaching a host's daemon over its socket, with auto-start, or over `ssh` (`dialer`); installing the bundle locally or over `ssh` (`install`); sampling process, cgroup and system memory into a rotating log (`memory`).
Node-only infrastructure shared by daemon, hub and CLI: XDG paths and file I/O (`files`); reaching a host's daemon over its socket, with auto-start, or over `ssh` (`dialer`); installing the bundle locally or over `ssh` (`install`); sampling process, cgroup and system memory into a rotating log (`memory`); running a process in a systemd user scope of its own (`scopes`).

## Building blocks

Expand All @@ -17,6 +17,7 @@ flowchart TD
platform__dialer["Dialer"]
platform__install["Install"]
platform__memory["Memory"]
platform__scopes["Scopes"]
end
subgraph daemon["Daemon"]
daemon__main["Main"]
Expand All @@ -34,16 +35,18 @@ flowchart TD
platform__memory --> platform__files
daemon --> platform__files
daemon__main --> platform__memory
daemon__main --> platform__scopes
hub__server --> platform__files
hub__config --> platform__files
hub__links --> platform__dialer
hub__links --> platform__install
hub__links --> platform__scopes
hub__main --> platform__memory
cli --> platform__files
cli --> platform__dialer
cli --> platform__install
classDef leaf fill:none;
class platform__files,platform__dialer,platform__install,platform__memory,daemon__main,hub__server,hub__links,hub__config,hub__main,cli leaf;
class platform__files,platform__dialer,platform__install,platform__memory,platform__scopes,daemon__main,hub__server,hub__links,hub__config,hub__main,cli leaf;
```
<!-- /likec4:platform -->

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-11-07
Loading
Loading