agentctl
by moreWax
README.md
# agentctl
K8s-native agent/model workload operator with SAM mesh registration and MCP ops plane.
Replaces process-spawning orchestrators (vllm-orchestrator) with declarative K8s resources.
Every operation is exposed as an MCP tool for LangGraph/LangChain workflow composition.
## Architecture
```
LangGraph agent ──MCP──▶ agentctl ops plane (:8091)
│
┌─────────┼─────────┐
▼ ▼ ▼
workload_* fleet_* sam_*
tools tools tools
│ │ │
▼ ▼ ▼
K8s API K8s API SAM node
(CRDs) (pods) (discovery)
```
## Install
```bash
pip install agentctl
# or
uv pip install agentctl
```
## Install (cluster)
```bash
kubectl apply -f https://github.com/moreWax/agentctl/releases/latest/download/install.yaml
```
This installs the CRD, the operator, and the MCP ops plane into
`agentctl-system`. The ops plane is reachable in-cluster at
`http://agentctl-mcp.agentctl-system.svc.cluster.local:8091/mcp`
(stateless Streamable HTTP).
## Usage
### Deploy the example workloads
```bash
kubectl apply -f examples/qwen3.8-27b.yaml # Qwen3.8-27B, TP2 on 2 GPUs
kubectl apply -f examples/qwen3.8-27b-fp8.yaml # FP8 variant, single GPU
kubectl apply -f examples/litellm-gateway.yaml # LiteLLM proxy gateway
```
### From source (development)
```bash
uv venv && uv pip install -e ".[dev]"
kubectl apply -f crd/agentctl-crd.yaml
agentctl serve # operator (kopf controllers)
agentctl mcp --port 8091 # MCP ops-plane server (stateless Streamable HTTP)
```
### Deploy a model workload
```bash
kubectl apply -f examples/qwen3.8-27b.yaml # Qwen3.8-27B, TP2 on 2 GPUs
kubectl apply -f examples/qwen3.8-27b-fp8.yaml # FP8 variant, single GPU
kubectl apply -f examples/litellm-gateway.yaml # LiteLLM proxy gateway
```
The LiteLLM gateway routes OpenAI-compatible requests to the vLLM backends.
Point your agents at `http://litellm-gateway:4000/v1` and use model names like
`qwen3.8-27b` or `qwen3.8-27b-fp8`.
### MCP tools (for LangGraph / agent composition)
The ops plane exposes **3 action-routed tools** — a small surface that keeps
agent context cheap and tool selection trivial, while all 20 capabilities
stay available as actions. Everything is stateless, returns structured JSON,
and gates destructive actions behind `approved=true`.
**`fleet(action=...)`** — cluster-wide views (read-only)
| Action | Params | Description |
|---|---|---|
| `status` | — | Aggregate: workloads by phase, GPU totals |
| `gpus` | — | Per-node capacity / allocated / free + consumers |
| `pods` | `namespace`, `limit` | Per-pod: node, boot phase, role, sleeping, GPUs, restarts |
| `footprint` | `gpu_count` | Dry-run placement — call before boot |
| `health` | `namespace` | Endpoint readiness per service |
| `sam_services` | `type` | SAM mesh discovery |
**`workload(action=...)`** — AgentWorkload CRUD
| Action | Params | Description |
|---|---|---|
| `list` | `namespace` (`"all"`) | Workloads with phase, GPUs, pin state |
| `status` | `name` | Full detail incl. per-pod boot phases |
| `logs` | `name`, `tail` | Recent container logs per pod |
| `scale` | `name`, `replicas`, `force` | 0 parks; refuses pinned scale-down unless forced |
| `delete` | `name`, `approved=true` | Delete workload + pods |
| `deploy` | `model_id`, `image`, `gpu_count`, `args`, `sam_enabled` | Create (idempotent) |
| `undeploy` | `model_id` | Remove |
**`model(action=...)`** — model lifecycle & actuation
| Action | Params | Description |
|---|---|---|
| `boot` | `model_id`, `image`, `gpu_count`, `args`, `wait_seconds` | Deploy + optional wait → `outcome: ready/failed/timeout` |
| `boot_status` | `model_id`, `tail_logs` | Pod phases + vLLM markers (`LoadingWeights`→`SizingKVCache`→`CapturingGraphs`→`Serving`) + % |
| `sleep` | `model_id` | Suspend, free GPUs. FMA: seconds-level (`fma-suspend`); native: full stop |
| `wake` | `model_id`, `wait_seconds` | Resume; FMA wake takes seconds |
| `kill_pod` | `pod_name`, `approved=true` | Force-delete hung pod; Deployment recreates it |
| `pin` / `unpin` | `model_id` | Pin blocks scale-down/sleep unless forced |
**Agent boot-wait pattern (LangGraph loop):**
```python
model(action="boot", model_id="qwen38-27b", image="vllm/vllm-openai:v0.26.0", gpu_count=2)
while True:
s = model(action="boot_status", model_id="qwen38-27b")
if s["ready"]: break # proceed to inference
if s["failed"]: ... # inspect s["pods"], kill_pod + retry
time.sleep(10) # poll again
```
Errors are structured: `{"error": "unknown_action", "valid_actions": [...]}`,
`{"error": "missing_params", "required": [...]}`, or `{"error": <cause>, "detail": ...}`.
## Development
```bash
uv venv && uv pip install -e ".[dev]"
pytest tests/ -q
```
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues