# Phase 2: Core VM Lifecycle - Context

**Gathered:** 2026-03-22
**Status:** Ready for planning

<domain>
## Phase Boundary

Implement the full cluster provisioning lifecycle — `cluster_define`, `cluster_start`, `cluster_stop`, `cluster_destroy`, `cluster_status`, and `cluster_handle` — as idempotent libvirt/QEMU operations. An agent should be able to go from a topology file to running, SSH-reachable nodes and back to a clean slate, with no human intervention.

Requirements: CLUS-01 through CLUS-07.

</domain>

<decisions>
## Implementation Decisions

### Network Lifecycle
- **D-01:** `cluster_define` creates the libvirt virtual network from the topology spec (bridge name, subnet, DHCP settings). `cluster_destroy` always removes that network. Zero manual network pre-provisioning required.
- **D-02:** The `remove_overlays` parameter on `cluster_destroy` only controls overlay disk file deletion — the network is always torn down regardless.

### Static IP Assignment
- **D-03:** Use cloud-init / NoCloud ISOs generated per node at `cluster_define` time to configure static IPs. Base images remain generic (no per-topology baking). Each node gets a small ISO with its network config attached as a cdrom device.

### Partial Failure Semantics
- **D-04:** `cluster_start` with `wait_for_ssh=True` is strict all-or-nothing: if any node fails SSH reachability within the timeout, the entire call returns `success=False` with per-node status in the result.
- **D-05:** `cluster_define` failure mid-way leaves partial state (any domains/overlays already created remain). The error result reports what was and wasn't created. Agent decides whether to retry or call `cluster_destroy` to clean up.

### Domain XML Generation
- **D-06:** Build libvirt domain XML programmatically using `xml.etree.ElementTree` (stdlib). No Jinja2 or string templates. Testable via tree structure assertions.

### Overlay Disk Creation
- **D-07:** Create overlay qcow2 disks via subprocess call to `qemu-img create -b <base> -F qcow2 -f qcow2 <overlay>`. Direct, no libvirt storage pool API complexity.

### Carried Forward (from Phase 1 / STATE.md decisions)
- **D-08:** Single `ThreadPoolExecutor(max_workers=1)` for all libvirt calls in `libvirt_client.py` — libvirt C library is not thread-safe.
- **D-09:** `@structured_tool_handler` wraps every tool handler from creation — no undecorated tools.
- **D-10:** Cluster-namespaced overlay paths: `{overlay_dir}/{cluster_name}/{node_name}.qcow2` with assertion that base_image != overlay path.
- **D-11:** asyncssh over paramiko for SSH execution (relevant for `wait_for_ssh` in `cluster_start`).

### Agent's Discretion
- Domain XML structure details (specific elements, attributes) — researcher/planner determine based on libvirt requirements.
- cloud-init ISO generation specifics (genisoimage vs mkisofs, meta-data format) — researcher determines.
- Idempotency check mechanism for `cluster_define` (CLUS-07) — planner determines approach.

</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### Design Specification
- `docs/vmcluster-mcp-design.md` §2.1–2.4 — Tool signatures, type contracts, and design goals for all cluster lifecycle operations

### Topology Schema
- `vmcluster_mcp/topology/schema.py` — `TopologySpec`, `NodeSpec`, `NetworkSpec`, `SshSpec` Pydantic models that define cluster shape

### Data Models
- `vmcluster_mcp/models.py` — `ClusterHandle`, `NodeDescriptor`, `NodeStatus`, `ClusterStatus`, `ToolResult`, `ErrorDetail` already defined

### Server Infrastructure
- `vmcluster_mcp/server.py` — `FastMCP` instance, `structured_tool_handler` decorator, `config` global

### Configuration
- `vmcluster_mcp/config.py` — `Config` model with `libvirt_uri`, `overlay_dir`, `ssh_key_path`, `ssh_user`

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- `TopologySpec` / `NodeSpec` / `NetworkSpec` / `SshSpec`: fully defined in `topology/schema.py` — drive all domain XML and network creation
- `ClusterHandle`, `ClusterStatus`, `NodeStatus`, `NodeDescriptor`: fully defined in `models.py` — return types are ready
- `ToolResult[T]` + `ErrorDetail`: error contract is established
- `structured_tool_handler`: decorator ready for all new tool registrations
- `load_topology()` in `topology/parser.py`: loads and validates YAML into `TopologySpec`
- `load_config()` in `config.py`: provides `libvirt_uri`, `overlay_dir`, `ssh_key_path`

### Established Patterns
- **Pydantic v2 models** for all data contracts (BaseModel, Generic[T])
- **`from __future__ import annotations`** in every module
- **Logging** via `logging.getLogger("vmcluster_mcp.{module}")`
- **No internal cross-imports** between subpackages — models.py and config.py are pure leaf nodes

### Integration Points
- `cluster/__init__.py` is empty — new code lives here plus `cluster/libvirt_client.py`, `cluster/domain_builder.py`
- Tools register on the `mcp` FastMCP instance from `server.py`
- `config` global from `server.py` provides libvirt URI and paths

</code_context>

<specifics>
## Specific Ideas

No specific requirements — open to standard approaches based on libvirt best practices.

</specifics>

<deferred>
## Deferred Ideas

None — discussion stayed within phase scope.

</deferred>

---

*Phase: 02-core-vm-lifecycle*
*Context gathered: 2026-03-22*
