knot.pool
The knot.pool library manages space pools. A pool keeps a desired count of identical spaces (created from the same template) running and ready, so the server can hand out method, HTTP, and TCP traffic across healthy members. Pools are useful for scaling stateless services and for method backends that need more capacity than a single space. Lease-enabled pools additionally support exclusive member checkout — acquire(), extend(), release(), leases() and the leased() context manager — for callers that need a member to themselves.
Execution Environment
| Environment | Behaviour |
|---|---|
Embedded (MCP tool execution, event sinks, remote/space scripts, knot run-script) |
Available; authenticated automatically via the Go-provided knot.apiclient transport. |
| Health check scripts | Not available. |
| External (standalone scripts) | Python implementation; configure knot.apiclient first (or set the KNOT_* environment variables). |
Functions
| Function | Description |
|---|---|
list() |
List visible pools with current utilization |
get(name) |
Get pool details and utilization by name or ID |
create(name, template_name, startup_script_id='', desired_count=1, active=True) |
Create a pool and return its ID. Bridged KVM templates are rejected — their spaces need an IP address chosen at creation, which a pool can’t provide; NAT KVM templates work |
update(name, desired_count=None, active=None) |
Update the pool’s desired count or active state |
delete(name) |
Delete a stopped pool and all its spaces |
set_size(name, desired_count) |
Set the pool’s desired space count |
start(name) |
Start a stopped pool (starts all members, creates any missing) |
stop(name) |
Stop a running pool (stops all members without deleting them) |
acquire(name, time=None, wait=None) |
Acquire a member exclusively until the lease ends (lease-enabled pools). time: None = the pool’s maximum, "none" = never expire (no-timeout pools only), seconds or a "5m"-style string. wait: optionally wait this long for a free member before raising |
extend(space, time=None) |
Renew the lease held on a member (space name or id) — the new deadline is now + time (or never, on no-timeout pools). Bounded by the pool’s extension count |
release(space, destroy=False) |
Release the lease held on a member (space name or id — what acquire returned); the member returns to the pool after in-flight work drains (~15s). With destroy=True the member is deleted and a fresh replacement is created, so the next acquire gets a clean space |
leases(name) |
List the pool’s held leases — active plus draining |
leased(name, time=None, wait=None, destroy=False) |
Context manager: acquire on entry, release (or destroy) on exit |
Usage
import knot.pool as pool
# List pools
for p in pool.list():
print(f"{p['name']}: {p['alive_members']}/{p['desired_count']} alive")
# Create a pool of 3 spaces from a template
pool_id = pool.create(
"api-pool",
"my-service",
desired_count=3,
active=True,
)
# Scale the pool up
pool.set_size("api-pool", 5)
# Stop and later start the pool
pool.stop("api-pool")
pool.start("api-pool")
# Delete the pool (must be stopped first)
pool.delete("api-pool")Exclusive member leases (requires a lease-enabled pool — lease_max_time set
at creation or via the update API):
import knot.apiclient
import knot.pool as pool
# Check a member out for 5 minutes; the held instance is
# member["space_name"] / member["space_id"]
member = pool.acquire("build-workers", time="5m", wait="30s")
# Pin method calls to it while held
knot.apiclient.post("/api/methods/call", {
"jsonrpc": "2.0", "id": 1,
"method": "run_build",
"params": {},
"space_id": member["space_id"],
})
pool.extend(member["space_name"], time="5m")
pool.release(member["space_name"])
# Or let the context manager release on exit (including on exception)
with pool.leased("build-workers", "5m") as member:
...Pool Properties
get() and list() return pool dicts containing:
id- Pool IDname- Pool nametemplate_id- Template the pool’s spaces are created fromstartup_script_id- Startup script applied to membersdesired_count- Target number of spacesalive_members- Number of currently healthy membersactive- Whether the pool is active (members are started as they are created)lease_max_time- Lease time budget in seconds:0= leases disabled,-1= no timeoutlease_max_extensions- Max extensions per lease:0= extending forbidden,-1= unlimitedutilization- Aggregate utilization across members:combined_rps- Total requests per second (method + HTTP + TCP)method_rps- Method requests per secondhttp_rps- HTTP requests per secondtcp_rps- TCP requests per secondmethod_inflight- In-flight method requestsavg_cpu_percent- Average CPU usage across membersavg_memory_percent- Average memory usage across members
members- List of member space dicts (see below)
Member Properties
Each member in members contains:
id- Space IDname- Space namestate- Member statecombined_rps,method_rps,http_rps,tcp_rps- Per-member request ratesmethod_inflight- In-flight method requestscpu_percent- CPU usagememory_percent- Memory usagehealthy- Whether the member is healthyis_pending- Whether the member is pending creationis_deleting- Whether the member is being deletedis_deployed- Whether the member is deployed (running)lease_state- Lease state:""free,"active"exclusively leased,"draining"lease ended and waiting for in-flight work to finishlease_holder- Username of the lease holder (when leased)lease_expires_at- Lease expiry (None= never expires)
Lease Properties
acquire(), extend() and release() return lease dicts, and leases()
returns a list of them:
pool_name- The pool the lease was granted fromspace_id,space_name- The held member; extend/release take these, and method calls can be pinned withspace_idusername- The lease holderexpires_at- When the lease ends (None= never expires)extensions_used,max_extensions- Extension counter and cap (-1= unlimited)state-"active"or"draining"(ended, waiting for in-flight work)
Lifecycle Notes
create()accepts a template name (resolved to an ID internally). The template, startup script, and pool name are immutable after creation; onlydesired_countandactiveare mutable viaupdate().delete()requires the pool to be stopped first.set_size(),start(), andstop()are asynchronous: the server’s sweep loop creates, drains, or deletes member spaces to reach the desired state.- Lease functions require the pool to be lease-enabled (
lease_max_time != 0) and active. The simplest mode is a no-timeout pool (lease_max_time = -1): acquire, use, release —timecan stayNoneeverywhere. While a lease is held, shared method routing and pool-name port routing skip the member; on expiry or release the member returns to the pool after in-flight method work drains, normally within one 15-second sweep.acquire()raises when no member is free (afterwait, if given);extend()raises once the pool’s extension count is exhausted.