fix: remove hardcoded IPs + implement worker + add gate S2 + .env.example

- Replace 192.168.0.119 hardcoded IPs with env vars in server.py, mcp.json, CLAUDE.md
- Implement worker HTTP polling against Mascarade task queue (was a sleep stub)
- Add .env.example documenting all required env vars
- Fill empty bmad/gates/gate_s2.md with web/PR review checklist
- Gitignore .claude/settings.json and remove private macOS paths
- Add test for /bridge/mascarade env var usage (no hardcoded IPs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clement Saillant
2026-03-29 18:48:26 +00:00
parent ea472a3ae2
commit e9f87a38da
9 changed files with 151 additions and 28 deletions
+3 -13
View File
@@ -8,19 +8,9 @@
"Bash(make aperant-install:*)",
"Bash(make aperant-build:*)",
"Bash(make aperant-test:*)",
"Bash(/Users/electron/Documents/Lelectron_rare/Kill_LIFE/tools/run_knowledge_base_mcp.sh --doctor)",
"Bash(/Users/electron/Documents/Lelectron_rare/Kill_LIFE/tools/run_github_dispatch_mcp.sh --doctor)",
"Bash(/Users/electron/Documents/Lelectron_rare/Kill_LIFE/tools/hw/run_kicad_mcp.sh --doctor)",
"Bash(ssh -o ConnectTimeout=5 -o BatchMode=yes clems@192.168.0.120 'echo OK')",
"Bash(ssh -o ConnectTimeout=5 -o BatchMode=yes root@192.168.0.119 'echo OK')",
"Bash(ssh -o ConnectTimeout=5 -o BatchMode=yes kxkm@kxkm-ai 'echo OK')",
"Bash(ssh -o ConnectTimeout=5 -o BatchMode=yes cils@192.168.0.210 'echo OK')"
],
"additionalDirectories": [
"/Users/electron/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings",
"/Volumes/root/mascarade-main/api/src/routes",
"/Volumes/root/mascarade-main/api/src",
"/Volumes/root/mascarade-main/core/tests"
"Bash(./tools/run_knowledge_base_mcp.sh --doctor)",
"Bash(./tools/run_github_dispatch_mcp.sh --doctor)",
"Bash(./tools/hw/run_kicad_mcp.sh --doctor)"
]
}
}
+23
View File
@@ -0,0 +1,23 @@
# Kill_LIFE — Environment variables
# Copy to .env and adjust values for your setup.
# Mascarade integration (LLM router / agent execution)
MASCARADE_CORE_URL=http://localhost:8100
MASCARADE_API_URL=http://localhost:8000
MASCARADE_API_KEY=
MASCARADE_MCP_URL=http://localhost:8100/mcp
# P2P mesh bootstrap node
P2P_BOOTSTRAP=localhost:4002
# Worker config
KILL_LIFE_API_URL=http://localhost:8200
WORKER_POLL_INTERVAL=10
# MCP servers (optional)
APIFY_API_KEY=
HUGGINGFACE_API_KEY=
# CI/CD (optional)
WOKWI_CLI_TOKEN=
KILL_LIFE_ENV=development
+3
View File
@@ -26,6 +26,9 @@ ENV/
.vscode/
.history/
# Claude Code (machine-specific paths)
.claude/settings.json
# MacOS
.DS_Store
.AppleDouble
+1 -1
View File
@@ -97,6 +97,6 @@ KiCad 10 schematics. KiBot for exports (BOM, SVG, PDF, netlist). ERC validation
## External Services
- **Mascarade** (`MASCARADE_CORE_URL`, default `http://192.168.0.119:8100`) — LLM router with agentic RAG
- **Mascarade** (`MASCARADE_CORE_URL`, default `http://localhost:8100`) — LLM router with agentic RAG
- **MCP servers** (10 configured in `mcp.json`) — kicad, freecad, openscad, platformio, github-dispatch, knowledge-base, validate-specs, apify, huggingface, mascarade-bridge
- **n8n** — workflow automation (ZeroClaw integration)
+17
View File
@@ -0,0 +1,17 @@
# Gate S2 — Web / PR review
Web (si concerné):
- [ ] `npm run build` ok (aucune erreur TypeScript)
- [ ] `npx playwright test e2e/smoke.spec.ts` ok
- [ ] Pas de régression visuelle sur les pages modifiées
API (si concerné):
- [ ] `python -m unittest discover test/` — tests stable verts
- [ ] Endpoints modifiés documentés dans `specs/contracts/`
- [ ] Pas de chemin hardcodé (IPs, paths absolus)
PR review:
- [ ] Scope guard validé (write_set respecté par agent)
- [ ] Pas de secret dans le diff (`CLAUDE.md`, `.env`, tokens)
- [ ] Handoff template rempli si cross-repo
- [ ] Evidence pack généré (si lot concerné)
+5 -3
View File
@@ -15,6 +15,8 @@ from kill_life import __version__
from kill_life.agent_catalog import canonical_agents_by_id, canonical_agents_for_api, legacy_runtime_mapping
MASCARADE_CORE_URL = os.environ.get("MASCARADE_CORE_URL", "http://localhost:8100")
MASCARADE_API_URL = os.environ.get("MASCARADE_API_URL", "http://localhost:8000")
P2P_BOOTSTRAP = os.environ.get("P2P_BOOTSTRAP", "localhost:4002")
REPO_ROOT = Path(__file__).resolve().parent.parent
@@ -161,9 +163,9 @@ async def list_mcp_servers():
@app.get("/bridge/mascarade")
async def mascarade_bridge_info():
return {
"mascarade_api": "http://192.168.0.119:8000",
"mascarade_core": "http://192.168.0.119:8100",
"p2p_bootstrap": "192.168.0.119:4002",
"mascarade_api": MASCARADE_API_URL,
"mascarade_core": MASCARADE_CORE_URL,
"p2p_bootstrap": P2P_BOOTSTRAP,
"mcp_servers_count": len(_load_mcp_json().get("mcpServers", {})),
"integration": {
"specs_as_datasets": "mascarade finetune/ reads Kill_LIFE specs/",
+76 -10
View File
@@ -1,21 +1,27 @@
"""Kill_LIFE background worker — processes agent execution tasks.
"""Kill_LIFE background worker — polls mascarade-core for pending tasks.
Stub implementation. In production, this would:
- Poll a task queue (Redis/mascarade P2P)
- Execute BMAD agent pipelines
- Report results back to the API
Polls the Mascarade task queue and dispatches agent execution requests
to the local Kill_LIFE API. Gracefully degrades when Mascarade is
unreachable (logs and retries).
"""
from __future__ import annotations
import asyncio
import logging
import os
import signal
import sys
import httpx
logging.basicConfig(level=logging.INFO, format="%(asctime)s [kill-life-worker] %(message)s")
log = logging.getLogger(__name__)
MASCARADE_CORE_URL = os.environ.get("MASCARADE_CORE_URL", "http://localhost:8100")
KILL_LIFE_API_URL = os.environ.get("KILL_LIFE_API_URL", "http://localhost:8200")
POLL_INTERVAL = int(os.environ.get("WORKER_POLL_INTERVAL", "10"))
_running = True
@@ -25,16 +31,76 @@ def _shutdown(signum, frame):
_running = False
async def poll_and_dispatch(client: httpx.AsyncClient) -> int:
"""Poll Mascarade for pending tasks and dispatch them. Returns count of tasks processed."""
try:
resp = await client.get(
f"{MASCARADE_CORE_URL}/v1/api/agents/queue",
params={"source": "kill-life"},
timeout=10.0,
)
if resp.status_code == 404:
# Endpoint not available yet — not an error
return 0
resp.raise_for_status()
data = resp.json()
except httpx.ConnectError:
log.debug("Mascarade unreachable at %s — will retry", MASCARADE_CORE_URL)
return 0
except httpx.TimeoutException:
log.debug("Mascarade poll timed out")
return 0
except Exception as exc:
log.warning("Poll error: %s", exc)
return 0
tasks = data.get("tasks", [])
if not tasks:
return 0
processed = 0
for task in tasks:
agent_name = task.get("agent")
task_input = task.get("input", "")
task_id = task.get("id", "unknown")
if not agent_name:
log.warning("Task %s has no agent name, skipping", task_id)
continue
log.info("Dispatching task %s → agent %s", task_id, agent_name)
try:
run_resp = await client.post(
f"{KILL_LIFE_API_URL}/agents/{agent_name}/run",
json={"input": task_input, "context": task.get("context")},
timeout=60.0,
)
if run_resp.is_success:
log.info("Task %s completed (agent=%s)", task_id, agent_name)
processed += 1
else:
log.warning("Task %s failed: %s %s", task_id, run_resp.status_code, run_resp.text[:200])
except Exception as exc:
log.warning("Task %s dispatch error: %s", task_id, exc)
return processed
async def main():
signal.signal(signal.SIGTERM, _shutdown)
signal.signal(signal.SIGINT, _shutdown)
log.info("Kill_LIFE worker started. Waiting for tasks...")
log.info(" MASCARADE_CORE_URL: %s", __import__("os").environ.get("MASCARADE_CORE_URL", "(not set)"))
log.info("Kill_LIFE worker started")
log.info(" MASCARADE_CORE_URL: %s", MASCARADE_CORE_URL)
log.info(" KILL_LIFE_API_URL: %s", KILL_LIFE_API_URL)
log.info(" POLL_INTERVAL: %ds", POLL_INTERVAL)
while _running:
# Stub: sleep and wait. Real implementation would poll task queue.
await asyncio.sleep(5)
async with httpx.AsyncClient() as client:
while _running:
count = await poll_and_dispatch(client)
if count:
log.info("Processed %d task(s) this cycle", count)
await asyncio.sleep(POLL_INTERVAL)
log.info("Worker stopped.")
+1 -1
View File
@@ -67,7 +67,7 @@
},
"mascarade-bridge": {
"type": "url",
"url": "http://192.168.0.119:8100/mcp",
"url": "http://localhost:8100/mcp",
"headers": {
"Authorization": "Bearer ${MASCARADE_API_KEY}"
},
+22
View File
@@ -81,5 +81,27 @@ class KillLifeAgentApiTests(unittest.TestCase):
self.assertIn('"lot_id": "T-123"', DummyAsyncClient.last_payload["messages"][0]["content"])
def test_bridge_mascarade_uses_env_vars(self) -> None:
"""Ensure /bridge/mascarade returns env-based URLs, not hardcoded IPs."""
with patch.dict("os.environ", {
"MASCARADE_API_URL": "http://test-api:8000",
"MASCARADE_CORE_URL": "http://test-core:8100",
"P2P_BOOTSTRAP": "test-node:4002",
}):
# Re-import to pick up env vars
import importlib
import kill_life.server as srv
importlib.reload(srv)
client = TestClient(srv.app)
response = client.get("/bridge/mascarade")
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertEqual(data["mascarade_api"], "http://test-api:8000")
self.assertEqual(data["mascarade_core"], "http://test-core:8100")
self.assertEqual(data["p2p_bootstrap"], "test-node:4002")
# No hardcoded IPs
self.assertNotIn("192.168.0.119", str(data))
if __name__ == "__main__":
unittest.main()