mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(agent): launch agent uv-run subprocesses with --no-sync
Agents with a write workspace (developer/product_owner/head_marketing/documenter) run with cwd = their git workspace clone. Claude Code launches each MCP server (flow/do/git-readonly/optimal/docs/search) and the SDK server as `uv run python -m ...` from that cwd. When the clone's uv.lock drifts from the baked image, `uv run` re-resolves and re-syncs /app/.venv against the clone's lock — a multi-minute stall on a cold wheel cache — so the servers never reach "connected": they sit at status="pending" and the agent gets ZERO gateway verbs. It then can't claim/commit/idle (all MCP verbs), its Stop is rejected, and it respawns in a loop redoing work it can't submit. UV_PROJECT_ENVIRONMENT pins the venv location but does NOT stop the cwd-relative resolve/sync (confirmed empirically on uv 0.11.1); `--no-sync` does, so the servers reuse the baked /app/.venv as-is and start instantly. The /app-cwd roles (qa/cell_pm/main_pm/auditor) were unaffected because their env already matches. - orchestrator.py: --no-sync on all 6 generated MCP servers - docker/scripts/sdk-startup-hook.sh: --no-sync on the agent_sdk.server launch - test_spawn_strict_mcp.py: assert every server's args start with run,--no-sync
This commit is contained in:
@@ -18,15 +18,18 @@ PRECOMPACT_FILE="/tmp/roboco-precompact-${AGENT_ID}.md"
|
|||||||
# set (torch/lancedb/pyarrow/scipy, ~350MB) into a fresh venv. On a cold
|
# set (torch/lancedb/pyarrow/scipy, ~350MB) into a fresh venv. On a cold
|
||||||
# uv wheel cache (first spawn after an image rebuild) that download takes
|
# uv wheel cache (first spawn after an image rebuild) that download takes
|
||||||
# minutes and the SDK/MCP layer never comes up before the agent reaps.
|
# minutes and the SDK/MCP layer never comes up before the agent reaps.
|
||||||
# Pin uv to the pre-baked image venv so it starts instantly regardless
|
# Pin uv to the pre-baked image venv AND pass `--no-sync` on the run below.
|
||||||
# of cwd. (The orchestrator sets the same var in every MCP server's env
|
# Pinning the env location alone is NOT sufficient: `uv run` still discovers
|
||||||
# in the generated mcp-config.json — keep both in sync.)
|
# the cwd project and re-syncs the pinned venv against the clone's (drifted)
|
||||||
|
# lock — that resync is the actual multi-minute stall. `--no-sync` skips it.
|
||||||
|
# (The orchestrator passes the same var + --no-sync to every MCP server in
|
||||||
|
# the generated mcp-config.json — keep both in sync.)
|
||||||
export UV_PROJECT_ENVIRONMENT=/app/.venv
|
export UV_PROJECT_ENVIRONMENT=/app/.venv
|
||||||
|
|
||||||
# --- SDK bring-up ---------------------------------------------------------
|
# --- SDK bring-up ---------------------------------------------------------
|
||||||
if ! curl -sf "http://localhost:${SDK_PORT}/health" >/dev/null 2>&1; then
|
if ! curl -sf "http://localhost:${SDK_PORT}/health" >/dev/null 2>&1; then
|
||||||
echo "[SDK] Starting for agent ${AGENT_ID} on port ${SDK_PORT}..."
|
echo "[SDK] Starting for agent ${AGENT_ID} on port ${SDK_PORT}..."
|
||||||
nohup uv run python -m roboco.agent_sdk.server > "$LOG_FILE" 2>&1 &
|
nohup uv run --no-sync python -m roboco.agent_sdk.server > "$LOG_FILE" 2>&1 &
|
||||||
SDK_PID=$!
|
SDK_PID=$!
|
||||||
sleep 2
|
sleep 2
|
||||||
if curl -sf "http://localhost:${SDK_PORT}/health" >/dev/null 2>&1; then
|
if curl -sf "http://localhost:${SDK_PORT}/health" >/dev/null 2>&1; then
|
||||||
|
|||||||
@@ -2015,8 +2015,12 @@ class AgentOrchestrator:
|
|||||||
# but on a cold cache (first spawn after an image rebuild) the
|
# but on a cold cache (first spawn after an image rebuild) the
|
||||||
# download takes minutes and the MCP servers never come up
|
# download takes minutes and the MCP servers never come up
|
||||||
# before the agent burns its budget. Pinning the project env
|
# before the agent burns its budget. Pinning the project env
|
||||||
# to the pre-baked venv makes `uv run` reuse it instantly,
|
# to the pre-baked venv is necessary but NOT sufficient: `uv run`
|
||||||
# regardless of cwd.
|
# still resolves the project from the workspace cwd and re-syncs
|
||||||
|
# when the clone's uv.lock drifts from the image — leaving the MCP
|
||||||
|
# servers stuck at status="pending" so the agent gets zero gateway
|
||||||
|
# verbs. Each server is therefore launched with `uv run --no-sync`
|
||||||
|
# (below) to use /app/.venv as-is and start instantly.
|
||||||
"UV_PROJECT_ENVIRONMENT": "/app/.venv",
|
"UV_PROJECT_ENVIRONMENT": "/app/.venv",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2031,19 +2035,19 @@ class AgentOrchestrator:
|
|||||||
# Intent verbs — every role-scoped lifecycle transition.
|
# Intent verbs — every role-scoped lifecycle transition.
|
||||||
"roboco-flow": {
|
"roboco-flow": {
|
||||||
"command": "uv",
|
"command": "uv",
|
||||||
"args": ["run", "python", "-m", "roboco.mcp.flow_server"],
|
"args": ["run", "--no-sync", "python", "-m", "roboco.mcp.flow_server"],
|
||||||
"env": mcp_env,
|
"env": mcp_env,
|
||||||
},
|
},
|
||||||
# Content tools — commit, push, PR, journal, notify, message.
|
# Content tools — commit, push, PR, journal, notify, message.
|
||||||
"roboco-do": {
|
"roboco-do": {
|
||||||
"command": "uv",
|
"command": "uv",
|
||||||
"args": ["run", "python", "-m", "roboco.mcp.do_server"],
|
"args": ["run", "--no-sync", "python", "-m", "roboco.mcp.do_server"],
|
||||||
"env": mcp_env,
|
"env": mcp_env,
|
||||||
},
|
},
|
||||||
# Read-only git views — status, log, diff, branches.
|
# Read-only git views — status, log, diff, branches.
|
||||||
"roboco-git-readonly": {
|
"roboco-git-readonly": {
|
||||||
"command": "uv",
|
"command": "uv",
|
||||||
"args": ["run", "python", "-m", "roboco.mcp.git_readonly"],
|
"args": ["run", "--no-sync", "python", "-m", "roboco.mcp.git_readonly"],
|
||||||
"env": mcp_env,
|
"env": mcp_env,
|
||||||
},
|
},
|
||||||
# Knowledge base — RAG / semantic search / ask_mentor.
|
# Knowledge base — RAG / semantic search / ask_mentor.
|
||||||
@@ -2051,6 +2055,7 @@ class AgentOrchestrator:
|
|||||||
"command": "uv",
|
"command": "uv",
|
||||||
"args": [
|
"args": [
|
||||||
"run",
|
"run",
|
||||||
|
"--no-sync",
|
||||||
"python",
|
"python",
|
||||||
"-m",
|
"-m",
|
||||||
"roboco.mcp.optimal_server",
|
"roboco.mcp.optimal_server",
|
||||||
@@ -2075,6 +2080,7 @@ class AgentOrchestrator:
|
|||||||
"command": "uv",
|
"command": "uv",
|
||||||
"args": [
|
"args": [
|
||||||
"run",
|
"run",
|
||||||
|
"--no-sync",
|
||||||
"python",
|
"python",
|
||||||
"-m",
|
"-m",
|
||||||
"roboco.mcp.docs_server",
|
"roboco.mcp.docs_server",
|
||||||
@@ -2097,6 +2103,7 @@ class AgentOrchestrator:
|
|||||||
"command": "uv",
|
"command": "uv",
|
||||||
"args": [
|
"args": [
|
||||||
"run",
|
"run",
|
||||||
|
"--no-sync",
|
||||||
"python",
|
"python",
|
||||||
"-m",
|
"-m",
|
||||||
"roboco.mcp.search_server",
|
"roboco.mcp.search_server",
|
||||||
|
|||||||
@@ -92,3 +92,13 @@ class TestMcpConfigPinsBakedVenv:
|
|||||||
f"/app/.venv — without it `uv run` re-downloads deps into a "
|
f"/app/.venv — without it `uv run` re-downloads deps into a "
|
||||||
f"cwd-relative venv on every spawn (#179). env={spec['env']}"
|
f"cwd-relative venv on every spawn (#179). env={spec['env']}"
|
||||||
)
|
)
|
||||||
|
# Pinning the env location is necessary but NOT sufficient: from a
|
||||||
|
# workspace-clone cwd `uv run` still discovers the clone project and
|
||||||
|
# re-syncs the pinned venv against its drifted lock, which stalls and
|
||||||
|
# leaves the server stuck at status="pending" (zero gateway verbs).
|
||||||
|
# `--no-sync` skips that resync — it must come right after `run`.
|
||||||
|
assert spec["args"][:2] == ["run", "--no-sync"], (
|
||||||
|
f"MCP server {name!r} must launch with `uv run --no-sync ...` so "
|
||||||
|
f"a drifted workspace-clone lock can't trigger a resync stall; "
|
||||||
|
f"got args={spec['args']}"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user