Local inference
Independent GPU provisioning, DNS, and local coding host configuration
This component owns independent GPU provisioning, DNS, and host configuration
for llama.cpp, Codex CLI, and T3 Code. These definitions do not establish
deployed state or service health. Deployment requires separate authorization.
Layout
tf_setup/ owns the GPU VM, encrypted storage, rotating KMS key, network
and Yandex child DNS zone. Its stage-local AL configuration selects only
Yandex credentials and the provisioning state backend. Its
change record
records provisioning source acceptance and allocation outcomes.dns/ owns the public Cloudflare delegation and CNAME with independent
state and DNS-only credentials. Its change record
records verified DNS ownership.ansible/ installs checksum-pinned Codex, llama.cpp, and the approved
CUDA 12.8 runtime; stages a checksum-verified model; and configures
local Codex inference and tools. It also runs T3 Code behind Traefik mTLS.
Bazel owns runtime pins in third_party/; the deployment packages the
verified archives for Ansible. The shared llama role owns inference.al.lua wires the component’s Vault AppRole and signed Ansible SSH key.openspec/ records the component contract and implementation evidence.
There is no service-side tf/ stage: llama.cpp and Codex configuration lives
in host files managed by Ansible, rather than a separately provisioned API.
Prerequisites for a later deployment
The provisioning stage supplies the VM address.
The host deployment consumes its VM address; it does not create cloud resources
or load Yandex credentials. Ansible requires the VM address, a
controller-side GGUF, and its SHA-256 digest, as described in
host configuration. No model is selected or downloaded
implicitly, and no cloud inference credential is needed. The documented
provisioning uses Fedora, while the current Ansible stage requires Ubuntu
24.04; host configuration needs adaptation before deployment.
Inference and access
Codex connects to the local Responses bridge at 127.0.0.1:8080/v1.
The project-owned bridge
translates function namespaces for llama.cpp on port 8081 and
restores their identities in replies. It performs no inference or tool execution.
Root-owned requirements constrain
its provider and disable OpenAI-hosted search, apps, plugins, image
generation, and remote control. T3’s authenticated loopback MCP is allowed;
unapproved MCP identities remain blocked. Local shell/edit tools remain
enabled. Accounts retain normal networking; Codex’s command permissions
govern their tool use.
T3 Code and direct Codex sessions share an ordinary coding account. T3
initially selects local-coder with Auto approval review through local inference. Full access
remains selectable. Seed settings preserve subsequent UI changes. Traefik
exposes https://t3code.local-inference.alwaldend.com using Vault ACME and
requires a trusted client certificate plus native T3 pairing/session authentication.
T3 and llama.cpp listen only on loopback. The canonical dnsconfig.json is
consumed by Vault’s exact-hostname PKI role and the independent
DNS Terraform root. Provisioning retains only the Yandex
child zone and VM address.
Local means inference runs on the VM. Browser interactions reach that VM, and Yandex
Cloud operates its hardware. Encryption protects persistent storage at rest.
Source validation
Formatting, packaged Ansible syntax, rendered configuration, and
backend-disabled Terraform validation can run without deploying. Never use
the normal Ansible target or a Terraform plan as an offline check.
The maintained change records the actual source-validation evidence and
the GPU, fresh-session tool-call, approval, mTLS, and reboot checks required
after separately authorized deployment.
The checked-in protocol acceptance target is
//projects/llama_responses_bridge/test/protocol:protocol_test. It starts the actual bridge
against a deterministic function-only backend and checks JSON/streaming routing,
replayed calls, and explicit rejection of unsupported tools. See
protocol acceptance for the pinned Codex round trip.
1 - Local inference configuration
Local llama.cpp, Codex, and T3 Code behind Traefik mTLS
This stage configures the dedicated Ubuntu 24.04 GPU VM. The image must
already supply a proprietary V100-compatible NVIDIA driver at least 570.26.
The approved CUDA 12.8 runtime is installed from llama.cpp’s checksum-pinned
archive. Bazel owns runtime versions and integrity digests under
third_party/com_github_ggml_org_llama_cpp, com_github_openai_codex, and
com_github_pingdotgg_t3code. The deployment packages all four archives;
Ansible transfers and extracts them without downloading release assets on the VM.
The shared alwaldend.main.llama role owns the account, runtime, model, unit,
restart handler, and readiness. This deployment owns the V100 image checks
and Codex-specific policy and bridge.
Set LOCAL_INFERENCE_HOST from Terraform’s ansible_host output. Supply
LOCAL_INFERENCE_MODEL_SRC as an absolute controller-side GGUF path and
LOCAL_INFERENCE_MODEL_SHA256 as its SHA-256 digest. No model is downloaded
implicitly. The default context is one 32k conversation; reduce
llama_context_size when the model’s memory requirements demand it.
The prepared Vault server PKI role, component AppRole, DNS permissions, and
public delegation must be deployed separately before certificate enrollment.
No production token or certificate is created by source validation.
T3 Code runs as its ordinary unprivileged t3code account on
127.0.0.1:3773. The complete checksum-pinned official standalone bundle
is installed before startup. Traefik exposes
https://t3code.local-inference.alwaldend.com, requires a certificate from
the repository’s client CA, and obtains its server certificate through
Vault ACME. Port 80 serves ACME validation and HTTPS redirection. T3’s native
pairing/session authentication remains enabled. The standalone CLI is
/opt/t3code/releases/standalone/t3; use its full path as the service
account to create pairing grants after deployment. Treat
pairing codes and startup URLs as secrets. This release does not consume the
legacy T3CODE_AUTH_TOKEN variable.
Initial T3 settings select the existing Codex executable, its dedicated
service-account home, the local-coder model, and Auto mode. They seed only
an absent settings file, preserving later UI changes. Auto uses Codex’s
selected local provider for approval review. Full access remains selectable
and removes the command sandbox when chosen. The actual local model must
support tool calls and approval review; fitting in VRAM does not establish
agent reliability.
Root-owned /etc/codex/requirements.toml pins Codex to
the Responses bridge at 127.0.0.1:8080/v1 and disables OpenAI-hosted search, apps, plugins, image
generation, and remote-control capabilities. The MCP allowlist permits only
t3-code at http://127.0.0.1:3773/mcp; its port follows t3code_port.
T3 supplies per-session authentication. A changed URL or another MCP name
remains blocked. Local shell/edit tools, hooks, and subagents remain available.
Accounts retain normal networking, including network access from commands
allowed by Codex. This configuration does not add an outbound
firewall or custom restrictions to T3’s other normal functions.
The packaged first-party bridge runs as llama in responses-bridge.service.
It translates namespaced function schemas, call history, and JSON/SSE replies
for the pinned llama.cpp server on 127.0.0.1:8081. Unsupported tool schemas
fail explicitly instead of disappearing from the model’s tools. The bridge
has a fixed loopback upstream and no authentication state. Readiness checks
reach llama.cpp through the bridge. Archive comparison repairs incomplete
runtime extraction on subsequent Ansible runs.
T3 and direct Codex sessions share the same unprivileged coding account,
home, and workspace. An administrative SSH session can run Codex directly:
sudo -iu t3code codex -C /opt/t3code/work
Build the package without connecting to a host:
bazel build //infra/local_inference/ansible:ansible_bin
The normal //infra/local_inference/ansible target deploys and requires
separate authorization. Future live acceptance must verify GPU model loading,
a fresh Codex response and tool follow-up, local Auto review, Full selection,
T3 MCP discovery, mTLS rejection/acceptance, application authentication, and
reboot persistence.
Keep non-secret verification artifacts under ignored out/ and record
model/runtime revisions with the results.
2 - Local inference DNS
Independent Cloudflare DNS root for the local inference service
This Terraform project owns the public delegation and T3 Code CNAME declared
in ../dnsconfig.json. It uses the pinned Cloudflare provider, the component
AppRole and DNS credentials, and its own Vault HTTP state backend at
alwaldend.com/vault1/approles/src_infra_local_inference/tf_backend/dns.
It has no dependency on GPU provisioning inputs, Yandex login, or VM state.
Use //infra/local_inference/dns:tf.plan to save a private plan, inspect it
with :tf.show, and apply the reviewed file with :tf.apply. Source checks
use :tf_tests.fmt_test and affected Bazel lint/tests. Live operations require
explicit authorization.
3 - Local inference provisioning
Encrypted Yandex Cloud Cascade Lake V100 VM
This stage owns the VM, network, security group, encrypted SSD, KMS key,
and the delegated Yandex DNS zone. Providers are pinned
by the repository’s shared Terraform toolchain. The Vault HTTP backend
and Yandex credentials arrive through this stage’s al.lua.
The component AppRole is owned by infra/vault/tf. Prepare the Yandex folder
and service-account credentials through infra/yandex_cloud/org1/tf after
that identity is available. These are separately authorized prerequisites.
Required inputs are image_id and admin_ipv4_cidrs; folder_id is injected.
The authorized provisioning uses Fedora 43 image fd8iku26nnkveh8s4di1, already
used by infra/download and infra/threexui. No image family is resolved
implicitly. This standard OS image does not establish NVIDIA driver readiness;
GPU driver installation and Fedora host configuration remain host-stage work.
The current host candidate in PR #233 still requires Ubuntu and must be adapted
before it can configure this Fedora VM.
Defaults are ru-central1-a, one preemptible A100 with 80 GB VRAM, 28 vCPUs,
119 GiB RAM, and a 200 GiB network SSD. The disk uses AES-256 KMS encryption
and annual key rotation. Key, disk, and instance have destruction guards;
the instance does not auto-delete the disk. The boot disk explicitly uses legacy
BIOS with PCI topology V1, matching the last applied compatibility trial.
Ingress allows TCP 22 from administrator CIDRs and public TCP 80/443 for
Traefik’s ACME validation, HTTPS redirect, and mTLS application route.
T3 Code and inference listen on loopback. Accounts retain normal outbound
networking, and no service account is attached to the VM. Zone and hostname
inputs come from ../dnsconfig.json; the VM’s dynamic address is maintained
in the delegated Yandex zone.
Offline formatting check:
bazel test //infra/local_inference/tf_setup:tf_tests.fmt_test
After separate authorization, operators can use :tf_setup.plan and
:tf_setup.apply. These commands contact Vault and Yandex Cloud and must
not be used for offline validation. Export the ansible_host output as
LOCAL_INFERENCE_HOST for the Ansible stage.
Public delegation and CNAME ownership are extracted into the independent
infra/local_inference/dns Terraform root and backend. This provisioning root
contains no Cloudflare resources, credentials, or DNS-only command variants.
Its Yandex child zone and VM address still consume the canonical component
DNS document. DNS has independent ownership and state. Host configuration is delivered
separately in PR #233.
The operator explicitly selected public SSH for this deployment using
admin_ipv4_cidrs = ["0.0.0.0/0"]; cloud-init disables password authentication
and root login. Other deployments can supply narrower IPv4 CIDRs.
Creating a VM starts it automatically. This provider does not manage a desired
power state; stopping the VM after provisioning is a separate control-plane
operation. The authorized deployment’s final state must be verified explicitly.
On 2026-11-03, the approved folder/account prerequisite completed with nine
additions. The provisioning apply created the six supporting resources,
including the encrypted disk, but Yandex rejected VM creation because the
memory quota was exceeded in ru-central1-a, with one further unspecified
quota violation. No VM was created or booted.
The operator then requested another availability zone and authorized replacement
of unused component resources. The ru-central1-b attempt replaced the subnet
and deleted the old unused disk, but Yandex rejected the new disk with
ResourceExhausted: Resource allocation is restricted. That left five resources:
the subnet, network, security group, KMS key and child DNS zone. The boot disk
and VM were absent; disk destruction protection was restored.
The indexed incident listing described zone b as unavailable. A later live
zone-status read reports b UP, while the cloud’s b CPU, RAM and SSD allocation
quotas remain zero. The platform support table
does not support either V100 platform in zones d/e.
The operator then selected a return to ru-central1-a with a smaller VM.
gpu-standard-v2 retains one 32 GB V100 and eight vCPUs while reducing host RAM
from 96 GiB to 48 GiB. Provisioning remains subject to cloud quotas and capacity.
The smaller attempt created the disk and replaced the subnet in ru-central1-a,
but VM creation still exceeded the memory quota, with one additional unspecified
quota violation. No VM was created or booted. A subsequent read with the cloud
management identity verified the correct target cloud and showed sufficient
published RAM, CPU, GPU and dynamic-IP headroom for the 48 GiB configuration.
Those later values do not establish the second earlier violation or prove that
a quota increase was necessary. The component identity cannot read cloud-level
quotas; the cloud-management identity can.
After the operator reported increased quota, a fresh 48 GiB retry passed the
initial quota check but failed during allocation with Not enough resources.
Operation fhmvss42r60r4mrqmcp7 completed with error code 8 and no further
details; no VM was created or booted. The operator then authorized restoring
the original 96 GiB Broadwell configuration after increasing RAM quota.
That original-size retry also failed allocation with Not enough resources,
in operation fhmk1pqi4vth1k6f4oj6. Both V100 configurations have therefore
failed their tested regular-capacity requests in zone a. Six supporting
resources remain, including the encrypted disk; no VM exists. Further attempts
need a changed allocation condition or a selected alternative.
A preemptible V100 retains the requested GPU and VM size but may be stopped
by Yandex at any time, with a maximum 24-hour run. The larger Gen2 option in
zone d provides 80 GB VRAM and requires at least 144 GiB host RAM, additional
quota and a compatible UEFI image. Those alternatives require an operator
choice before changing the current regular V100 configuration.
The operator selected the preemptible V100 alternative after both regular
configurations failed allocation. This keeps the original GPU, eight vCPUs,
96 GiB RAM and encrypted disk while setting preemptible = true. Yandex may
stop it at any time and each run lasts at most 24 hours. The initial creation
must still be followed immediately by a verified stop. Preemptible mode does
not establish available capacity or GPU-driver readiness.
The 96 GiB preemptible attempt also failed allocation, in operation
fhmp4c79geskjtismueq. The next retry uses the earlier approved 48 GiB
Cascade Lake shape with the same preemptible policy, GPU capacity and disk.
The 48 GiB preemptible attempt also failed with Not enough resources, in
operation fhmljpjc6622s8uo7jlk. No VM exists. The configuration remains
preemptible Cascade Lake V100 in zone a, ready for a fresh authorized retry
after allocation conditions change. Kazakhstan’s region-specific platform
limits list only the 24 GB T4i GPU, below the requested 32 GB VRAM; no
Kazakhstan VM was attempted.
The subsequent larger-GPU trials tested A100 80 GB in zone a and Gen2 80 GB
in zone d. After the operator increased zone-a CPU quota, A100 requests
reached allocation but failed with Not enough resources in both regular
and preemptible modes, including with legacy PCI topology V1. The Gen2
attempt was rejected by zone-d memory quota plus another unspecified quota;
its published GPU quota was also zero. These errors do not identify which
physical resource was unavailable.
The current candidate retains the last attempted preemptible A100 shape in
zone a. No VM was created or booted. The six supporting resources remain;
all three destruction guards are restored. The
change evidence
records the exact configurations and operation IDs.