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:
+3
-13
@@ -8,19 +8,9 @@
|
|||||||
"Bash(make aperant-install:*)",
|
"Bash(make aperant-install:*)",
|
||||||
"Bash(make aperant-build:*)",
|
"Bash(make aperant-build:*)",
|
||||||
"Bash(make aperant-test:*)",
|
"Bash(make aperant-test:*)",
|
||||||
"Bash(/Users/electron/Documents/Lelectron_rare/Kill_LIFE/tools/run_knowledge_base_mcp.sh --doctor)",
|
"Bash(./tools/run_knowledge_base_mcp.sh --doctor)",
|
||||||
"Bash(/Users/electron/Documents/Lelectron_rare/Kill_LIFE/tools/run_github_dispatch_mcp.sh --doctor)",
|
"Bash(./tools/run_github_dispatch_mcp.sh --doctor)",
|
||||||
"Bash(/Users/electron/Documents/Lelectron_rare/Kill_LIFE/tools/hw/run_kicad_mcp.sh --doctor)",
|
"Bash(./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"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -26,6 +26,9 @@ ENV/
|
|||||||
.vscode/
|
.vscode/
|
||||||
.history/
|
.history/
|
||||||
|
|
||||||
|
# Claude Code (machine-specific paths)
|
||||||
|
.claude/settings.json
|
||||||
|
|
||||||
# MacOS
|
# MacOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.AppleDouble
|
.AppleDouble
|
||||||
|
|||||||
@@ -97,6 +97,6 @@ KiCad 10 schematics. KiBot for exports (BOM, SVG, PDF, netlist). ERC validation
|
|||||||
|
|
||||||
## External Services
|
## 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
|
- **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)
|
- **n8n** — workflow automation (ZeroClaw integration)
|
||||||
|
|||||||
@@ -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
@@ -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
|
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_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
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
@@ -161,9 +163,9 @@ async def list_mcp_servers():
|
|||||||
@app.get("/bridge/mascarade")
|
@app.get("/bridge/mascarade")
|
||||||
async def mascarade_bridge_info():
|
async def mascarade_bridge_info():
|
||||||
return {
|
return {
|
||||||
"mascarade_api": "http://192.168.0.119:8000",
|
"mascarade_api": MASCARADE_API_URL,
|
||||||
"mascarade_core": "http://192.168.0.119:8100",
|
"mascarade_core": MASCARADE_CORE_URL,
|
||||||
"p2p_bootstrap": "192.168.0.119:4002",
|
"p2p_bootstrap": P2P_BOOTSTRAP,
|
||||||
"mcp_servers_count": len(_load_mcp_json().get("mcpServers", {})),
|
"mcp_servers_count": len(_load_mcp_json().get("mcpServers", {})),
|
||||||
"integration": {
|
"integration": {
|
||||||
"specs_as_datasets": "mascarade finetune/ reads Kill_LIFE specs/",
|
"specs_as_datasets": "mascarade finetune/ reads Kill_LIFE specs/",
|
||||||
|
|||||||
+76
-10
@@ -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:
|
Polls the Mascarade task queue and dispatches agent execution requests
|
||||||
- Poll a task queue (Redis/mascarade P2P)
|
to the local Kill_LIFE API. Gracefully degrades when Mascarade is
|
||||||
- Execute BMAD agent pipelines
|
unreachable (logs and retries).
|
||||||
- Report results back to the API
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [kill-life-worker] %(message)s")
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [kill-life-worker] %(message)s")
|
||||||
log = logging.getLogger(__name__)
|
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
|
_running = True
|
||||||
|
|
||||||
|
|
||||||
@@ -25,16 +31,76 @@ def _shutdown(signum, frame):
|
|||||||
_running = False
|
_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():
|
async def main():
|
||||||
signal.signal(signal.SIGTERM, _shutdown)
|
signal.signal(signal.SIGTERM, _shutdown)
|
||||||
signal.signal(signal.SIGINT, _shutdown)
|
signal.signal(signal.SIGINT, _shutdown)
|
||||||
|
|
||||||
log.info("Kill_LIFE worker started. Waiting for tasks...")
|
log.info("Kill_LIFE worker started")
|
||||||
log.info(" MASCARADE_CORE_URL: %s", __import__("os").environ.get("MASCARADE_CORE_URL", "(not set)"))
|
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:
|
async with httpx.AsyncClient() as client:
|
||||||
# Stub: sleep and wait. Real implementation would poll task queue.
|
while _running:
|
||||||
await asyncio.sleep(5)
|
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.")
|
log.info("Worker stopped.")
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
},
|
},
|
||||||
"mascarade-bridge": {
|
"mascarade-bridge": {
|
||||||
"type": "url",
|
"type": "url",
|
||||||
"url": "http://192.168.0.119:8100/mcp",
|
"url": "http://localhost:8100/mcp",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Authorization": "Bearer ${MASCARADE_API_KEY}"
|
"Authorization": "Bearer ${MASCARADE_API_KEY}"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -81,5 +81,27 @@ class KillLifeAgentApiTests(unittest.TestCase):
|
|||||||
self.assertIn('"lot_id": "T-123"', DummyAsyncClient.last_payload["messages"][0]["content"])
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user