Skip to content

Commit 99d05ad

Browse files
committed
Remove Landlock, BPF LSM, and prlimit from branching
Branching should focus purely on branching, not sandboxing or resource limits. Users who need confinement can combine branching with sandlock. Signed-off-by: Cong Wang <cwang@multikernel.io>
1 parent 230aee3 commit 99d05ad

25 files changed

Lines changed: 56 additions & 2530 deletions

README.md

Lines changed: 19 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -356,25 +356,28 @@ with ws.branch("strategy_a") as a:
356356
# a auto-commits into main on success
357357
```
358358

359-
### Process isolation
359+
### Process forking
360360

361-
For untrusted or crash-prone agent code, `BranchContext` runs each task in
362-
a sandboxed child process confined to its own branch via Landlock. No root
363-
needed.
361+
For crash-prone agent code, `BranchContext` runs each task in a forked child
362+
process with its own process group. The child is automatically killed on
363+
timeout or context exit.
364+
365+
For sandboxing (filesystem confinement, resource limits, syscall filtering),
366+
combine with [sandlock](https://github.com/multikernel/sandlock).
364367

365368
```python
366369
from branching import BranchContext
367370

368-
with ws.branch("sandboxed", on_success=None, on_error=None) as fb:
369-
with BranchContext(run_untrusted, workspace=fb.path) as ctx:
371+
with ws.branch("forked", on_success=None, on_error=None) as fb:
372+
with BranchContext(run_agent, workspace=fb.path) as ctx:
370373
try:
371374
ctx.wait(timeout=30)
372375
fb.commit()
373376
except ProcessBranchError:
374377
fb.abort()
375378
```
376379

377-
Run N tasks in parallel, each in its own sandbox:
380+
Run N tasks in parallel, each in its own forked process:
378381

379382
```python
380383
with BranchContext.create(
@@ -385,42 +388,6 @@ with BranchContext.create(
385388
ctx.wait(timeout=60)
386389
```
387390

388-
Any agent pattern can also opt into process isolation:
389-
390-
```python
391-
outcome = Speculate(candidates, isolate_processes=True, timeout=60)(ws)
392-
```
393-
394-
### Resource limits
395-
396-
Constrain per-branch memory and CPU via setrlimit(2). Passing
397-
`resource_limits` to any pattern automatically enables process isolation -
398-
each branch runs in a forked child with limits enforced.
399-
400-
```python
401-
from branching import ResourceLimits, BestOfN
402-
403-
limits = ResourceLimits(memory=512 * 1024 * 1024, cpu_time=30) # 512 MB, 30s CPU
404-
405-
outcome = BestOfN(candidates, resource_limits=limits)(ws)
406-
```
407-
408-
All patterns accept `resource_limits`: `Speculate`, `BestOfN`, `Reflexion`,
409-
`TreeOfThoughts`, `BeamSearch`, `Tournament`, and `Cascaded`. Fields default to `None`
410-
(unlimited). A `ResourceLimits()` with all `None` fields triggers process
411-
isolation without applying any limits.
412-
413-
You can also pass limits directly to `BranchContext`:
414-
415-
```python
416-
from branching import BranchContext, ResourceLimits
417-
418-
limits = ResourceLimits(memory=1024 * 1024 * 1024) # 1 GB
419-
420-
with BranchContext(run_agent, workspace=branch.path, limits=limits) as ctx:
421-
ctx.wait(timeout=30)
422-
```
423-
424391
## CLI
425392

426393
The `branching` command exposes the agent patterns as shell commands.
@@ -435,8 +402,6 @@ Run a command in a new branch. Commits on exit 0, aborts on non-zero.
435402
branching run -- ./build.sh
436403
branching run --on-error none -- python train.py
437404
branching run --ask -- make test # prompt before commit/abort
438-
branching run --memory-limit 512M -- ./agent.sh # cap memory at 512 MB
439-
branching run --memory-limit 1G --cpu-limit 0.5 -- python train.py
440405
```
441406

442407
### speculate
@@ -446,7 +411,6 @@ Race N commands in parallel branches. First success wins.
446411
```bash
447412
branching speculate -c "./fix_a.sh" -c "./fix_b.sh" -c "./fix_c.sh"
448413
branching speculate --timeout 60 -c "python solve_v1.py" -c "python solve_v2.py"
449-
branching speculate --memory-limit 256M -c "./a.sh" -c "./b.sh"
450414
```
451415

452416
### best-of-n
@@ -461,7 +425,6 @@ Each child receives `BRANCHING_ATTEMPT` (0-indexed) in its environment.
461425
branching best-of-n -n 5 -- ./solve.py
462426
branching best-of-n -n 3 --timeout 120 --json -- python attempt.py
463427
branching best-of-n -n 3 -- bash -c 'python run.py && echo "$SCORE" >&3'
464-
branching best-of-n -n 5 --memory-limit 1G --cpu-limit 0.5 -- python attempt.py
465428
```
466429

467430
### reflexion
@@ -475,7 +438,6 @@ The child receives `BRANCHING_ATTEMPT` (0-indexed) and `BRANCHING_FEEDBACK`
475438
branching reflexion --retries 5 -- ./fix.sh
476439
branching reflexion --retries 3 --critique "./review.sh" -- ./solve.py
477440
branching reflexion --retries 3 --critique "python critique.py" --json -- python agent.py
478-
branching reflexion --retries 3 --memory-limit 512M -- ./fix.sh
479441
```
480442

481443
### status
@@ -496,20 +458,13 @@ first-winner-commit semantics.
496458

497459
You just create a `Workspace` pointed at a mounted BranchFS path.
498460

499-
Process isolation (`BranchContext`) uses fork + Landlock LSM + BPF LSM to
500-
sandbox each child process. No namespaces, no cgroups, no root required:
501-
502-
- **Landlock LSM** (Linux 5.13+) confines each child to its own branch.
503-
The child can read the filesystem outside the workspace but can only write
504-
under its branch path. Sibling branches and the mount root are denied.
505-
`LANDLOCK_ACCESS_FS_REFER` is included in the handled set so that
506-
rename/link across the branch boundary is blocked.
507-
- **BPF LSM** provides inescapable process tracking. All descendants of a
508-
branched process inherit the branch ID, enabling atomic teardown of an
509-
entire branch's process tree. Requires `CONFIG_BPF_LSM=y` and
510-
`lsm=...,bpf,...` in the kernel command line.
511-
- **setrlimit(2)** enforces per-branch resource limits (memory via
512-
`RLIMIT_AS`, CPU time via `RLIMIT_CPU`, process count via `RLIMIT_NPROC`).
513-
Lightweight alternative to cgroups -- no cgroupfs infrastructure needed,
514-
limits are inherited by children on fork.
461+
Process forking (`BranchContext`) uses `fork(2)` + process groups to run
462+
each task in an isolated child process. The child's working directory is set
463+
to the branch path, and `mprotect(2)` enforces copy-on-write invariants on
464+
parent memory regions.
465+
466+
BranchContext focuses purely on branching. For sandboxing (filesystem
467+
confinement, syscall filtering, resource limits), use
468+
[sandlock](https://github.com/multikernel/sandlock) alongside branching --
469+
the two are designed to compose together.
515470

src/branching/__init__.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
BranchContext - Unified branching for speculative execution.
44
55
Supports filesystem branching (BranchFS FUSE), process branching
6-
(fork + Landlock + BPF LSM), and AI agent integration patterns
7-
(speculation, best-of-N, reflexion, tree-of-thoughts).
6+
(fork + mprotect), and AI agent integration patterns (speculation,
7+
best-of-N, reflexion, tree-of-thoughts).
88
99
Layers are loaded lazily — importing only what you need avoids pulling
1010
in unrelated dependencies:
@@ -44,7 +44,6 @@
4444
"Branch",
4545
# Process
4646
"BranchContext",
47-
"ResourceLimits",
4847
# Agent patterns
4948
"Speculate",
5049
"BestOfN",
@@ -79,7 +78,6 @@
7978
"Branch": ".core.branch",
8079
# Process layer
8180
"BranchContext": ".process.context",
82-
"ResourceLimits": ".process.limits",
8381
# Agent layer
8482
"Speculate": ".agent.speculate",
8583
"BestOfN": ".agent.patterns",

0 commit comments

Comments
 (0)