feat: Kill_LIFE service skeleton — FastAPI + Docker + mascarade bridge
- kill_life/ Python package (FastAPI v0.1.0) - server.py: 6 endpoints (health, agents, specs, agent/run, mcp/servers, bridge) - worker.py: background task worker stub - Dockerfile: python:3.12-slim + uv - docker-compose.yml: kill-life-api (port 8200) + kill-life-worker - mcp.json: added mascarade-bridge + kill-life-api server entries - 6 BMAD agents exposed, ~25 specs discoverable, MCP bridge to mascarade-core Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install uv for fast dependency management
|
||||
RUN pip install --no-cache-dir uv
|
||||
|
||||
# Copy dependency definition first (cache layer)
|
||||
COPY pyproject.toml .
|
||||
|
||||
# Install dependencies
|
||||
RUN uv pip install --system -e "."
|
||||
|
||||
# Copy source
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8200
|
||||
|
||||
CMD ["uvicorn", "kill_life.server:app", "--host", "0.0.0.0", "--port", "8200"]
|
||||
@@ -0,0 +1,44 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
kill-life-api:
|
||||
build: .
|
||||
container_name: kill-life-api
|
||||
ports:
|
||||
- "8200:8200"
|
||||
volumes:
|
||||
- .:/app
|
||||
environment:
|
||||
- MASCARADE_CORE_URL=http://mascarade-core:8100
|
||||
- MASCARADE_API_KEY=${MASCARADE_API_KEY:-}
|
||||
- KILL_LIFE_ENV=production
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import httpx; r=httpx.get('http://localhost:8200/health'); r.raise_for_status()"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- kill-life-net
|
||||
|
||||
kill-life-worker:
|
||||
build: .
|
||||
container_name: kill-life-worker
|
||||
command: ["python", "-m", "kill_life.worker"]
|
||||
volumes:
|
||||
- .:/app
|
||||
environment:
|
||||
- MASCARADE_CORE_URL=http://mascarade-core:8100
|
||||
- MASCARADE_API_KEY=${MASCARADE_API_KEY:-}
|
||||
- KILL_LIFE_ENV=production
|
||||
depends_on:
|
||||
kill-life-api:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- kill-life-net
|
||||
|
||||
networks:
|
||||
kill-life-net:
|
||||
driver: bridge
|
||||
name: kill-life-net
|
||||
@@ -0,0 +1,3 @@
|
||||
"""Kill_LIFE — AI-native control plane for embedded systems."""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
@@ -0,0 +1,156 @@
|
||||
"""Kill_LIFE FastAPI service — minimal skeleton for mascarade ecosystem integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from kill_life import __version__
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
app = FastAPI(
|
||||
title="Kill_LIFE API",
|
||||
version=__version__,
|
||||
description="Control plane API for Kill_LIFE embedded systems framework",
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Agent definitions (from agents/*.md)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
BMAD_AGENTS: dict[str, dict[str, str]] = {
|
||||
"pm": {
|
||||
"name": "PM Agent",
|
||||
"file": "agents/pm_agent.md",
|
||||
"role": "Project management, intake, prioritization",
|
||||
},
|
||||
"architect": {
|
||||
"name": "Architect Agent",
|
||||
"file": "agents/architect_agent.md",
|
||||
"role": "System architecture, component design, tech decisions",
|
||||
},
|
||||
"firmware": {
|
||||
"name": "Firmware Agent",
|
||||
"file": "agents/firmware_agent.md",
|
||||
"role": "Embedded firmware development, HAL, drivers",
|
||||
},
|
||||
"hw_schematic": {
|
||||
"name": "HW Schematic Agent",
|
||||
"file": "agents/hw_schematic_agent.md",
|
||||
"role": "Hardware schematics, KiCad, PCB layout, BOM",
|
||||
},
|
||||
"qa": {
|
||||
"name": "QA Agent",
|
||||
"file": "agents/qa_agent.md",
|
||||
"role": "Testing, compliance, evidence collection",
|
||||
},
|
||||
"doc": {
|
||||
"name": "Doc Agent",
|
||||
"file": "agents/doc_agent.md",
|
||||
"role": "Documentation, runbooks, operator guides",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _list_specs() -> list[dict[str, str]]:
|
||||
"""Scan specs/ directory for markdown files."""
|
||||
specs_dir = REPO_ROOT / "specs"
|
||||
if not specs_dir.is_dir():
|
||||
return []
|
||||
specs = []
|
||||
for p in sorted(specs_dir.glob("*.md")):
|
||||
specs.append({"name": p.stem, "file": f"specs/{p.name}"})
|
||||
return specs
|
||||
|
||||
|
||||
def _load_mcp_json() -> dict[str, Any]:
|
||||
"""Load mcp.json from repo root."""
|
||||
mcp_path = REPO_ROOT / "mcp.json"
|
||||
if not mcp_path.exists():
|
||||
return {}
|
||||
return json.loads(mcp_path.read_text())
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Endpoints
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
return {"status": "ok", "service": "kill-life", "version": __version__}
|
||||
|
||||
|
||||
@app.get("/agents")
|
||||
async def list_agents():
|
||||
return {"agents": BMAD_AGENTS}
|
||||
|
||||
|
||||
@app.get("/specs")
|
||||
async def list_specs():
|
||||
return {"specs": _list_specs(), "count": len(_list_specs())}
|
||||
|
||||
|
||||
class AgentRunRequest(BaseModel):
|
||||
input: str
|
||||
context: dict[str, Any] | None = None
|
||||
|
||||
|
||||
@app.post("/agents/{name}/run")
|
||||
async def run_agent(name: str, req: AgentRunRequest):
|
||||
if name not in BMAD_AGENTS:
|
||||
raise HTTPException(status_code=404, detail=f"Agent '{name}' not found. Available: {list(BMAD_AGENTS.keys())}")
|
||||
|
||||
agent = BMAD_AGENTS[name]
|
||||
|
||||
# Load agent definition
|
||||
agent_file = REPO_ROOT / agent["file"]
|
||||
definition = ""
|
||||
if agent_file.exists():
|
||||
definition = agent_file.read_text()
|
||||
|
||||
# Stub response — real execution would route through mascarade LLM
|
||||
return {
|
||||
"agent": name,
|
||||
"role": agent["role"],
|
||||
"input": req.input,
|
||||
"status": "stub",
|
||||
"message": f"Agent '{name}' received input. Connect mascarade-core for LLM execution.",
|
||||
"definition_loaded": bool(definition),
|
||||
"mascarade_endpoint": "http://mascarade-core:8100/v1/chat/completions",
|
||||
}
|
||||
|
||||
|
||||
@app.get("/mcp/servers")
|
||||
async def list_mcp_servers():
|
||||
mcp = _load_mcp_json()
|
||||
servers = mcp.get("mcpServers", {})
|
||||
return {
|
||||
"servers": {k: {"type": v.get("type", "local"), "tools": v.get("tools", [])} for k, v in servers.items()},
|
||||
"count": len(servers),
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Mascarade bridge info
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@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",
|
||||
"mcp_servers_count": len(_load_mcp_json().get("mcpServers", {})),
|
||||
"integration": {
|
||||
"specs_as_datasets": "mascarade finetune/ reads Kill_LIFE specs/",
|
||||
"mcp_tools": "mascarade mcp/client.py loads Kill_LIFE MCP servers",
|
||||
"agent_execution": "POST /agents/{name}/run routes through mascarade-core LLM",
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Kill_LIFE background worker — processes agent execution tasks.
|
||||
|
||||
Stub implementation. In production, this would:
|
||||
- Poll a task queue (Redis/mascarade P2P)
|
||||
- Execute BMAD agent pipelines
|
||||
- Report results back to the API
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import signal
|
||||
import sys
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [kill-life-worker] %(message)s")
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_running = True
|
||||
|
||||
|
||||
def _shutdown(signum, frame):
|
||||
global _running
|
||||
log.info("Received signal %s, shutting down...", signum)
|
||||
_running = False
|
||||
|
||||
|
||||
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)"))
|
||||
|
||||
while _running:
|
||||
# Stub: sleep and wait. Real implementation would poll task queue.
|
||||
await asyncio.sleep(5)
|
||||
|
||||
log.info("Worker stopped.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(0)
|
||||
@@ -64,6 +64,21 @@
|
||||
"Authorization": "Bearer ${HUGGINGFACE_API_KEY}"
|
||||
},
|
||||
"note": "Use https://huggingface.co/mcp?login for OAuth browser login instead of token auth"
|
||||
},
|
||||
"mascarade-bridge": {
|
||||
"type": "url",
|
||||
"url": "http://192.168.0.119:8100/mcp",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${MASCARADE_API_KEY}"
|
||||
},
|
||||
"tools": ["llm-chat", "llm-complete", "p2p-status", "agent-run"],
|
||||
"note": "Bridge to mascarade-core for LLM execution of Kill_LIFE agents"
|
||||
},
|
||||
"kill-life-api": {
|
||||
"type": "url",
|
||||
"url": "http://localhost:8200",
|
||||
"tools": ["agents", "specs", "mcp-servers"],
|
||||
"note": "Local Kill_LIFE API (docker-compose up)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
[project]
|
||||
name = "kill-life"
|
||||
version = "0.1.0"
|
||||
description = "Kill_LIFE control plane — AI-native embedded systems framework"
|
||||
requires-python = ">=3.12"
|
||||
license = "MIT"
|
||||
dependencies = [
|
||||
"fastapi>=0.115",
|
||||
"uvicorn[standard]>=0.34",
|
||||
"pydantic>=2.10",
|
||||
"httpx>=0.28",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=8.0",
|
||||
"ruff>=0.9",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py312"
|
||||
line-length = 120
|
||||
Reference in New Issue
Block a user