feat(backend): local-ornith — a mission backend served by the node's own GPU

Claude Code pointed at the Ollama already installed on every GPU node. Ollama
has served a native Anthropic-compatible /v1/messages since v0.14, so this is
an env contract rather than a translation layer — the fourth variation on the
same idea as agent-glm and agent-kimi.

The route is NOT the egress proxy, and that is the design. `egress` speaks
CONNECT, takes a destination from the guest, resolves it and decides; every one
of those powers is a liability, which is why it refuses non-443 ports and IP
literals after a unit test caught them being bypassed. Routing a local model
through it would have meant relaxing both.

`local_model` is the opposite shape: there is no destination in the protocol.
fcagent listens on guest 127.0.0.1:11434 and pumps to vsock 9003; the node
splices that onto its own 127.0.0.1:11434 and copies bytes. A compromised guest
cannot redirect it because there is nothing to redirect — it is a pipe, not a
proxy, and strictly narrower than anything an allow-list could express. The
bytes never touch a network, so there is no wire for TLS to protect, and Ollama
stays bound to loopback rather than being exposed on the tailnet.

The socket is bound only for a backend declared to use a local model, so a
`local-ornith` VM reaches the forge through egress and nothing else, while every
other backend's guest port simply refuses. Both halves have negative controls.

`scripts/fleet-model-setup.sh` exists because of one measurement: stock
ornith:9b reported input_tokens=2050 for a 48000-word prompt and answered as
though nothing had been dropped. Ollama's default window is ~2K whatever the
model card says, and it truncates silently — the exact failure an agent turn
would hit and never report. The script pins num_ctx=131072 into a derived tag
and then PROVES both the window and tool calling before declaring success.
Verified on architect: ~65536 words -> 65604 input tokens, stop_reason=tool_use.

Placement needs no new capability key: building the rootfs only on GPU nodes
means `nodes::online_for_backend`'s existing `rootfs @> ["local-ornith"]`
predicate does the affinity, so morpheus never offers the backend.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-09 13:13:54 -07:00
co-authored by Claude Opus 5
parent e96c5143bc
commit f56d41f5b7
9 changed files with 498 additions and 10 deletions
+30
View File
@@ -79,6 +79,15 @@ fn provider_hosts(backend: Option<&str>) -> &'static [&'static str] {
// a different account namespace and rejects that key. Only the host the
// `agent-kimi` image bakes in.
Some("kimi") => &["api.kimi.com"],
// A locally-hosted model reaches NOTHING through this proxy. Its route
// is `crate::local_model` — a vsock pipe to the node's own loopback,
// with no destination in the protocol — so the correct allow-list here
// is the empty one, and it falls through to the branch below.
//
// Spelled out rather than left implicit because the temptation was to
// widen this proxy instead: an entry here would have meant relaxing the
// 443-only rule AND the IP-literal refusal, both of which exist because
// a unit test caught them being bypassed.
// Fail closed: a backend nobody taught this function about reaches the
// forge and no model API. It cannot silently borrow another provider's
// door, which is the failure this split exists to prevent.
@@ -449,6 +458,27 @@ mod tests {
}
/// A raw address must not sidestep a list written in names.
/// A local-model backend gets NO egress, and the 443 rule is untouched.
///
/// The alternative design routed the node's Ollama through this proxy, which
/// would have meant permitting port 11434 and an address the guest names.
/// Both are refused here, still, and a `local-ornith` VM reaches the forge
/// and nothing else — its model lives on the other socket entirely.
#[test]
fn a_local_model_backend_gets_no_egress_and_no_new_port() {
let allow = allow_list_for(Some("local-ornith"));
assert!(
allow.iter().all(|a| a == "git.redclaw.dev"),
"a local backend must reach only the forge, got {allow:?}"
);
for h in ["api.anthropic.com", "api.z.ai", "api.kimi.com", "127.0.0.1"] {
assert!(!host_allowed(h, &allow), "{h} must NOT be reachable");
}
// The rules this design exists to avoid loosening.
assert!(parse_target("anything:11434").is_err());
assert!(parse_target("127.0.0.1:443").is_ok_and(|(h, _)| !host_allowed(&h, &allow)));
}
#[test]
fn an_ip_literal_is_not_allowed() {
let a = vec![".anthropic.com".to_string()];