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:
co-authored by
Claude Opus 5
parent
e96c5143bc
commit
f56d41f5b7
@@ -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()];
|
||||
|
||||
Reference in New Issue
Block a user