Record the MCP server deployment decisions in the spec #36
No files matched your search
@@ -1,157 +0,0 @@
|
|||||||
# Amendment — Agent Hosts, Egress, and MCP Server Deployment
|
|
||||||
|
|
||||||
An amendment to `Architecture-Build-Spec.md`, recording decisions from a design session
|
|
||||||
on MCP server deployment. It widens two decisions that were recorded as Dev Agent
|
|
||||||
specific into rules for every agent, settles where MCP servers run and how they are
|
|
||||||
split, and adjusts the secrets, permission, and image-delivery entries that those
|
|
||||||
decisions touch.
|
|
||||||
|
|
||||||
Each numbered section below names the spec locations it changes. After review, this
|
|
||||||
amendment is folded into the spec and removed, as the Tiered Inference amendment was.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Every agent gets its own VM
|
|
||||||
|
|
||||||
**Changes:** new Part F1; B3 first bullet; C5.
|
|
||||||
|
|
||||||
The spec records the dedicated VM as a Dev Agent decision, justified by
|
|
||||||
Docker-in-Docker. That justification is specific to an agent that builds and runs
|
|
||||||
containers. A future agent that does not build code would read it and correctly
|
|
||||||
conclude it may run as a container. So the decision is promoted to a standing rule
|
|
||||||
with its own rationale:
|
|
||||||
|
|
||||||
- **Every agent gets its own VM.** No agent runs as a container on a shared host, and
|
|
||||||
no two agents share a VM.
|
|
||||||
- **General rationale:**
|
|
||||||
- *Isolation boundary between agents.* The VM is the trust boundary (Part H,
|
|
||||||
monorepo decision). A compromised or misbehaving agent must not reach another
|
|
||||||
agent's state, credentials, or processes.
|
|
||||||
- *Per-agent credential attribution.* Everything an agent holds lives on its own
|
|
||||||
host and is issued to its own identity, so every upstream action traces back to
|
|
||||||
exactly one agent.
|
|
||||||
- **Additional reason for the Dev Agent:** it builds and runs Docker containers, so it
|
|
||||||
needs its own real Docker daemon. Docker-in-a-container is either a socket mount,
|
|
||||||
which is close to host root, or dind, which brings nested-storage problems. This
|
|
||||||
reason is additive: it is not why the rule exists.
|
|
||||||
|
|
||||||
## 2. Egress policy is per agent instance
|
|
||||||
|
|
||||||
**Changes:** new Part F2; B3 "Sealed to start" bullet (becomes `nisse-dev`'s instance
|
|
||||||
of the rule); C5.
|
|
||||||
|
|
||||||
The Dev-Agent-scoped egress entry becomes a general rule:
|
|
||||||
|
|
||||||
- **Every agent instance has its own default-deny allowlist.** Requirements differ by
|
|
||||||
agent type. No agent inherits another agent's policy.
|
|
||||||
- **The allowlist is a declared artifact,** versioned alongside the agent's
|
|
||||||
definition. Each entry is justified by the component that needs it.
|
|
||||||
- **The declaration point is fixed. The enforcement point is not.** Policy may be
|
|
||||||
enforced at the VM, at the Docker network, at the UniFi firewall, or at several of
|
|
||||||
these at once. Layering is deliberate: enforcement at the network layer survives
|
|
||||||
compromise of the agent's own host.
|
|
||||||
- **The allowlist is the union** of what the agent reaches directly and what its MCP
|
|
||||||
servers (§3) reach on its behalf. `nisse-ops` must permit Proxmox and UniFi
|
|
||||||
endpoints at both sites, even though the agent never opens those connections
|
|
||||||
itself.
|
|
||||||
- **The tailnet is `100.64.0.0/10`** (CGNAT shared address space), not RFC1918. A
|
|
||||||
rule written around RFC1918 denial does not cover it. Deny it explicitly.
|
|
||||||
|
|
||||||
## 3. MCP servers: one instance per agent VM
|
|
||||||
|
|
||||||
**Changes:** new Part F3.
|
|
||||||
|
|
||||||
- **MCP servers run as containers on the owning agent's Docker host.** Images are
|
|
||||||
shared through the OCI registry. Processes are not shared between agents.
|
|
||||||
- **Rationale:**
|
|
||||||
- *A shared process means a shared credential.* The upstream API's own audit log
|
|
||||||
then cannot attribute a call to a specific agent. Separate instances with
|
|
||||||
separate tokens give attribution at the API layer, instead of depending on our
|
|
||||||
own transcripts. This matters most for a read-only auditing agent, such as a
|
|
||||||
future `nisse-security`.
|
|
||||||
- *An agent should not host the tooling used to inspect it.*
|
|
||||||
- *Duplication is nearly free.* These servers wrap remote HTTP APIs and hold no
|
|
||||||
state.
|
|
||||||
|
|
||||||
## 4. Per-target, per-privilege server instances
|
|
||||||
|
|
||||||
**Changes:** Part F3; Part 0 (site identifiers); A7 (machine accounts); C2 (writes
|
|
||||||
require an approved plan); C3.
|
|
||||||
|
|
||||||
There is **one MCP server instance per (service, site, privilege level).**
|
|
||||||
|
|
||||||
- **Site identifier is the domain stem:** `garrett-hall` (Arlington) and
|
|
||||||
`hartman-hut` (Cross Plains). The same string appears in the container name, the
|
|
||||||
Vaultwarden machine account, and the MCP tool prefix. Site belongs in the server's
|
|
||||||
identity, not in a `site` tool argument. A wrong-house action should require a tool
|
|
||||||
that isn't mounted, not just a wrong parameter value.
|
|
||||||
- **Read-only and elevated access are separate instances** with separate upstream
|
|
||||||
credentials. The read-only instance is mounted by default. The elevated instance
|
|
||||||
is mounted only after plan approval. This puts the capability gate at the
|
|
||||||
credential layer, instead of a flag inside a third-party server.
|
|
||||||
- **Per-task tool profiles** mount only the servers that a given task's scope
|
|
||||||
requires.
|
|
||||||
|
|
||||||
Consequences for existing sections (settled with the operator while drafting this
|
|
||||||
amendment):
|
|
||||||
|
|
||||||
- **A7: a machine account per MCP server instance.** Each (service, site,
|
|
||||||
privilege) instance gets its own dedicated Vaultwarden machine account and
|
|
||||||
bootstrap token, alongside the agent's own. "One machine account per agent"
|
|
||||||
becomes "one per agent, plus one per MCP server instance." Each bootstrap token is
|
|
||||||
available only to its own container.
|
|
||||||
- **C2: writes require an approved plan.** Every write credential sits behind the
|
|
||||||
elevated instance, so every write, including a reversible service action such as a
|
|
||||||
container restart, first needs an approved plan that mounts it. The C2 tiers then
|
|
||||||
govern execution *within* that plan: reversible actions run without further
|
|
||||||
confirmation, and high-consequence actions stay confirm-first. Read-only telemetry
|
|
||||||
stays free, through the read-only instance.
|
|
||||||
|
|
||||||
## 5. Watchtower carve-out
|
|
||||||
|
|
||||||
**Changes:** Part F4 (the existing Part F image-delivery entries).
|
|
||||||
|
|
||||||
The existing pipeline (Gitea Actions → OCI registry → Watchtower, with restart gated
|
|
||||||
on the idle flag) gets an exception. **Third-party MCP server images hold
|
|
||||||
root-equivalent credentials to Proxmox and UniFi.** They are digest-pinned, excluded
|
|
||||||
from Watchtower, and updated deliberately after review.
|
|
||||||
|
|
||||||
## 6. Reframe the network-gear browser item
|
|
||||||
|
|
||||||
**Changes:** Part H; C3.
|
|
||||||
|
|
||||||
The open item "Sysadmin browser access to network gear: standing vs. per-task
|
|
||||||
hand-off" is narrowed. With UniFi reachable over MCP, the browser becomes a fallback
|
|
||||||
for API gaps, not the primary path. The open question is now whether the *fallback*
|
|
||||||
browser access, used only for operations the UniFi API does not expose, is standing
|
|
||||||
or handed off per task. The lockout risk still argues for per-task.
|
|
||||||
|
|
||||||
## 7. Rejected alternative: routed meta-tools / lazy tool registration
|
|
||||||
|
|
||||||
**Changes:** Part F3.
|
|
||||||
|
|
||||||
Several community Proxmox and UniFi MCP servers use search-then-dispatch over a large
|
|
||||||
generated tool catalog. This is **rejected in favor of per-task tool profiles**:
|
|
||||||
|
|
||||||
- *Two extra round-trips per call,* which is worse with a local model driving the
|
|
||||||
control loop.
|
|
||||||
- *The model can no longer discover capabilities it wasn't already looking for.*
|
|
||||||
That is backwards for exploratory diagnostic work.
|
|
||||||
- *Arguments arrive as an opaque blob,* instead of being constrained by per-tool JSON
|
|
||||||
Schema.
|
|
||||||
- *A dynamic tool list defeats prompt prefix caching,* and caching is much of what
|
|
||||||
makes a large static list affordable.
|
|
||||||
|
|
||||||
Acceptable only when consuming a third-party server whose surface we can't trim.
|
|
||||||
|
|
||||||
## 8. Parked: relocating stateful MCP servers
|
|
||||||
|
|
||||||
**Changes:** Part H.
|
|
||||||
|
|
||||||
Relocating stateful MCP servers off the owning agent's VM. The trigger is
|
|
||||||
**statefulness** (a browser pool, a vector index, a warm cache), not sharing:
|
|
||||||
duplicating a stateful server duplicates its state. Relocating has a cost. A shared
|
|
||||||
server can't hold per-agent tokens, so per-agent identity has to move into the
|
|
||||||
request, through caller auth plus per-caller upstream credentials. That loses the
|
|
||||||
attribution property that makes instance-per-VM clean. Instance-per-agent-VM is the
|
|
||||||
starting position. Migrate one server at a time, when a specific server justifies it.
|
|
||||||
+145
-19
@@ -10,7 +10,10 @@ Gitea, Vikunja, Outline, LiteLLM) and heaviest on the **agent library** and the
|
|||||||
document and no longer exists separately: its A-series components appear as A10–A16,
|
document and no longer exists separately: its A-series components appear as A10–A16,
|
||||||
its network and credential revisions in A5 and B3, and its revised build order as
|
its network and credential revisions in A5 and B3, and its revised build order as
|
||||||
Part G. The inference-hardware question it raised has since been resolved in
|
Part G. The inference-hardware question it raised has since been resolved in
|
||||||
principle (Part H: a 4090-class host).
|
principle (Part H: a 4090-class host). The MCP Deployment amendment has likewise been
|
||||||
|
folded in: the every-agent-gets-a-VM rule, per-instance egress policy, and MCP server
|
||||||
|
placement now form Part F1–F3; its consequences appear in Part 0 (site identifiers),
|
||||||
|
A7, B3, C2, C3, C5, the Part F4 Watchtower carve-out, and Part H.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -32,6 +35,11 @@ same string across all of those: the VM, the container, the Caddy host, the Matr
|
|||||||
account, and the Bitwarden machine account for a given agent all share one name, so
|
account, and the Bitwarden machine account for a given agent all share one name, so
|
||||||
that grepping the infrastructure for `nisse-ops` finds everything.
|
that grepping the infrastructure for `nisse-ops` finds everything.
|
||||||
|
|
||||||
|
**Sites** are identified by their domain stem: `garrett-hall` (Arlington) and
|
||||||
|
dongar marked this conversation as resolved
Outdated
|
|||||||
|
`hartman-hut` (Cross Plains). As with agent names, one string is used everywhere a site
|
||||||
|
appears. For an MCP server instance (F3), that means its container name, its
|
||||||
|
Vaultwarden machine account, and its MCP tool prefix.
|
||||||
|
|
||||||
The three artifacts live as three packages in **one repository, `nisse`** (decision
|
The three artifacts live as three packages in **one repository, `nisse`** (decision
|
||||||
recorded in Part H). Directory and package names carry the identity. If mirrored to
|
recorded in Part H). Directory and package names carry the identity. If mirrored to
|
||||||
GitHub, it goes under a personal account rather than an org — `github.com/nisse` is
|
GitHub, it goes under a personal account rather than an org — `github.com/nisse` is
|
||||||
@@ -261,9 +269,13 @@ Requirements:
|
|||||||
Requirements:
|
Requirements:
|
||||||
- Client for **Bitwarden/Vaultwarden** via **machine account / Secrets Manager** (or
|
- Client for **Bitwarden/Vaultwarden** via **machine account / Secrets Manager** (or
|
||||||
`bw` CLI + API key). Not the personal vault.
|
`bw` CLI + API key). Not the personal vault.
|
||||||
- Reads only from a **dedicated, minimally-scoped machine account** per agent.
|
- Reads only from a **dedicated, minimally-scoped machine account** per agent, **plus
|
||||||
- A single **bootstrap token** (the machine-account credential) is the one secret
|
one per MCP server instance** (F3). Each (service, site, privilege) instance holds
|
||||||
that lives on the box: tight file perms, never in image or repo.
|
its own upstream credential, so it gets its own machine account, and the agent's
|
||||||
|
account cannot read an instance's credential.
|
||||||
|
- Each machine account has a single **bootstrap token**. These are the only secrets
|
||||||
|
that live on the box: tight file perms, never in image or repo. Each token is
|
||||||
|
available only to its own container.
|
||||||
- (Deferred, not first cut: dynamic/short-lived secrets via Vault/OpenBao. Leave the
|
- (Deferred, not first cut: dynamic/short-lived secrets via Vault/OpenBao. Leave the
|
||||||
interface able to grow toward this; don't build it yet.)
|
interface able to grow toward this; don't build it yet.)
|
||||||
|
|
||||||
@@ -746,13 +758,16 @@ PR_OPEN
|
|||||||
|
|
||||||
### B3. Deployment & Isolation
|
### B3. Deployment & Isolation
|
||||||
|
|
||||||
- **Dedicated VM** (Proxmox), not a bare container — the agent builds and runs Docker
|
- **Dedicated VM** (Proxmox), as for every agent (F1). The Dev Agent has an
|
||||||
containers, so it needs its own real Docker daemon. (Docker-in-a-container via
|
additional reason: it builds and runs Docker containers, so it needs its own real
|
||||||
socket-mount ≈ host root; via dind = nested-storage pain. The VM boundary is the
|
Docker daemon. (Docker-in-a-container via socket-mount ≈ host root; via dind =
|
||||||
correct trust boundary for unattended arbitrary command execution.)
|
nested-storage pain. The VM boundary is the correct trust boundary for unattended
|
||||||
- **Sealed to start:** egress allow-list (LiteLLM, Gitea, package registries), **no
|
arbitrary command execution.)
|
||||||
ingress**. Egress denial must include **RFC1918 except the specific LiteLLM/Gitea
|
- **Sealed to start:** `nisse-dev`'s egress allowlist (F2) permits LiteLLM, Gitea,
|
||||||
IPs** — sealed from the internet must also mean no reach to NAS/Proxmox/UDM.
|
and package registries, and there is **no ingress**. Egress denial must include
|
||||||
|
**RFC1918 except the specific LiteLLM/Gitea IPs**, and also the tailnet's
|
||||||
|
**`100.64.0.0/10`**, which is not RFC1918. Sealed from the internet must also mean
|
||||||
|
no reach to NAS/Proxmox/UDM.
|
||||||
- **The LAN-resident inference host is not a second exception.** It sits behind
|
- **The LAN-resident inference host is not a second exception.** It sits behind
|
||||||
LiteLLM (A5), so the allowlist keeps exactly one LLM destination regardless of how
|
LiteLLM (A5), so the allowlist keeps exactly one LLM destination regardless of how
|
||||||
many models or vendors are in play.
|
many models or vendors are in play.
|
||||||
@@ -789,9 +804,16 @@ deployments over the same library.
|
|||||||
|
|
||||||
Tiered policy, evaluated on every action:
|
Tiered policy, evaluated on every action:
|
||||||
- **Read-only telemetry** (metrics, logs, status) — free.
|
- **Read-only telemetry** (metrics, logs, status) — free.
|
||||||
- **Reversible service actions** (restart a container) — allowed.
|
- **Reversible service actions** (restart a container) — allowed within an approved
|
||||||
|
plan, without further confirmation.
|
||||||
- **High-consequence actions** (storage, networking, auth) — **confirm-first** over
|
- **High-consequence actions** (storage, networking, auth) — **confirm-first** over
|
||||||
transport before executing.
|
transport before executing, even within an approved plan.
|
||||||
|
|
||||||
|
**Every write requires an approved plan first.** Write credentials exist only in the
|
||||||
|
elevated MCP server instances (F3), and those are mounted only after plan approval. A
|
||||||
|
reversible restart therefore needs an approved plan too. The tiers above govern
|
||||||
|
execution *within* that plan. Read-only telemetry goes through the read-only
|
||||||
|
instances, which are mounted by default, so it needs no plan.
|
||||||
|
|
||||||
This is the sysadmin analog of the dev agent's plan gate: the human boundary sits at
|
This is the sysadmin analog of the dev agent's plan gate: the human boundary sits at
|
||||||
irreversible / high-blast-radius actions.
|
irreversible / high-blast-radius actions.
|
||||||
@@ -805,9 +827,13 @@ Special cases fold into the tiers:
|
|||||||
|
|
||||||
### C3. Interfaces
|
### C3. Interfaces
|
||||||
|
|
||||||
|
- **MCP servers (F3):** Proxmox and UniFi, at both sites, through per-site,
|
||||||
|
per-privilege server instances on the agent's own Docker host. This is the primary
|
||||||
|
path to that infrastructure.
|
||||||
- **Browser (A6):** into admin UIs. **Prefer real APIs where they exist** (Proxmox,
|
- **Browser (A6):** into admin UIs. **Prefer real APIs where they exist** (Proxmox,
|
||||||
TrueNAS good; UniFi partial); use the browser only for genuinely UI-only gaps —
|
TrueNAS good; UniFi now reachable over MCP); use the browser only for genuinely
|
||||||
it's the least stable interface.
|
UI-only gaps. It's the least stable interface. For network gear, it is a fallback
|
||||||
|
for API gaps, not the primary path.
|
||||||
- **SSO (A8):** logs into admin UIs as `svc-nisse-ops`. This account is a high-value
|
- **SSO (A8):** logs into admin UIs as `svc-nisse-ops`. This account is a high-value
|
||||||
credential; its blast radius equals its Authelia scope, so scope it minimally.
|
credential; its blast radius equals its Authelia scope, so scope it minimally.
|
||||||
- **Secrets (A7):** UI/SSO logins and service API tokens from Bitwarden.
|
- **Secrets (A7):** UI/SSO logins and service API tokens from Bitwarden.
|
||||||
@@ -835,8 +861,11 @@ The sysadmin analog of "dev agent driven by Gitea."
|
|||||||
|
|
||||||
### C5. Deployment & Isolation
|
### C5. Deployment & Isolation
|
||||||
|
|
||||||
- Longer-lived, **stateful VM** (not disposable).
|
- Longer-lived, **stateful VM** (not disposable), dedicated to this agent (F1).
|
||||||
- Has **Tailscale** + scoped credentials to reach infrastructure across both sites.
|
- Has **Tailscale** + scoped credentials to reach infrastructure across both sites.
|
||||||
|
- **Its MCP servers (F3) run on its own Docker host.** Its egress allowlist (F2)
|
||||||
|
therefore includes the Proxmox and UniFi endpoints at both sites, even though the
|
||||||
|
agent process never opens those connections itself.
|
||||||
- Updated **in place** via the container pipeline, **restart gated on an idle flag**.
|
- Updated **in place** via the container pipeline, **restart gated on an idle flag**.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -899,7 +928,89 @@ each agent is built — not standing up the service itself.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## PART F — DEPLOYMENT MODEL (both agents)
|
## PART F — DEPLOYMENT MODEL (all agents)
|
||||||
|
|
||||||
|
These rules apply to every agent, current and future. Parts B3 and C5 record how each
|
||||||
|
existing agent instantiates them.
|
||||||
|
|
||||||
|
### F1. Agent hosts: every agent gets its own VM
|
||||||
|
|
||||||
|
- **Every agent gets its own VM** (Proxmox). No agent runs as a container on a shared
|
||||||
|
host, and no two agents share a VM.
|
||||||
|
- **Why, in general:**
|
||||||
|
- *Isolation boundary between agents.* The VM is the trust boundary (Part H). A
|
||||||
|
compromised or misbehaving agent must not reach another agent's state,
|
||||||
|
credentials, or processes.
|
||||||
|
- *Per-agent credential attribution.* Everything an agent holds lives on its own
|
||||||
|
host and is issued to its own identity, so every upstream action traces back to
|
||||||
|
exactly one agent.
|
||||||
|
- **Agent-specific reasons add to this; they are not its foundation.** The Dev Agent
|
||||||
|
also needs a real Docker daemon, because it builds and runs containers (B3). An
|
||||||
|
agent that builds no code still gets a VM, for the two reasons above.
|
||||||
|
|
||||||
|
### F2. Egress policy: per agent instance
|
||||||
|
|
||||||
|
- **Every agent instance has its own default-deny allowlist.** Requirements differ by
|
||||||
|
agent type. No agent inherits another agent's policy.
|
||||||
|
- **The allowlist is a declared artifact,** versioned alongside the agent's
|
||||||
|
definition. Each entry is justified by the component that needs it.
|
||||||
|
- **The declaration point is fixed. The enforcement point is not.** Policy may be
|
||||||
|
enforced at the VM, at the Docker network, at the UniFi firewall, or at several of
|
||||||
|
these at once. Layering is deliberate: enforcement at the network layer survives
|
||||||
|
compromise of the agent's own host.
|
||||||
|
- **The allowlist is the union** of what the agent reaches directly and what its MCP
|
||||||
|
servers (F3) reach on its behalf. `nisse-ops` must permit Proxmox and UniFi
|
||||||
|
endpoints at both sites, even though the agent never opens those connections
|
||||||
|
itself.
|
||||||
|
- **The tailnet is `100.64.0.0/10`** (CGNAT shared address space), not RFC1918. A
|
||||||
|
rule written around RFC1918 denial does not cover it. Deny it explicitly.
|
||||||
|
|
||||||
|
### F3. MCP servers
|
||||||
|
|
||||||
|
**One instance per agent VM.** MCP servers run as containers on the owning agent's
|
||||||
|
Docker host. Images are shared through the OCI registry. Processes are not shared
|
||||||
|
between agents.
|
||||||
|
|
||||||
|
- *A shared process means a shared credential.* The upstream API's own audit log
|
||||||
|
then cannot attribute a call to a specific agent. Separate instances with separate
|
||||||
|
tokens give attribution at the API layer, instead of depending on our own
|
||||||
|
transcripts. This matters most for a read-only auditing agent, such as a future
|
||||||
|
`nisse-security`.
|
||||||
|
- *An agent should not host the tooling used to inspect it.*
|
||||||
|
- *Duplication is nearly free.* These servers wrap remote HTTP APIs and hold no
|
||||||
|
state. (Stateful servers are the exception that could justify relocation. That
|
||||||
|
item is parked in Part H.)
|
||||||
|
|
||||||
|
**One instance per (service, site, privilege level).**
|
||||||
|
|
||||||
|
- **Site belongs in the server's identity,** using the site identifier (Part 0), not
|
||||||
|
in a `site` tool argument. A wrong-house action should require a tool that isn't
|
||||||
|
mounted, not just a wrong parameter value.
|
||||||
|
- **Read-only and elevated access are separate instances** with separate upstream
|
||||||
|
credentials, each from its own machine account (A7). The read-only instance is
|
||||||
|
mounted by default. The elevated instance is mounted only after plan approval (C2).
|
||||||
|
This puts the capability gate at the credential layer, instead of a flag inside a
|
||||||
|
third-party server.
|
||||||
|
- **Per-task tool profiles** mount only the servers that a given task's scope
|
||||||
|
requires.
|
||||||
|
|
||||||
|
#### Rejected alternative: routed meta-tools / lazy tool registration
|
||||||
|
|
||||||
|
Several community Proxmox and UniFi MCP servers use search-then-dispatch over a large
|
||||||
|
generated tool catalog. This is **rejected in favor of per-task tool profiles**:
|
||||||
|
|
||||||
|
- *Two extra round-trips per call,* which is worse with a local model driving the
|
||||||
|
control loop.
|
||||||
|
- *The model can no longer discover capabilities it wasn't already looking for.*
|
||||||
|
That is backwards for exploratory diagnostic work.
|
||||||
|
- *Arguments arrive as an opaque blob,* instead of being constrained by per-tool JSON
|
||||||
|
Schema.
|
||||||
|
- *A dynamic tool list defeats prompt prefix caching,* and caching is much of what
|
||||||
|
makes a large static list affordable.
|
||||||
|
|
||||||
|
Acceptable only when consuming a third-party server whose surface we can't trim.
|
||||||
|
|
||||||
|
### F4. Containers, images, and restarts
|
||||||
|
|
||||||
- **Containers + persistent volumes.** Stateless image; named volumes carry state
|
- **Containers + persistent volumes.** Stateless image; named volumes carry state
|
||||||
across redeploys (and allow optional persistence between deployments).
|
across redeploys (and allow optional persistence between deployments).
|
||||||
@@ -907,6 +1018,9 @@ each agent is built — not standing up the service itself.
|
|||||||
- **Gate the restart, not the pull:** an idle/"safe-to-restart" flag; Watchtower must
|
- **Gate the restart, not the pull:** an idle/"safe-to-restart" flag; Watchtower must
|
||||||
never restart mid-work. *Unplanned* interruptions (reboot/crash) are handled by the
|
never restart mid-work. *Unplanned* interruptions (reboot/crash) are handled by the
|
||||||
durability layers (A2/A3), not by restart timing.
|
durability layers (A2/A3), not by restart timing.
|
||||||
|
- **Carve-out: third-party MCP server images** (F3) are excluded from Watchtower.
|
||||||
|
They hold root-equivalent credentials to Proxmox and UniFi, so they are
|
||||||
|
digest-pinned and updated deliberately after review.
|
||||||
- **Survive reboots:** VM + Docker service set to start on boot; `restart:
|
- **Survive reboots:** VM + Docker service set to start on boot; `restart:
|
||||||
unless-stopped`; ZFS snapshots of the state dataset (scheduled + on each approval).
|
unless-stopped`; ZFS snapshots of the state dataset (scheduled + on each approval).
|
||||||
- **Terraform + cloud-init:** deferred. When adopted, `bpg/proxmox` + cloud-init,
|
- **Terraform + cloud-init:** deferred. When adopted, `bpg/proxmox` + cloud-init,
|
||||||
@@ -976,8 +1090,11 @@ The rest resolve when reached, not now.
|
|||||||
are still moving. One repo can still build both agent images via path-filtered
|
are still moving. One repo can still build both agent images via path-filtered
|
||||||
CI. Splitting later stays easy if a real need appears; merging later would not
|
CI. Splitting later stays easy if a real need appears; merging later would not
|
||||||
have been.
|
have been.
|
||||||
- **Sysadmin browser access to network gear:** standing vs. per-task hand-off. The
|
- **Sysadmin fallback browser access to network gear:** standing vs. per-task
|
||||||
lock-yourself-out risk argues for per-task.
|
hand-off. With UniFi reachable over MCP (F3), the browser is no longer the primary
|
||||||
|
path to network gear. It is a fallback for operations the UniFi API does not
|
||||||
|
expose. The open question is whether that *fallback* access is standing or handed
|
||||||
|
off per task. The lock-yourself-out risk still argues for per-task.
|
||||||
- **Policy action taxonomy (C2):** the concrete list of which operations are
|
- **Policy action taxonomy (C2):** the concrete list of which operations are
|
||||||
allow vs. confirm. Best decided with the agent half-built in front of you.
|
allow vs. confirm. Best decided with the agent half-built in front of you.
|
||||||
- **Approved-plan externalization — decided: Gitea-backed from the start.** Plans are
|
- **Approved-plan externalization — decided: Gitea-backed from the start.** Plans are
|
||||||
@@ -987,6 +1104,14 @@ The rest resolve when reached, not now.
|
|||||||
so boot-time reconciliation (A3) can re-derive gate state from Gitea even after
|
so boot-time reconciliation (A3) can re-derive gate state from Gitea even after
|
||||||
state-volume loss — external truth wins (A2). Approval mechanics and revision
|
state-volume loss — external truth wins (A2). Approval mechanics and revision
|
||||||
binding are specified in A4.
|
binding are specified in A4.
|
||||||
|
- **Parked: relocating stateful MCP servers off the owning agent's VM.** The trigger
|
||||||
|
is **statefulness** (a browser pool, a vector index, a warm cache), not sharing:
|
||||||
|
duplicating a stateful server duplicates its state. Relocating has a cost. A shared
|
||||||
|
server can't hold per-agent tokens, so per-agent identity has to move into the
|
||||||
|
request, through caller auth plus per-caller upstream credentials. That loses the
|
||||||
|
attribution property that makes instance-per-agent-VM (F3) clean.
|
||||||
|
Instance-per-agent-VM is the starting position. Migrate one server at a time, when
|
||||||
|
a specific server justifies it.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1013,6 +1138,7 @@ nisse-dev/ # app: sealed VM (Part B)
|
|||||||
nisse-ops/ # app: separate host, Tailscale (Part C)
|
nisse-ops/ # app: separate host, Tailscale (Part C)
|
||||||
policy engine, tiered confirmation, scheduled checks
|
policy engine, tiered confirmation, scheduled checks
|
||||||
Vikunja ticket intake, Outline runbooks/records
|
Vikunja ticket intake, Outline runbooks/records
|
||||||
|
MCP → Proxmox/UniFi per site and privilege (F3); runs on its own VM
|
||||||
browser → admin UIs (API-first, UI fallback), confirm-first gating
|
browser → admin UIs (API-first, UI fallback), confirm-first gating
|
||||||
|
|
||||||
infra/ # (future) Terraform + cloud-init (Part F)
|
infra/ # (future) Terraform + cloud-init (Part F)
|
||||||
|
|||||||
Reference in new issue
Block a user
One note (and I think this was wrong in the original prompt), the domains for the two sites end in garrett-hall.com, and hartman-hut.com.
Good catch. The identifiers themselves stay the same:
garrett-hallandhartman-hutare the stems ofgarrett-hall.comandhartman-hut.com. But the text said "domain stem" without naming the domains, so a reader could not check which domain it meant. Each site now names its domain: "hosts undergarrett-hall.com" and "hosts underhartman-hut.com".