Address review findings on the Grok provider work:
- budget-feed plugin failed open unconditionally, so a one-shot task agent
whose in-container SDK budget server went unreachable would run with the
cost cap unenforced. The entrypoint now exports ROBOCO_BUDGET_ENFORCE=1
(one-shot agents always start that server) and the plugin's pre-exec gate
fails CLOSED when the flag is set and the budget endpoint is unreachable,
halting an uncapped burn. Interactive serve agents (intake/secretary) set
no flag and keep failing open (they run no budget server by design).
- Pin opencode-ai to the live-verified 1.17.8 (was an unpinned global npm
install). Untrusted model output runs under it; bump the pin deliberately.
- Document the ROBOCO_GROK_* operator vars in .env.example (image, the three
opencode permissions, reasoning effort, idle-kill, cost ceiling).
- Refresh stale plugin comments: the MCP tool-name shape and the secretary
tool-registration path are confirmed live, and secret-scrub's load route is
the auto-discovery dir (not a config plugin: array). Keep the honest
not-yet-exercised caveat on secret-scrub's deny path and the reasoning
variant — those remain genuinely unverified.
Live end-to-end verification (opencode 1.17.8 + grok-build-0.1) of the WHOLE
integration, then fixes for what it surfaced:
1) Intake draft card (FUNCTIONAL): opencode's synchronous serve reply
(POST /session/:id/message) returns only [step-start, text, step-finish] — it
does NOT include tool-call parts, so the driver could never extract the
propose_draft draft. intake-tools.js now POSTs the draft straight to the
prompter-live relay (/api/prompter/live/{session}/events, the same endpoint
the driver's relay sink uses), so the panel renders the card regardless.
Verified live: grok calls propose_draft -> the relay receives the draft.
2) Correct misattributed opencode "bugs" (DOCS): earlier comments asserted as
general opencode behavior that a provider.xai block / npm override / config
plugin:-array "break" registration. Re-testing showed those were artifacts of
a PROJECT-level .opencode/opencode.json; from the GLOBAL config (which
opencode_config writes) the built-in provider, model resolution, the plugin
array AND the auto-discovery dir all work, and MCP gateway verbs register
(delivery agents verified). Reframed the comments as design choices (built-in
provider + XAI_API_KEY env + plugins baked in the auto-discovery dir with
named exports) and dropped the false claims.
3) Reasoning --variant: passing it does not error, but whether opencode applies a
named reasoning variant to grok-build-0.1 (no provider-defined variants) is
UNVERIFIED — comment softened from a "~54% cut" claim to best-effort,
measure-on-NAS.
Verified live this session: one-shot delivery (model + MCP verbs + plugins +
hooks), secretary tools (read_company_state + submit_directive -> backend with
token), intake draft (relay), grok built-in-provider tool-calling. Remaining
NAS-only: full container assembly (SDK :9000 startup, entrypoint hooks, 429
parking) + the --variant cost measurement. Gate green (ruff/mypy + 51 tests;
node --check the plugins).
Live verification (opencode 1.17.8 + grok-build-0.1, funded key) showed the Grok
runtime was loading INERT, three ways:
1. The provider override `provider.xai.npm=@ai-sdk/openai` failed model
resolution (ProviderModelNotFoundError) — opencode can't resolve that package
from its module path. Worse, ANY custom `provider.xai` block (even just
options) breaks plugin-tool registration. opencode's BUILT-IN xai provider
drives grok-build-0.1 with working tool-calls, so emit NO provider block; the
key + base reach it via XAI_API_KEY / XAI_BASE_URL env (provider.options.apiKey
alone does NOT authenticate).
2. Plugins referenced by absolute path in the config `plugin:` array never
registered their hooks/tools. opencode 1.17.8 only registers from the plugin
AUTO-DISCOVERY dir (~/.config/opencode/plugin/). Bake all plugins there.
3. Plugins must use a NAMED export, not `export default`.
Changes:
- opencode_config: no `provider` block, no `plugin` array; drop the dead
XaiTarget + timeout machinery; build_opencode_config now takes a model string.
- GrokProvider / orchestrator interactive env: inject XAI_API_KEY + XAI_BASE_URL
(drop the now-unused OPENAI_*).
- secret-scrub / budget-feed / secretary-tools / intake-tools: named exports;
baked into /home/agent/.config/opencode/plugin/ (drop the EXTRA_PLUGINS env).
- agent-grok* Dockerfiles: plugin dir + agent ownership; drop the unneeded
@ai-sdk/openai global install.
Verified live end-to-end: grok-build-0.1 calls read_company_state AND
submit_directive through secretary-tools.js and the backend receives both with
the agent token; a tool.execute.before guard fires; built-in tool-calls work.
Targeted gate green (ruff/mypy/xenon + opencode_config/providers/interactive
tests; node --check the plugins).
The keystone of the Grok parity work (CEO's "take Claude as baseline, create
what's missing" call): the one-shot Grok container now starts the same SDK
server the Claude path runs, so the per-verb circuit breaker (the flow/do MCP
servers already POST /verb/attempted to it), the per-session budget/loop
counters, the terminal-verb tracking, and the SessionEnd post-mortem all work
on Grok instead of being silently absent.
- entrypoint: launch roboco.agent_sdk.server (bare venv python, not `uv run`
which would re-sync the drifted clone lock and stall), wait for /health,
reset counters; run opencode WITHOUT exec so the script regains control to
run the post-mortem and the silent-exit substitute after the run returns.
- budget-feed.js: opencode plugin that gates on /budget/status in
tool.execute.before (halt/loop deny — the only place to stop a runaway
one-shot run; opencode has no PostToolUse-deny) and records the executed
tool + args-hash in tool.execute.after. Fail-open; bare-verb normalization
for MCP-namespaced terminal verbs.
- silent-exit substitute: on a graceful exit with no terminal verb the
entrypoint posts /terminal/force_substitute so the task isn't left stuck
claimed/in_progress (Stop-hook parity at the boundary).
- opencode_config: wire budget-feed into the plugin array; add
ROBOCO_OPENCODE_EXTRA_PLUGINS so per-image role tool plugins load scoped to
one role; read the per-role ROBOCO_GROK_EDIT_PERMISSION.
Targeted gate green (ruff/mypy/xenon + opencode_config tests; node --check on
the plugins; bash -n on the entrypoint).