Skip to content

v0.31.0

Choose a tag to compare

@rennf93 rennf93 released this 17 Sep 22:01
· 145 commits to master since this release
v0.31.0
5884a49

Added

  • Hummin becomes a first-class LLM provider (ModelProvider.HUMMIN) and the GLM go-to — the GLM-native hummin CLI (a pi-harness fork) running headless in Docker on the GLM Coding Plan. The kimi pattern with the OpenRouter auth shape: the operator's Z.ai key is stored Fernet-encrypted (PUT /providers/hummin-key, migration 102_seed_hummin_provider seeds the enabled=true row after 101_modelprovider_hummin adds the enum value) and injected as ZAI_API_KEY at spawn — no ~/.hummin credential mount, no refresh loop, initial prompt as an env var never an argv token. The provider quartet (roboco/llm/providers/hummin.py + hummin_cli_config.py / _cli_sniff.py / _cli_usage.py) renders fleet-safe ~/.hummin/agent/settings.json defaults plus a per-role --tools strict allowlist (kimi's permission-rules semantics mapped onto hummin's allowlist; no hooks/MCP exist to port — see below), scrapes the tee'd --mode json run log (sum of message.usage across ALL assistant message_end events, a genuine 4-bucket split, turns = assistant count), and classifies rate-limit/auth parks from ONLY the structured stopReason/errorMessage/error.message channels plus stderr — load-bearing because hummin's JSON mode exits 0 even when the assistant errored, so the entrypoint branches on the sniff result, never the raw exit code. The roboco-agent-hummin image (docker/agent-hummin.Dockerfile + docker/scripts/hummin-cli-agent-entrypoint.sh, hummin-cli npm install, Node >= 22.19 build-fails-loud guard, prompt_guard, hummin's own auth check --provider zai --json preflight mapped to the fleet's exit-78/75 park convention); the routing-service integration (set_hummin_api_key, apply_mode("hummin")/_apply_hummin — spawn-time key gate, the openrouter precedent; derive_mode; HUMMIN in the interactive guard and the upsert auto-enable tuple); the runtime plumbing (registry registration, per-agent usage dirs, finalize token/turn readers, rate-limit/auth park handlers, active-token sweep reader, an uncapped-by-default hummin_max_concurrent); the API (GET/PUT /providers/hummin-key, HumminKeyStatus/SetHumminKeyRequest, "hummin" in the three mode Literals); and the full panel surface (HumminProviderKeyRow, the key-gated mode button, ProviderBadge variant, the GLM group in the Mix picker). The static catalog's GLM entries (glm-5.3, glm-5.3-flash, new glm-5.3-highspeed) re-pointed from ZAI to HUMMIN — ZAI (the Anthropic-protocol Claude-spawn path) keeps its row and key endpoint as a manual fallback but has no catalog entries left, and glm-5.3:cloud/glm-5.3-flash:cloud stay on OLLAMA_CLOUD. Two documented V1 degradations, both live-verified against the hummin source: hummin has NO MCP client, so the RoboCo gateway is unreachable from a hummin agent (completion rides the orchestrator's still-owned-task path; an explicit -e bridge extension is the named follow-up), and there is no PreToolUse hook surface, so the bash-guard cannot install — the tool-level allowlist is the only boundary. HUMMIN_MEMORY=0 is force-set in the container env so hummin's memory extension can never write the operator's personal vault. No _PRICING row for glm-5.3-highspeed yet (no verifiable published rate): calculate_cost falls back to the glm-5.3 rates via longest-fragment match, over-attributing spend at the full rate — the safe direction — until a published price lands.

  • The Z.ai GLM provider (ModelProvider.ZAI) runs GLM through Claude Code via the built-in Anthropic translator - the manual fallback hummin now fronts. A static-catalog batch of GLM ids (glm-5.3, glm-5.3-flash), a Fernet-stored key endpoint pair (GET/PUT /providers/zai-key, migrations 097_modelprovider_zai + 098_seed_zai_provider seeding key-gated), and spawn-time ANTHROPIC_BASE_URL injection pointing at z.ai's Anthropic-compatible endpoint, so ZAI agents speak the Anthropic Messages protocol with no new CLI runtime. Panel: the four provider key boxes laid out in a 2x2 band and the routing mode strip wrapped into rows with a Z.ai mode button (an intermediate tabs restructure of the card was rejected and reverted same-day). When hummin landed, the GLM catalog entries re-pointed from ZAI to HUMMIN: ZAI keeps its provider row and key endpoint as the manual path but holds no catalog entries. derive_mode's return Literal gained zai in the same cycle.

  • Nebius Token Factory becomes a first-class LLM provider (ModelProvider.NEBIUS) - the full-parity twin of the OpenRouter provider. Nebius Token Factory (https://tokenfactory.nebius.com) is an OpenAI-compatible inference API serving 60+ open models (NVIDIA Nemotron, DeepSeek, Qwen, Llama) behind one metered API key at https://api.tokenfactory.nebius.com/v1; the provider's default model is NVIDIA's Nemotron 3 Super (nvidia/nemotron-3-super-120b-a12b, nebius_cli_model), so a Nebius-mode fleet runs an NVIDIA open model by construction. The openrouter pattern is cloned end to end: the provider quartet (roboco/llm/providers/nebius.py + nebius_cli_config.py / _cli_sniff.py / _cli_usage.py, the Ollama shape - static Fernet-stored key injected as NEBIUS_API_KEY/NEBIUS_BASE_URL env at spawn, no auth mount, no refresh loop, initial prompt as an env var never an argv token); the roboco-agent-nebius image (docker/agent-nebius.Dockerfile + docker/scripts/nebius-agent-entrypoint.sh, opencode CLI runtime, prompt_guard + exit-78 auth preflight + exit-75/78 park sniffing); migrations 099_modelprovider_nebius (enum add in an autocommit_block) and 100_seed_nebius_provider (key-gated enabled=false seed; renumbered from 097/098 in the slave merge after the ZAI provider took those numbers); the routing-service integration in roboco/services/llm.py (set_nebius_api_key, apply_mode("nebius")/_apply_nebius with provider_type_override, derive_mode, probe_nebius_models, NEBIUS in the interactive guard and the upsert auto-enable tuple); the runtime plumbing (registry registration, per-agent usage dirs, finalize token/cost readers off the metered usage.cost, rate-limit/auth park handlers, active-token sweep reader); the API (GET/PUT /providers/nebius-key, GET /providers/nebius/models reusing the OpenRouterModelEntry schema, NebiusKeyStatus/SetNebiusKeyRequest, "nebius" in the three mode Literals); and the full panel surface (NebiusProviderKeyRow, the key-gated Nebius mode button + model picker, useNebiusKey/useSetNebiusKey/useSearchNebiusModels). Two deliberate differences from OpenRouter: Token Factory's model list carries no capability metadata, so the search proxy has no tools filter and only id is substantive; and there are no attribution-header settings (no referer/title dashboard to feed). Per-agent Mix-mode pins were out of scope at ship time (the openrouter decision) and landed in the levels-parity batch below. Docs: docs/backend/api/nebius-provider.md, docs/frontend/nebius-provider.md, docs/frontend/components/nebius-routing-ui.md.

  • Hackathon submission notes (HACKATHON.md) + a Nebius provider block in the README. RoboCo predates the Nebius x NVIDIA hackathon's submission window (August 26 to October 30, 2026), and Stage One screens for "not a superficial rebrand", so the new root-level HACKATHON.md isolates exactly what changed in-window for that pass/fail review: the Nebius Token Factory provider integration with NVIDIA's Nemotron 3 Super (nvidia/nemotron-3-super-120b-a12b) as the fleet-wide default by construction (apply_mode("nebius") resolves default_model or settings.nebius_cli_model), the v0.30.0 platform release (2026-09-13, 16 merged PRs), and the post-release panel contract fixes; it also walks the judge path from make quickstart through saving the Token Factory key and flipping the fleet to Nebius mode. The README gained a matching "run agents on Nebius Token Factory (NVIDIA Nemotron)" optional-provider block alongside Grok/Kimi and a hackathon section linking the doc.

  • The run_sandbox_tests gateway verb executes the QA test run inside a Token Factory Sandbox microVM (the Coding and Agentic Engineering track's signature product). A new never-raise REST client (roboco/llm/providers/nebius_sandboxes.py) drives the Sandboxes service (https://api.tokenfactory.nebius.com/sandboxes/v1, Contree) three-call flow - octet-stream file upload (POST /v1/files), disposable instance spawn (POST /v1/instances with the uploaded uuid in files, shell: true, a per-run timeout, output truncation bounded), and operation polling (GET /v1/operations/{id} until SUCCESS/FAILED/CANCELLED, cancelling best-effort at the deadline, flattening metadata.result into exit_code/stdout/stderr/metered cost). The verb (in _DEV_DO/_QA_DO only - the two roles that run test suites - so no coordinator/board manifest offers it; request schema RunSandboxTestsRequest with a 2000-char command, optional image, optional 30-3600s timeout_seconds) guards flag off -> no active project-bound task -> no stored Nebius key (the Sandboxes API shares the inference key, bearer + optional Project header) -> workspace/team/project unresolvable -> git archive failure -> the new 64 MiB upload ceiling, then archives the committed HEAD (worktree else clone root, mirroring request_render's choice), runs the composed mkdir -p /workspace && tar -xzf ... && cd /workspace && <command> shell inside the microVM, and returns evidence.sandbox (exit code, stdout/stderr tails, cost, duration, instance/operation uuids) plus a journal entry, heartbeating the claim before and after the wait. Default-off via ROBOCO_TOKEN_FACTORY_SANDBOXES_ENABLED (also on the Feature Flags card) and inert on any failure - QA keeps its container shell; the local path is never broken. command joined _WAF_FREETEXT_BODY_FIELDS (test commands are agent-authored free text; the route keeps mesh WAF scanning, and the command only ever executes inside the ephemeral sandbox VM). Config settings, panel copy, tests/unit/llm/providers/test_nebius_sandboxes.py + tests/unit/gateway/test_run_sandbox_tests_verb.py (23 cases: contract shapes, headers, Location polling, deadline cancel, result flattening, the full guard matrix, evidence shape, and QA-only manifest scoping), and docs/backend/api/token-factory-sandboxes.md (with live bring-up verification items for the bearer/Project and default-image assumptions). Post-merge live verification with beta-granted Sandboxes access ran the full client flow green end to end (echo-scale run: exit 0, metered cost about 0.0003) and caught one real bug, fixed in the same cycle: the spawn response's Location header is HOST-ROOTED, so polling appended it to the /v1 base and double-pathed into empty 404s forever - wait_sandbox_operation now normalizes the path to the last /v1/ anchor, with a regression test pinning the host-rooted case.

  • Nebius levels parity: the four live-verified Nemotron ids joined MODEL_CATALOG, so Nemotron models are configurable at every routing level exactly like Claude. Before this batch the fleet default was the only Nebius knob: the static catalog carried no NEBIUS rows, which locked Nemotron out of the Mix-mode per-agent picker (the Select groups render from the catalog) and out of the complexity-override validator (resolve_provider_for_model is a catalog lookup). The new entries are the Nemotron family the live GET /v1/models returned with the hackathon key (Nano nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B, Super nvidia/nemotron-3-super-120b-a12b, Ultra nvidia/Nemotron-3-Ultra-550b-a55b, nvidia/Nemotron-3_5-Lightning; the first two ids' correctness matters most - the shipped default had been a best-effort guess). With the entries in place: the Mix picker renders a Nebius group (badge "N"), complexity-override rows accept Nemotron models (downgrade-only vs each role's Anthropic baseline; no _PRICING rows exist yet, so the comparator ranks them cheapest-tier - the same documented ceiling as OpenRouter), and mix pins resolve to the auto-enabled NEBIUS provider row. Nebius joins Codex/Gemini/Kimi in the interactive-only group's exclusions (one-shot V1). Tests: catalog membership + the fleet default being a catalog member (unit), a mix pin resolving to the NEBIUS provider row (integration), and a complexity override round-trip with the non-null cross-family warning (integration).

  • NAS deployment went blue-green: one compose carries both colors, and a deploy is an nginx include-file flip with zero dropped requests. docker-compose.nas.yml runs blue unprefixed and green under the green profile with per-color image tags; deploy/active-upstreams.conf holds three set directives consumed by a variable proxy_pass + the docker resolver, so upstreams resolve at request time and nginx can never crash on an absent color (it 502s that one route). The flip is a tmp+mv swap guarded by nginx -t (restore the previous file and abort on failure) followed by nginx -s reload, and the old color stops only after the new one passes health checks - rollback is re-running the stopped color. deploy-nas.sh ensures ALL images exist on the host BEFORE the new color comes up (a pull pass, then a host-side build pass for any missing docker/*.Dockerfile - in-container builds cannot work against a bind-mounted host path), polls health instead of --wait, and exports a per-color ROBOCO_API_URL so agents always resolve their own generation's dispatcher. An earlier option-0 script (pre-build/pull while serving, then up --wait) landed mid-batch and was superseded.

  • The runtime warns when an active spawn session's finalize records zero tokens - capture gaps used to undercount spend as $0 silently; the check now surfaces them at finalize time instead.

Fixed

  • The Nebius fleet default model id is live-verified against the Token Factory catalog: the shipped nvidia/nemotron-3-super-120b was a best-effort guess that does not exist - the real id is nvidia/nemotron-3-super-120b-a12b (Nemotron 3 Super, 120B-A12B). Verified with the hackathon Token Factory key (GET /v1/models, 200, zero credits): the catalog carries 24 models including the Nemotron family NVIDIA-Nemotron-3-Nano-30B-A3B, nemotron-3-super-120b-a12b, Nemotron-3-Ultra-550b-a55b, and Nemotron-3_5-Lightning. The old id would have failed every Nebius-mode spawn at runtime (the exit-78 auth preflight only checks key presence, not model validity). Fixed everywhere the id is pinned: nebius_cli_model default, the entrypoint's --model fallback, the usage reader's default, all tests, and the docs. The same live session settled the Sandboxes integration's assumptions: the Project header is REQUIRED (400 without it; the service-account id embedded in the key resolves as its value), and the hackathon static key is inference-only (403 spawn) - Sandboxes spawn permission must be granted console-side before run_sandbox_tests can execute, and the verb degrades to a 403 error string until then.
  • The OpenRouter and Nebius model pickers in Settings -> AI Routing rendered blank rows and could not actually select a model. The panel's OpenRouterModel/NebiusModel types (and every field access in the two picker blocks of ai-routing-card.tsx) were written against an invented {model_name, display_name, pricing: {prompt, completion}} shape, while the backend's OpenRouterModelEntry schema (which both GET /providers/openrouter/models and GET /providers/nebius/models return, the Nebius endpoint reusing it verbatim) has always carried {id, name, context_length, prompt_price, completion_price}. Every model name, display name, and price therefore rendered undefined (the screenshots' nameless rows with bare "— in / — out" and "1,000,000 ctx"), and clicking a row stored undefined as the picked model, so flipping to OpenRouter/Nebius mode never sent a default_model at all. The panel types now mirror the backend schema (id is the slug used as the routing model_name, prices are per-token USD floats), the rows render name/id and format prompt_price/completion_price per million tokens, and the vitest mocks were rewritten to the real contract (they had been written against the invented shape, which is how the suite stayed green under a broken UI) plus a new regression test pinning that clicking a row selects it and reports the picked id. The same audit removed the panel's remaining phantom contract fields: Task declared work_session_id/execution_log/outputs (the backend sends a work_session summary object instead, now typed as WorkSessionInTask mirroring the backend's WorkSessionSummaryInTask, and FileRef/ExecutionLog went with their only consumers) and Notification declared acked_by/acked_at/related_message_ids (per-recipient ack state is resolved server-side into is_acknowledged/is_fully_acknowledged); the notifications client also stopped sending a priority_filter query param the backend's ListNotificationsParams has never accepted.
  • Model pricing re-verified against source (2026-09-16): sonnet-5's 2/10 standard rate is permanent - the announced increase was cancelled - and glm-5.3-flash gained its missing _PRICING row.
  • The release workflow builds and publishes the OpenRouter agent image, and the image is listed in every compose image set. On v0.30.0 it was missing from the release build map and the registry compose, so an OpenRouter-mode fleet had no image to pull and the pull-smoke could not catch the gap.