escarp 1.0.0__tar.gz
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.
- escarp-1.0.0/.gitignore +16 -0
- escarp-1.0.0/LICENSE +21 -0
- escarp-1.0.0/PKG-INFO +205 -0
- escarp-1.0.0/README.md +174 -0
- escarp-1.0.0/V2_PLAN.md +363 -0
- escarp-1.0.0/pyproject.toml +84 -0
- escarp-1.0.0/src/escarp/__init__.py +13 -0
- escarp-1.0.0/src/escarp/broker/__init__.py +8 -0
- escarp-1.0.0/src/escarp/broker/api.py +135 -0
- escarp-1.0.0/src/escarp/broker/browser.py +220 -0
- escarp-1.0.0/src/escarp/broker/daemon.py +214 -0
- escarp-1.0.0/src/escarp/broker/discovery.py +94 -0
- escarp-1.0.0/src/escarp/broker/launcher.py +122 -0
- escarp-1.0.0/src/escarp/broker/lease.py +250 -0
- escarp-1.0.0/src/escarp/broker/slots.py +115 -0
- escarp-1.0.0/src/escarp/cli.py +42 -0
- escarp-1.0.0/src/escarp/config.py +26 -0
- escarp-1.0.0/src/escarp/identity/__init__.py +4 -0
- escarp-1.0.0/src/escarp/identity/keypair.py +70 -0
- escarp-1.0.0/src/escarp/identity/signing.py +148 -0
- escarp-1.0.0/src/escarp/mcp/__init__.py +5 -0
- escarp-1.0.0/src/escarp/mcp/server.py +213 -0
escarp-1.0.0/.gitignore
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
.venv/
|
|
4
|
+
dist/
|
|
5
|
+
*.egg-info/
|
|
6
|
+
.mypy_cache/
|
|
7
|
+
.ruff_cache/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
|
|
10
|
+
# v2: Chrome for Testing dropped here by @puppeteer/browsers during local dev.
|
|
11
|
+
# Production install path is ~/.escarp/chrome/ (daemon-managed on first run).
|
|
12
|
+
chrome/
|
|
13
|
+
|
|
14
|
+
# v2: ephemeral profile/state dirs used by the broker daemon.
|
|
15
|
+
.escarp/
|
|
16
|
+
|
escarp-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David Gao
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
escarp-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: escarp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Identity-aware runtime for parallel coding agents -- persistent browser pool + lease broker over CDP
|
|
5
|
+
Project-URL: Homepage, https://github.com/ddavidgao/escarp
|
|
6
|
+
Project-URL: Repository, https://github.com/ddavidgao/escarp
|
|
7
|
+
Project-URL: Issues, https://github.com/ddavidgao/escarp/issues
|
|
8
|
+
Author-email: David Gao <davidgao1345@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,ai,browser-pool,chrome-devtools-protocol,claude-code,codex,mcp,worktree
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Testing
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: aiohttp>=3.9
|
|
21
|
+
Requires-Dist: cryptography>=42.0
|
|
22
|
+
Requires-Dist: httpx>=0.28.1
|
|
23
|
+
Requires-Dist: mcp>=1.27.2
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
26
|
+
Requires-Dist: playwright>=1.44; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# escarp
|
|
33
|
+
|
|
34
|
+
**Identity-aware runtime for parallel coding agents.** A single broker daemon
|
|
35
|
+
owns a pool of persistent Chrome for Testing windows and hands CDP endpoint
|
|
36
|
+
leases to coding agents (Claude Code, Codex) over MCP. N worktrees can run N
|
|
37
|
+
agents with N isolated browsers, no stale-lock hell.
|
|
38
|
+
|
|
39
|
+
> **Why?** Spawn-a-browser-per-tool-call leaks chromes on every chat exit.
|
|
40
|
+
> Per-agent lockfiles strand themselves when the agent dies. Driving the
|
|
41
|
+
> user's daily-driver browser pollutes cookies and session state. Escarp
|
|
42
|
+
> separates lifecycle (persistent, owned by escarp) from leases (ephemeral,
|
|
43
|
+
> owned by the agent). The browsers always exist; agents check them out.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install escarp
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Requirements: Python 3.11+, a Chrome for Testing binary on disk. Easiest way
|
|
52
|
+
to get one:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx @puppeteer/browsers install chrome@stable
|
|
56
|
+
export ESCARP_CFT_BINARY=".../Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Quick start
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# 1. Spawn N persistent Chrome for Testing windows (one-shot, exits immediately).
|
|
63
|
+
# The chromes are detached and survive escarp restarts.
|
|
64
|
+
escarp launch-pool # default 4
|
|
65
|
+
ESCARP_POOL_SIZE=8 escarp launch-pool
|
|
66
|
+
|
|
67
|
+
# 2. Start the broker daemon. It discovers the chromes via /json/version,
|
|
68
|
+
# serves the lease HTTP API on 127.0.0.1:7878, and runs a reaper for
|
|
69
|
+
# expired leases. ^C releases the slot locks but leaves chromes alive.
|
|
70
|
+
escarp daemon &
|
|
71
|
+
|
|
72
|
+
# 3. Inspect the pool
|
|
73
|
+
curl -s http://127.0.0.1:7878/status | jq
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Drive one of the leased browsers in Python:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
import asyncio, httpx
|
|
80
|
+
from playwright.async_api import async_playwright
|
|
81
|
+
|
|
82
|
+
async def main():
|
|
83
|
+
lease = httpx.post(
|
|
84
|
+
"http://127.0.0.1:7878/acquire",
|
|
85
|
+
json={"holder": "demo-script"},
|
|
86
|
+
).json()
|
|
87
|
+
print("got slot", lease["slot"], "->", lease["cdp_ws_url"])
|
|
88
|
+
|
|
89
|
+
async with async_playwright() as pw:
|
|
90
|
+
browser = await pw.chromium.connect_over_cdp(lease["cdp_ws_url"])
|
|
91
|
+
page = browser.contexts[0].pages[0]
|
|
92
|
+
await page.goto("https://example.com")
|
|
93
|
+
await page.screenshot(path="/tmp/example.png")
|
|
94
|
+
await browser.close() # disconnect; broker-owned chrome stays alive
|
|
95
|
+
|
|
96
|
+
httpx.post(
|
|
97
|
+
"http://127.0.0.1:7878/release",
|
|
98
|
+
json={"lease_token": lease["lease_token"]},
|
|
99
|
+
) # broker auto-resets the tab to about:blank
|
|
100
|
+
|
|
101
|
+
asyncio.run(main())
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Wire it into Claude Code / Codex via MCP
|
|
105
|
+
|
|
106
|
+
Register the bundled MCP shim so the model gets three first-class tools
|
|
107
|
+
(`escarp_status`, `escarp_acquire`, `escarp_release`) and never has to
|
|
108
|
+
curl the broker by hand:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Claude Code
|
|
112
|
+
claude mcp add escarp -- escarp-mcp
|
|
113
|
+
|
|
114
|
+
# Codex CLI
|
|
115
|
+
codex mcp add escarp escarp-mcp
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Auto-heartbeat lives in the shim, so a long-running session can't lose the
|
|
119
|
+
lease mid-task. On disconnect the lease releases and the slot returns to
|
|
120
|
+
the pool, reset to about:blank.
|
|
121
|
+
|
|
122
|
+
## HTTP API (three verbs)
|
|
123
|
+
|
|
124
|
+
| Verb | Body | Returns |
|
|
125
|
+
| ---- | ---- | ------- |
|
|
126
|
+
| `GET /status` | -- | Pool snapshot, no lease tokens leaked |
|
|
127
|
+
| `POST /acquire` | `{"holder": str, "slot"?: int, "dev_port"?: int}` | `{slot, cdp_ws_url, lease_token, expires_at, ...}` |
|
|
128
|
+
| `POST /heartbeat` | `{"lease_token": str}` | Refreshed lease record |
|
|
129
|
+
| `POST /release` | `{"lease_token": str}` | Lease record in `state: free` |
|
|
130
|
+
| `GET /reaped` | -- | Last 50 TTL-expired reclamations (debug) |
|
|
131
|
+
|
|
132
|
+
## Architecture (one paragraph)
|
|
133
|
+
|
|
134
|
+
**Control plane (escarp):** slot allocator with kernel-flock atomicity,
|
|
135
|
+
lease broker with TTL + reaper, HTTP API on 7878, MCP shim. **Data plane
|
|
136
|
+
(your agent's tools):** Playwright `connect_over_cdp`, `@playwright/mcp`
|
|
137
|
+
`--cdp-endpoint`, `chrome-devtools-mcp` `--browser-url`, or whatever CDP
|
|
138
|
+
client you want, attached directly to the leased `cdp_ws_url`. Escarp
|
|
139
|
+
provisions and points; it never proxies your clicks. If escarp ever shows
|
|
140
|
+
up in your per-click latency, that's a bug.
|
|
141
|
+
|
|
142
|
+
The persistence contract is the load-bearing trick: chromes are launched
|
|
143
|
+
detached (`start_new_session=True`) and reparent to launchd/init. The
|
|
144
|
+
daemon discovers them by GET `/json/version`; it never owns their
|
|
145
|
+
lifecycle. Kill the daemon, chromes stay up. Kill an agent mid-task, the
|
|
146
|
+
reaper reclaims its lease within one sweep interval (default 2 s). On every
|
|
147
|
+
release boundary the broker `PUT /json/new?about:blank`s a fresh tab and
|
|
148
|
+
closes the old ones, so no state inherits across holders.
|
|
149
|
+
|
|
150
|
+
See [V2_PLAN.md](V2_PLAN.md) for the full design and decision record.
|
|
151
|
+
|
|
152
|
+
## Demos
|
|
153
|
+
|
|
154
|
+
The repo ships scripts that prove the headline claims end-to-end:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Two holders, two browsers, asyncio.gather'd lockstep concurrency.
|
|
158
|
+
# Steps fire within ~70 ms across both browsers; ~2x parallel speedup.
|
|
159
|
+
uv run python scripts/demo_two_holders_concurrent.py
|
|
160
|
+
|
|
161
|
+
# Lease-boundary reset: drive to YouTube, release, watch the tab snap back
|
|
162
|
+
# to about:blank. Proof that state does not leak across holders.
|
|
163
|
+
uv run python scripts/demo_reset_on_release.py
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Configuration
|
|
167
|
+
|
|
168
|
+
| Env var | Default | What |
|
|
169
|
+
| ------- | ------- | ---- |
|
|
170
|
+
| `ESCARP_POOL_SIZE` | `4` | Number of browser slots |
|
|
171
|
+
| `ESCARP_CDP_BASE` | `9222` | cdp port for slot 0; slot N uses base+N |
|
|
172
|
+
| `ESCARP_API_PORT` | `7878` | Broker HTTP API port (bind-and-shift on collision) |
|
|
173
|
+
| `ESCARP_LEASE_TTL_S` | `60` | Lease expiry; reaper reclaims past this |
|
|
174
|
+
| `ESCARP_CFT_BINARY` | autodetect | Path to Chrome for Testing binary |
|
|
175
|
+
| `ESCARP_BROKER_URL` | `http://127.0.0.1:7878` | Where the MCP shim looks for the broker |
|
|
176
|
+
|
|
177
|
+
## Per-slot resource derivation
|
|
178
|
+
|
|
179
|
+
Each slot derives all its resources from a single index, so two worktrees
|
|
180
|
+
on different slots never collide on ports:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
slot s -> frontend = 3000 + s*10
|
|
184
|
+
backend = 8000 + s*10
|
|
185
|
+
postgres = 5432 + s*10
|
|
186
|
+
cdp_port = 9222 + s
|
|
187
|
+
user_data = ~/.escarp/profiles/<tier>/slot-<s>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Status
|
|
191
|
+
|
|
192
|
+
v1.0.0. The four headline claims hold:
|
|
193
|
+
|
|
194
|
+
- Two agents on different slots drive their own leased CfTs, never collide.
|
|
195
|
+
- Killing an agent mid-task returns its browser within one reaper interval.
|
|
196
|
+
- Pool exhaustion returns a structured 409, not a hang.
|
|
197
|
+
- No lockfiles outside the broker's single source of truth.
|
|
198
|
+
|
|
199
|
+
What's not in 1.0: delegated and supervised identity tiers (v1.1 and v1.2),
|
|
200
|
+
cross-machine pooling (v2), Docker compose orchestration (hooks only for
|
|
201
|
+
now -- escarp doesn't own compose semantics).
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT
|
escarp-1.0.0/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# escarp
|
|
2
|
+
|
|
3
|
+
**Identity-aware runtime for parallel coding agents.** A single broker daemon
|
|
4
|
+
owns a pool of persistent Chrome for Testing windows and hands CDP endpoint
|
|
5
|
+
leases to coding agents (Claude Code, Codex) over MCP. N worktrees can run N
|
|
6
|
+
agents with N isolated browsers, no stale-lock hell.
|
|
7
|
+
|
|
8
|
+
> **Why?** Spawn-a-browser-per-tool-call leaks chromes on every chat exit.
|
|
9
|
+
> Per-agent lockfiles strand themselves when the agent dies. Driving the
|
|
10
|
+
> user's daily-driver browser pollutes cookies and session state. Escarp
|
|
11
|
+
> separates lifecycle (persistent, owned by escarp) from leases (ephemeral,
|
|
12
|
+
> owned by the agent). The browsers always exist; agents check them out.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install escarp
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Requirements: Python 3.11+, a Chrome for Testing binary on disk. Easiest way
|
|
21
|
+
to get one:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @puppeteer/browsers install chrome@stable
|
|
25
|
+
export ESCARP_CFT_BINARY=".../Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# 1. Spawn N persistent Chrome for Testing windows (one-shot, exits immediately).
|
|
32
|
+
# The chromes are detached and survive escarp restarts.
|
|
33
|
+
escarp launch-pool # default 4
|
|
34
|
+
ESCARP_POOL_SIZE=8 escarp launch-pool
|
|
35
|
+
|
|
36
|
+
# 2. Start the broker daemon. It discovers the chromes via /json/version,
|
|
37
|
+
# serves the lease HTTP API on 127.0.0.1:7878, and runs a reaper for
|
|
38
|
+
# expired leases. ^C releases the slot locks but leaves chromes alive.
|
|
39
|
+
escarp daemon &
|
|
40
|
+
|
|
41
|
+
# 3. Inspect the pool
|
|
42
|
+
curl -s http://127.0.0.1:7878/status | jq
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Drive one of the leased browsers in Python:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import asyncio, httpx
|
|
49
|
+
from playwright.async_api import async_playwright
|
|
50
|
+
|
|
51
|
+
async def main():
|
|
52
|
+
lease = httpx.post(
|
|
53
|
+
"http://127.0.0.1:7878/acquire",
|
|
54
|
+
json={"holder": "demo-script"},
|
|
55
|
+
).json()
|
|
56
|
+
print("got slot", lease["slot"], "->", lease["cdp_ws_url"])
|
|
57
|
+
|
|
58
|
+
async with async_playwright() as pw:
|
|
59
|
+
browser = await pw.chromium.connect_over_cdp(lease["cdp_ws_url"])
|
|
60
|
+
page = browser.contexts[0].pages[0]
|
|
61
|
+
await page.goto("https://example.com")
|
|
62
|
+
await page.screenshot(path="/tmp/example.png")
|
|
63
|
+
await browser.close() # disconnect; broker-owned chrome stays alive
|
|
64
|
+
|
|
65
|
+
httpx.post(
|
|
66
|
+
"http://127.0.0.1:7878/release",
|
|
67
|
+
json={"lease_token": lease["lease_token"]},
|
|
68
|
+
) # broker auto-resets the tab to about:blank
|
|
69
|
+
|
|
70
|
+
asyncio.run(main())
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Wire it into Claude Code / Codex via MCP
|
|
74
|
+
|
|
75
|
+
Register the bundled MCP shim so the model gets three first-class tools
|
|
76
|
+
(`escarp_status`, `escarp_acquire`, `escarp_release`) and never has to
|
|
77
|
+
curl the broker by hand:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Claude Code
|
|
81
|
+
claude mcp add escarp -- escarp-mcp
|
|
82
|
+
|
|
83
|
+
# Codex CLI
|
|
84
|
+
codex mcp add escarp escarp-mcp
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Auto-heartbeat lives in the shim, so a long-running session can't lose the
|
|
88
|
+
lease mid-task. On disconnect the lease releases and the slot returns to
|
|
89
|
+
the pool, reset to about:blank.
|
|
90
|
+
|
|
91
|
+
## HTTP API (three verbs)
|
|
92
|
+
|
|
93
|
+
| Verb | Body | Returns |
|
|
94
|
+
| ---- | ---- | ------- |
|
|
95
|
+
| `GET /status` | -- | Pool snapshot, no lease tokens leaked |
|
|
96
|
+
| `POST /acquire` | `{"holder": str, "slot"?: int, "dev_port"?: int}` | `{slot, cdp_ws_url, lease_token, expires_at, ...}` |
|
|
97
|
+
| `POST /heartbeat` | `{"lease_token": str}` | Refreshed lease record |
|
|
98
|
+
| `POST /release` | `{"lease_token": str}` | Lease record in `state: free` |
|
|
99
|
+
| `GET /reaped` | -- | Last 50 TTL-expired reclamations (debug) |
|
|
100
|
+
|
|
101
|
+
## Architecture (one paragraph)
|
|
102
|
+
|
|
103
|
+
**Control plane (escarp):** slot allocator with kernel-flock atomicity,
|
|
104
|
+
lease broker with TTL + reaper, HTTP API on 7878, MCP shim. **Data plane
|
|
105
|
+
(your agent's tools):** Playwright `connect_over_cdp`, `@playwright/mcp`
|
|
106
|
+
`--cdp-endpoint`, `chrome-devtools-mcp` `--browser-url`, or whatever CDP
|
|
107
|
+
client you want, attached directly to the leased `cdp_ws_url`. Escarp
|
|
108
|
+
provisions and points; it never proxies your clicks. If escarp ever shows
|
|
109
|
+
up in your per-click latency, that's a bug.
|
|
110
|
+
|
|
111
|
+
The persistence contract is the load-bearing trick: chromes are launched
|
|
112
|
+
detached (`start_new_session=True`) and reparent to launchd/init. The
|
|
113
|
+
daemon discovers them by GET `/json/version`; it never owns their
|
|
114
|
+
lifecycle. Kill the daemon, chromes stay up. Kill an agent mid-task, the
|
|
115
|
+
reaper reclaims its lease within one sweep interval (default 2 s). On every
|
|
116
|
+
release boundary the broker `PUT /json/new?about:blank`s a fresh tab and
|
|
117
|
+
closes the old ones, so no state inherits across holders.
|
|
118
|
+
|
|
119
|
+
See [V2_PLAN.md](V2_PLAN.md) for the full design and decision record.
|
|
120
|
+
|
|
121
|
+
## Demos
|
|
122
|
+
|
|
123
|
+
The repo ships scripts that prove the headline claims end-to-end:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Two holders, two browsers, asyncio.gather'd lockstep concurrency.
|
|
127
|
+
# Steps fire within ~70 ms across both browsers; ~2x parallel speedup.
|
|
128
|
+
uv run python scripts/demo_two_holders_concurrent.py
|
|
129
|
+
|
|
130
|
+
# Lease-boundary reset: drive to YouTube, release, watch the tab snap back
|
|
131
|
+
# to about:blank. Proof that state does not leak across holders.
|
|
132
|
+
uv run python scripts/demo_reset_on_release.py
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Configuration
|
|
136
|
+
|
|
137
|
+
| Env var | Default | What |
|
|
138
|
+
| ------- | ------- | ---- |
|
|
139
|
+
| `ESCARP_POOL_SIZE` | `4` | Number of browser slots |
|
|
140
|
+
| `ESCARP_CDP_BASE` | `9222` | cdp port for slot 0; slot N uses base+N |
|
|
141
|
+
| `ESCARP_API_PORT` | `7878` | Broker HTTP API port (bind-and-shift on collision) |
|
|
142
|
+
| `ESCARP_LEASE_TTL_S` | `60` | Lease expiry; reaper reclaims past this |
|
|
143
|
+
| `ESCARP_CFT_BINARY` | autodetect | Path to Chrome for Testing binary |
|
|
144
|
+
| `ESCARP_BROKER_URL` | `http://127.0.0.1:7878` | Where the MCP shim looks for the broker |
|
|
145
|
+
|
|
146
|
+
## Per-slot resource derivation
|
|
147
|
+
|
|
148
|
+
Each slot derives all its resources from a single index, so two worktrees
|
|
149
|
+
on different slots never collide on ports:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
slot s -> frontend = 3000 + s*10
|
|
153
|
+
backend = 8000 + s*10
|
|
154
|
+
postgres = 5432 + s*10
|
|
155
|
+
cdp_port = 9222 + s
|
|
156
|
+
user_data = ~/.escarp/profiles/<tier>/slot-<s>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Status
|
|
160
|
+
|
|
161
|
+
v1.0.0. The four headline claims hold:
|
|
162
|
+
|
|
163
|
+
- Two agents on different slots drive their own leased CfTs, never collide.
|
|
164
|
+
- Killing an agent mid-task returns its browser within one reaper interval.
|
|
165
|
+
- Pool exhaustion returns a structured 409, not a hang.
|
|
166
|
+
- No lockfiles outside the broker's single source of truth.
|
|
167
|
+
|
|
168
|
+
What's not in 1.0: delegated and supervised identity tiers (v1.1 and v1.2),
|
|
169
|
+
cross-machine pooling (v2), Docker compose orchestration (hooks only for
|
|
170
|
+
now -- escarp doesn't own compose semantics).
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT
|