gpt-6.1-terra runs through Codex but has no row in either price table,
so its usage estimates at $0 and shows up as an unmapped model. Add
OpenAI's standard short-context rates ($1 input, $0.05 cached input,
$1.25 cache write, $5 output per 1M) to the backend metrics table and
the frontend estimate table.
The model is live-only on codex-cli 0.164.0-alpha.11.1 (absent from
`codex debug models --bundled`), so also add it to the static fallback
catalog with the observed `low` default and low..ultra efforts, and
normalize its label alongside the rest of the GPT-6 family.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Multica Eve <[email protected]>
* fix(taskfailure): classify Claude Code session and weekly limits as provider quota
Claude Code reports a spent subscription window as "You've hit your session limit · resets 2pm (UTC)" or "You've hit your weekly limit · resets <date>". Neither matched the quota patterns, so these runs were classified as agent_error.unknown and quota-aware handling never saw them.
* fix(agent): stop waiting for claude after an error result
With stdin closed, the Claude Code CLI stays alive while any background task it started is running, then opens a new turn when the task ends. After an error result (a spent session limit, an API failure) that turn cannot change the outcome, yet the daemon kept the run open for the background task's lifetime. Terminate the process group 10s after an error result, the same way cancellation does, so the run fails with the result's error.
---------
Co-authored-by: Evgeniy Yakutov <[email protected]>
* fix(agent): drive OpenCode over ACP so long tools and subagents are not force-stopped
`opencode run --format json` reports a tool only when it finishes, with the
call and its result in one event. The daemon never saw a tool in flight, so a
long silent tool or a delegated subagent was judged by the 10-minute OpenCode
idle window instead of the tool budget and force-stopped (GH #9614).
Built-in OpenCode 1.15+ and 2.x runtimes now run `opencode acp`, which on both
majors announces a tool when it starts executing. MessageToolUse is emitted
from that in_progress frame, so the daemon applies AgentToolWatchdog for the
whole tool. One code path covers both majors:
- model via session/set_config_option (2.x has no session/set_model); effort
via the advertised `effort` option, splitting 2.x `model#variant`
- resume via session/load; a missing session is reported as ResumeRejected
- cancel via session/cancel, which aborts the running tool on both majors
- MCP: 1.x keeps OPENCODE_CONFIG_CONTENT; 2.x gets servers in session/new,
which lifts the 2.x MCP refusal on this transport
- 2.x subagent text (`_meta["opencode/child-session"]`) stays out of the
parent's output; its tool calls are kept
- usage summed from the session export over this turn's assistant messages,
since the prompt result's usage is not per-turn on either major
The run transport stays as the fallback for custom profiles, older or
unknown versions, agents with custom_args, and MULTICA_OPENCODE_TRANSPORT=run.
Also fix the run transport on 2.x: tool_use parts carry the call id as `id`
(1.x: `callID`), so every 2.x tool message had an empty CallID.
Co-authored-by: multica-agent <[email protected]>
* fix(agent): bound OpenCode ACP teardown when the runtime stops reading
A runtime that stops reading stdin mid-prompt left the prompt write blocked
holding the client's write lock. The cancel path then waited on that lock to
send session/cancel, and the cancel grace and the process-group kill only ran
after the write, so the process could outlive the task indefinitely.
- session/cancel is sent only once the prompt is fully delivered, from its own
goroutine, so the grace starts immediately and a stalled pipe cannot hold it
- a turn OpenCode never answered (or a cancelled/timed-out setup) is torn
down forcibly: stdin closed first to release a blocked write, then SIGTERM to
the group and SIGKILL after the grace; an answered turn still exits on EOF
Regression tests cover a stalled prompt write under a 1s timeout and a runtime
that ignores session/cancel.
Co-authored-by: multica-agent <[email protected]>
* fix(agent): bound OpenCode ACP teardown when a setup write stalls
Setup requests (initialize, session/new, session/load, set_config_option)
are synchronous stdin writes that check their context only after the write
returns. A runtime that stops reading after initialize, with a large MCP
payload in session/new, left the run goroutine blocked in that write, so
the deferred teardown never ran and the process outlived the timeout.
A watcher now force-stops the process as soon as the run ends before the
prompt is fully delivered; closing stdin releases the blocked write. Once
the prompt is delivered the existing session/cancel path owns shutdown.
Regression test: 256 KiB MCP env, runtime stalls after initialize, 1s
timeout; returns in ~1s (60s before the fix).
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Multica Eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
When a resume's baseline capture fails, the result event's modelUsage
was stored as the task's usage as-is. On newer Claude Code a resumed
session restores its cumulative counters, so that reported total spans
every prior task in the session: the #8781 inflation returned for every
baseline-unavailable mode. The reported trigger is a custom runtime
profile whose command is a wrapper script that sets CLAUDE_CONFIG_DIR
internally - the daemon resolves the transcript from its own env
(os.Environ() + agent custom_env) and never sees it, so every resume
logged 'claude usage baseline unavailable' and stored the running
session total (observed ~10x on a session's 7th task).
The code already distinguishes a transcript that exists but has no
cost-state (older Claude Code reports per-run totals there; behavior
preserved, pinned by existing tests) from one that cannot be found at
all. For the latter the reported modelUsage is now treated as
non-authoritative: usage keeps the stream-derived per-run totals, and
the reported totals are only recorded for the resume-rejected recapture
where the run is known to have started a fresh session of its own.
Affected modes now degrade to a conservative per-run approximation
instead of silently storing a cumulative total; the WRN stays, reworded
to say what actually happens.
Out of scope, per the adjudication on the issue: per-profile env vars
(schema/feature) and expanding $HOME/~ in custom_env values (makes the
manual workaround portable but leaves the default inflation path live).
Ablation: reverting only server/pkg/agent/claude.go on this branch makes
both new tests fail again with the cumulative total stored
(700/30/800/80 instead of the per-run 70/0/80/8).
Fixes#9564
Generated-by: GLM-5.3-Flash (ZCode)
* feat(agent): add host-local Devin CLI over ACP
Spawn `devin acp` via the shared ACP client. Local CLI only — not
cloud Devin VMs/Playbooks/Secrets. Model via `devin acp --model`.
Catalog from `devin models list --format json` (families/variants).
* fix: skip Devin ACP handshake when browser auth is also offered
* fix: close dim case string so Devin compiles
Missing quote after dim broke runtime_config.go and every Go CI job.
* fix: stop runtimeConfigPath recursion on Devin
Devin is a builtin whose ProtocolFamily is itself. Recursing on that
overflowed the stack and killed backend-tests.
* test(agent): cover Devin model failures and final-answer delivery
Co-authored-by: multica-agent <[email protected]>
* fix(agent): honor Devin model aliases and stored login (MUL-6623)
Co-authored-by: multica-agent <[email protected]>
* fix(agent): drain Kiro panic details before terminating runtime (MUL-6623)
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(openclaw): carry per-run model override on the run input (INFRA-71)
Reimplement MUL-7376 (#8419) on this fork and address the review points.
Resolve runtime_config.model_override once, at claim time, and carry it on the run input (model_override); the daemon no longer decodes it from mutable agent config.
OpenClaw keeps agent.model as the registered agent id (--agent) and the override as the underlying model (--model); arbitrary --model from custom_args stays blocked. Usage attribution falls back runtime model -> per-run override -> unknown, never the agent id.
Round-trip model_override through the runtime-config schema, serializer, equality check and the runtime-config tab (i18n in all locales). Docs updated.
Co-authored-by: multica-agent <[email protected]>
* docs(openclaw): clarify persistent agent model overrides
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Multica Eve <[email protected]>
* fix(opencode): treat process exit as the terminal signal on 2.x
OpenCode 2.x stops consuming step events once the session settles, so the
final step_finish is routinely never written. The daemon failed those
completed runs as 'step still open at EOF' / 'continuation never started',
classified them provider_network and auto-retried, repeating side effects.
On built-in 2.x runtimes, waive the two step-bracketing guards and let the
process outcome decide: exit code, error events, prompt write, cancel /
timeout and scanner errors still fail the run. The void-step guard stays.
1.x and custom runtimes are unchanged.
Also decode the 2.x error payload ({type, message}) so failures surface
their real cause instead of 'unknown opencode error'.
Refs #8892, #9456
Co-authored-by: multica-agent <[email protected]>
* fix(opencode): address review on 2.x terminal-signal relaxation
- Clear the void-step flag on 2.x once later text or a tool call shows the
run recovered, since the recovering step may never report step_finish.
1.x still requires that step to close.
- Never report stream-level completion (sawTerminalSignal) on 2.x: an
intermediate step_finish is not proof the run finished, so a failed
prompt write keeps failing the run.
Refs #9463
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Multica Eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(kiro): fail fast when the runtime panics mid-turn (MUL-8188)
Kiro can panic inside its agent loop while the ACP process stays alive, so
session/prompt never answered and the task hung until the 2h idle watchdog
or a manual cancel. Watch stderr live for a panic header, abort the prompt,
and surface the panic text. A resumed session that panics before its first
message signals ResumeRejected so the daemon retries once on a fresh session.
Co-authored-by: multica-agent <[email protected]>
* fix(resume): stop re-resuming sessions the runtime cannot drive (MUL-8188)
- taskfailure.UnresumableHistory matches "invalid conversation history",
so the daemon's fresh-session retry, classifyPoisonedError and
ResumeUnsafeFailure all treat Kiro's panic as resume-unsafe; the
GetLastTaskSession / GetLastChatTaskSession text guards mirror it for
rows written by older daemons.
- A resumed run cancelled after >=5 minutes with zero agent output now
retires its session: the daemon sends retired_session_id on cancel-ack
and the server records it on the cancelled row (status CAS, never
overwrites) and clears a chat pointer still naming it.
Co-authored-by: multica-agent <[email protected]>
* fix(resume): address review on retired sessions (MUL-8188)
- Manual retry no longer revives a retired session: the exact-source rerun
claim path now checks IsTaskSessionRetired (fails closed), still reuses
the source workdir, and sets PriorSessionResumeUnavailable.
- Silent-resume retirement measures awakeclock time, so host suspend
(Windows monotonic clock counts through sleep) cannot retire a healthy
session; adds a suspend regression test.
- Handler tests use dbfx.Issue/Task and testutil.Call per AGENTS.md.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
* fix(agent): discover Hermes reasoning options per model
* refactor(agent): extract per-model ACP effort probe
Move the discovery loop out of discoverACPModels into probeACPModelEffort
beside the other ACP effort helpers, and have acpOptionsAfterModelSwitch
return nil when the runtime exposes no post-switch state so callers no
longer re-check the response. Hermes execution only refreshes options when
a thinking level is set, which is the only consumer of the result.
* test(agent): trim Hermes effort regression tests
* feat(agent): surface Hermes dependency reinstall at launch (MUL-8124, GH #9390)
Hermes prints its launch-time dependency sync only to stderr, which the
daemon forwards at Debug, so the 30-60s rebuild that the per-task Hermes
home triggers when the user's home enables a memory plugin with Python
deps was invisible. Log one Warn when Hermes starts reinstalling (with
HERMES_HOME and a hint) and one when the ACP handshake returns, with
the measured startup delay. Task outcome is unchanged.
Document the cause and the memory.provider workaround in troubleshooting.
Co-authored-by: multica-agent <[email protected]>
* fix(agent): log Hermes reinstall finish when stderr lags the handshake (MUL-8124)
stdout and stderr are drained independently, so initialize can return
before the stderr copier reaches the reinstall marker Hermes wrote
first. Finish then saw no detection and the finish log was lost.
Record the handshake result and time in Finish, and emit the finish log
when the second of (marker, handshake) arrives. Measure the delay from
launch to handshake so it no longer depends on pipe ordering. Adds the
reviewer's repro as an Execute-level regression test plus a unit test.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
* feat(runtimes): add Qoder Cloud Agent managed remote execution
* fix(qoder): satisfy migration and localization CI contracts
* fix(qoder): bound execution and scope managed credentials
* fix(qoder): avoid upstream migration number collisions
* fix(qoder): gate issue creation and correlate tool results
* fix(qoder): synchronize guarded issue lifecycle transitions
* test(agent): publish Hermes holder identity atomically
* feat(agents): runtime-first create flow and cloud runtimes section for Qoder Cloud Agent
- Three-step create-agent flow (runtime -> configure -> done) with local,
remote and cloud sections; Qoder connect form inline in the runtime step
- Runtimes page regrouped into remote and cloud sections with Qoder card
- Generalize Qoder storage to cloud_agent_connection (migrations 586/587)
- Locales for en, zh-Hans, ja, ko, fr; desktop and web routes for done step
- Fix status-picker fixture owner_id and diagnostics route coverage after main merge
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(runtimes): unify runtimes page into machines and cloud services
- One Add menu replaces Add remote runtime, the duplicated Add a computer
and the web-only Cloud Runtime header button
- Group by machines / cloud services instead of local / remote / cloud, so
web and desktop share one structure; this device is pinned with a badge
- Cloud services use the machine row anatomy in a single ListGrid table;
unconnected Qoder is a row with Connect, coming-soon providers are rows
- Empty groups show the next step inline; search and online/offline filter
- Rename computer to machine across runtimes copy in all locales
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* test(runtimes): cover unified runtimes page; pin status and action tracks
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(runtimes): trim redundant descriptive copy on runtimes page
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(runtimes): keep row actions visible at narrow widths
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(runtimes): align Qoder add-menu item with other cloud services
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(runtimes): close the Qoder dialog after a first-time connection
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(agents): pick the runtime from one grouped list
The runtime step showed four kinds of cards, a comparison panel and
per-section descriptions. It is now one list in the runtimes-page layout:
machines (this one open, others folded to a row) and cloud services. An
unconnected Qoder opens the runtimes-page connect dialog in place and is
selected once it comes online.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(agents): show owners as 'you' and bare version numbers in the runtime list
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(qoder): disconnect action and dedicated Qoder runtime page
Disconnect removes the encrypted connection and the managed runtime, unbinding
its agents after the admin confirms them. The Qoder row now opens its own page
(status, agents, usage, configuration, pause/resume, disconnect) instead of the
machine layout; generic runtime delete and visibility refuse a managed runtime.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(agents): land on the done page after creating from step 2 on desktop
Desktop's location is the tab URL, which moves to /agents/new/done/<id>
while the configure page is still mounted. Its missing ?runtime= tripped
the skipped-step-1 redirect and replaced the tab back to /agents/new, so
Create looked like it did nothing. Only redirect on the manual route.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(agents): show only settings a Qoder agent can run with
The Qoder bridge fails every task whose agent carries a model, thinking or
speed override, env, custom args, custom skills, MCP config or connected
apps, and it runs issues only. The agent detail page still offered all of
those editors, so one save could break every later run.
For a qoder_cloud agent the page now keeps Instructions under Capabilities,
General / Access / QCA Agent under Settings, drops the model rows and the
header model chip, shows the QCA agent in the summary, and locks the
runtime. Other agents no longer see the Qoder runtime in their picker,
since moving onto it needs a QCA agent the server would otherwise reject.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(agents): hide conversation starters for Qoder agents
Starters only appear in a chat, and the Qoder bridge runs issues only.
The create flow already leaves them out for this runtime.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(runtimes): show the Qoder connection as cards with per-section editing
The Qoder runtime page rendered the whole connection as one always-editable
form. It now summarizes the connection, repositories and limits as read-only
cards; each section opens its own dialog and saves only that section.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(qoder): reuse a follow-up session only with the same repository and credential
Resolve repository credentials before deciding to continue a prior session,
and stamp each session with a fingerprint of its repositories, branches and
tokens. A follow-up continues only when the fingerprint still matches, so a
changed repository, branch or rotated/revoked token never rides into an old
session.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(qoder): keep a paused connection paused when its settings are saved
Saving repositories, limits or the connection no longer re-enables a paused
runtime. Resume goes through a new POST /qoder/resume.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(agents): drop settings a Qoder agent cannot run when creating one
A draft started on another runtime could carry hidden skills, model
overrides and starters into a Qoder agent that then failed every task. The
create request now drops them for Qoder, and the API rejects skills and
execution overrides on Qoder agents at save time.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* chore(db): regenerate sqlc models for cloud_agent_connection
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(runtimes): never list the Qoder runtime under Machines
When the server could not run Qoder (no MULTICA_VCS_SECRET_KEY /
MULTICA_QODER_STATE_DIR), the managed Qoder runtime fell back into the
Machines group as if it were a computer.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(qoder): drop MULTICA_QODER_STATE_DIR
The managed bridge keeps its run journal encrypted in cloud_agent_connection
and a database advisory lock already elects one supervisor, so the state
directory only held a redundant file lock and acted as a feature switch.
Qoder is now available whenever MULTICA_VCS_SECRET_KEY is set. The
standalone qoder-runtime command still uses its on-disk state_dir.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(qoder): keep the branch in the session fingerprint after a journal restore
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(agents): validate Qoder agents' effective settings on every write
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: 安辰 <[email protected]>
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(agent): support steering running Kiro turns via _session/steer
Kiro CLI 2.33.0 exposes an undocumented ACP extension, _session/steer,
that queues a message and injects it into the in-flight session/prompt at
the next tool or message boundary. Wire it up as the kiro backend's
Session.Supplement (active only while the prompt is in flight, and only
when the daemon negotiated task supplements), and gate kiro >= 2.33.0 in
SupportsTaskSupplement.
MUL-8126
Co-authored-by: multica-agent <[email protected]>
* fix(supplement): allow kiro in server-side steering whitelists
The capability handshake (StartAgentTaskWithSupplement), direct steering
(CreateTaskSupplement) and composer reply binding (BindCommentTaskSupplement)
each whitelist providers, and none included kiro, so a negotiated Kiro run
never got a capability row and steering stayed unreachable. Add kiro to all
three, regenerate sqlc, and cover kiro in the negotiation and both delivery
path tests. Also widen the kiro supplement test's readiness wait, which was
flaky under full-package parallel load.
MUL-8126
Co-authored-by: multica-agent <[email protected]>
* fix(supplement): allow grok in composer reply binding
BindCommentTaskSupplement was the only steering whitelist without grok, so
a reply from the normal composer to a running Grok turn fell back to the
next run even though direct steering worked. Add grok and cover it in the
composer binding test.
MUL-8126
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
* fix(agent): deliver the copilot prompt on stdin; name E2BIG start failures (MUL-8105)
Copilot inlined the whole task prompt as -p <prompt>, so a thread above Linux's 131,072-byte MAX_ARG_STRLEN died at fork/exec with E2BIG (#9362). The prompt now rides stdin, mirroring the OpenCode move in #6552. startOwnedProcessTree also wraps oversized-command-line failures in ErrArgvLimitExceeded with an actionable remedy.
Co-authored-by: multica-agent <[email protected]>
* docs(agent): update copilotBackend comment for stdin prompt delivery (MUL-8105)
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
* feat(agent): run Meta Muse Code as an MSP runtime
Muse Code speaks MSP (Muse Session Protocol) over `muse serve`: JSON-RPC
2.0 on stdio, like ACP, but with its own method vocabulary. A session opens
with session/start or session/resume, work is submitted with turn/start, and
assistant text, reasoning and tool calls arrive as transcript items.
The adapter maps those items onto the Backend contract, reads the terminal
turn outcome, and reports a notFound resume as ResumeRejected so the daemon
retries on a fresh session instead of losing continuity silently.
Approval is a session property, not a launch flag: `muse serve` has no
--yolo, and the CLI documents --yolo as TUI-only. A fresh session is created
with approvalMode=allowAll and a resumed session re-asserts it with
session/setApprovalMode, which is what lets a claimed task run without
waiting for a human. The container and per-task worktree remain the
isolation boundary, as they are for every other backend.
commandId must be UUIDv7 — Muse derives a turn id from it and rejects
anything else — so the adapter mints time-ordered ids rather than v4.
The model catalog comes from the session-free model/list query and the pick
is applied through session/start modelId.
* feat(runtime-profiles): allow muse as a protocol family
A custom runtime profile may only be based on a protocol family the daemon
supports, and the database enforces the same list. Widening both keeps the
create flow, the server validation and the CHECK constraint in lockstep with
agent.SupportedTypes.
* fix(muse): complete runtime integration and lifecycle handling (MUL-8057)
Co-authored-by: multica-agent <[email protected]>
* docs(muse): document runtime setup and support (MUL-8057)
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: yushen <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* MUL-8070: feat(cursor): support live-run steering via Cursor hooks
Cursor's headless --print mode has no mid-turn input channel, but its
postToolUse/postToolUseFailure hooks can return additional_context that
reaches the model in the same turn. A steerable Cursor run now writes a
managed .cursor/hooks.json whose command is the multica binary in a hidden
relay mode; the relay forwards the hook event to a token-protected loopback
endpoint owned by the run, which hands back pending guidance tagged with a
per-run nonce. The prompt declares that tag so the model treats the context
as genuine user guidance instead of a prompt injection.
Steering is offered only for daemon-managed workdirs on Unix with cursor-agent
2026.10.14+, and the server allowlists cursor for capability negotiation,
supplement creation and composer binding.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* MUL-8070: fix(cursor): confirm steering only on Cursor's receipt and gate on installable hooks
- Count hook-delivered guidance as delivered only when the tool call's
completed stream event echoes it in hookAdditionalContexts. Guidance
Cursor dropped for the same call is retried at the next tool boundary;
guidance without a receipt fails when the turn ends.
- Offer steering only when the workdir can carry the managed hooks.json,
and fail supplements immediately if installation still fails after
negotiation.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(agent): fail codebuddy runs that emit a top-level error event
CodeBuddy's startup-failure shape is one top-level {"type":"error"}
line followed by a process that stays alive and silent: with an
inherited SERVER__PORT (a user-scope config shared by several tools),
a second concurrent run of the same agent fails to listen, prints
{"type":"error","error":"listen EADDRINUSE: ..."} and never emits
anything again. The parser had no branch for that event type, so the
run sat at "waiting for agent response" until the idle watchdog
stopped it - 19 of 48 runs hung this way on the reporter's machine.
- type=error is now fatal when no terminal result has been seen: the
CLI's own message becomes the run error and the hung process is torn
down immediately instead of idling. A result already seen keeps
governing the outcome, so a late informational error line cannot
flip a finished run.
- buildCodebuddyEnv now also forces SERVER__PORT=0 (OS-assigned port
per run, so concurrent runs never share one) and
CODEBUDDY_BASH_AUTO_BACKGROUND_DISABLED=1 (CodeBuddy's PowerShell
tool moves timed-out commands to the background and only honors that
variable). Both are appended last like the existing background-task
disable so a caller-provided value cannot outvote them.
Fixes#9200
Generated-by: GLM-5.3-Flash (ZCode)
* fix(agent): keep CodeBuddy running after recoverable errors
MUL-7983
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Yushen <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(daemon): don't count host sleep as agent silence in idle watchdog (#9024)
The idle watchdog stamped activity in wall-clock unix nanos, so a laptop
that slept past the idle window woke up with every in-flight run already
over budget and force-stopped them all on the first tick.
Detect host suspend from the wall-clock gap between watchdog ticks and
credit that time back to lastActivityAt before each check, clamped to now.
Works the same on macOS/Linux (monotonic clock pauses in sleep) and
Windows (monotonic clock keeps counting), and covers the tool-in-flight
budget too since both read the same stamp.
MUL-7851
Co-authored-by: multica-agent <[email protected]>
* fix(daemon): measure idle watchdog on an awake-only clock (#9024)
Review on #9201 showed the tick-gap heuristic still charges sleep to the
agent: a sleep shorter than one tick is never credited, and when the
overdue tick fires on wake (Windows) only gap−tick is credited.
Replace it with pkg/awakeclock, a clock that only advances while the host
is awake: Go's monotonic clock on Linux/macOS (already stops in suspend),
minus host sleep on Windows (GetTickCount64 − QueryUnbiasedInterruptTime).
lastActivityAt, the drain loop stamp, and Cursor's native tool activity
(Session.ToolActivity now returns an awakeclock reading) all share it.
Regression tests cover both review boundaries plus a long sleep, each with
a wall-clock control that proves the scenario reproduces the bug.
MUL-7851
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
* fix(agent): deliver MCP servers to Pi tasks
Pi skipped untrusted project config in JSON mode, and Multica never wrote .pi/mcp.json, so composed MCP servers never reached the runtime.
Co-authored-by: Cursor <[email protected]>
* fix(agent): hand Pi its MCP servers through a temporary extension
Writing .pi/mcp.json and passing --approve trusted the whole project directory and failed when that file already existed. An explicit --extension loads the servers without project trust.
Co-authored-by: Cursor <[email protected]>
* docs: mark Pi as reading agent MCP configuration
The provider comparison still said Pi skipped the MCP field. Pi now receives that configuration.
Co-authored-by: Cursor <[email protected]>
* fix(agent): harden Pi MCP registration
Co-authored-by: multica-agent <[email protected]>
* fix(agent): allow empty Pi MCP configuration
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
Co-authored-by: Yushen <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(agent): fail kimi runs whose wire log records a failed turn
kimi's ACP adapter can answer session/prompt successfully while its own
turn failed: the session wire log carries a
{"type":"agent.turn.ended","outcome":"failed",...} record (e.g. a bare
"OAuthConnectionError: OAuth request to https://auth.kimi.com/api/oauth/
token failed" that matches none of the stderr sniffer's terminal
signatures). The run then kept status=completed and published the
intermediate narration as a successfully delivered result, so the daemon
sent no failure signal and retry/continuation policy never saw the
failure (#9054).
Fix: after the run, scan the session wire logs - the same source,
turn-boundary rules, and tolerant-line policy as the usage scan
(scanKimiSessionUsage) - for the main agent's failed agent.turn.ended
record, and promote completed->failed carrying kimi's sanitized
diagnostic. Only the structured outcome verdict counts, so conversation
text echoing failure words cannot flip a healthy run; a delegated
agent's failed turn stays that agent's own task path; timeout, abort,
and already-failed runs keep their own terminal semantics.
Complementary to #8573's zero-output guard: that incident shape had 26
tool calls and nonempty narration, which a zero-output guard
deliberately preserves.
Fixes#9054
Generated-by: GLM-5.3-Flash (ZCode)
* fix(kimi): sanitize the wire diagnostic and scan past oversized records
Review fixes for #9057 (CHANGES_REQUESTED, both repros offline with a
fake ACP; no live provider requests):
P1: kimiWireTurnFailure copied the record's errorMessage into the run's
diagnostic unsanitized. The failure path persists Result.Error, and the
shared redact.Text does not redact a quoted JSON credential shape
(request={"api_key":...}) — the sanitized diagnostic the PR described
was not actually provided. The diagnostic now goes through the same
sanitizeAgentDiagnostic pass as every child-process diagnostic before
it reaches Result.Error.
P2: the scan loop used newAgentStreamScanner, whose bufio.Scanner ends
its scan for good at a line beyond agentStreamMaxLineBytes, and the
loop never checked scanner.Err() — a valid but oversized context.append
record ahead of the terminal event hid the failed outcome and kept the
run completed. kimiWireTurnFailure now reads through
readAgentStreamLine, which consumes and discards an over-bound record
whole and keeps the later records readable, and ends the walk
explicitly on any other read failure (keeping what was read, the same
best-effort policy as a truncated tail).
Regressions, all red before the fix and green after:
- TestScanKimiMainTurnFailureSanitizesWireDiagnostic (P1)
- TestScanKimiMainTurnFailureReadsPastOversizedRecord (P2, scan level)
- TestKimiBackendWireTurnFailurePastOversizedRecord (P2, backend e2e
with the fake ACP: red as got "completed" (error=""), the review's
exact repro shape)
- TestReadAgentStreamLineSkipsOversizedAndContinues /
TestReadAgentStreamLineReturnsUnterminatedTail (helper contract; the
first caught a boundary miss where the cap was crossed only on the
final buffer chunk)
Generated-by: GLM-5.3-Flash (ZCode)
* feat(issues): add multi_text and multi_url custom property types
Workspaces could only hold list-shaped values as preset option lists
(multi_select) or member references (multi_actor). There was no way to
keep a list of free-form strings or links on an issue, so teams with
"related docs / spec links / aliases" needs had to either cram them into
one text field (no structure, nothing clickable) or burn one select
option per link.
This adds two property types:
- multi_text: an ordered list of free-form text entries
- multi_url: an ordered list of http(s) URLs
Each entry is validated exactly like a single text / url value
(length caps, http(s)-only for URLs), duplicates are dropped, insertion
order is preserved, and values are capped at 20 entries to respect the
16KB properties-bag budget. Values ride the existing jsonb bag, so the
jsonb_path_ops GIN index, the @> containment filter, and the client
value schema all keep working unchanged.
Filtering matches any single list element exactly (equality) plus "No
value" — the containment form the filter compiler already emits for
array elements, so no new server predicate is needed. List types
deliberately get no sort order and cannot group, like the other
multi-value types.
On the web, list values render as chips; multi_url chips are clickable
and open in a new tab, and the editor popover offers a per-entry open
button, so several related documents or links stay directly reachable
from the issue. The filter menu reuses the scalar input (equality-only
plus "No value"). CLI: comma-separated --value form for both types,
element-equality --property filtering, and display_values rows.
Server-side allowlist widened by migration 500 (check constraint
re-added NOT VALID then validated, same pattern as migration 341).
* fix(properties): address list property review nits
Co-authored-by: multica-agent <[email protected]>
* fix(properties): scope list drafts to the edited issue
Co-authored-by: multica-agent <[email protected]>
* test(agent): avoid fork races when writing zeroclaw fixtures
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
antigravityCompletedDespiteTrailingNetworkError exists to preserve a finished
answer when agy fails a follow-up round trip after producing it, but it
recognised exactly one error spelling. agy also reports an exhausted retry as
`API error (attempt N): request failed: Post "...": EOF`, which fell through and
discarded a response already sitting in result.response with its agent_response
step at DONE.
Match the transport cause rather than one sentence. The classifier stays narrow
on purpose: reportTaskResult documents failing closed so a provider 429 or
out-of-credit run can never be shown as Completed, and quota, capacity,
overload, policy and auth rejections all arrive as an HTTP response rather than
a failed round trip. agy's own `request failed:` prefix is excluded too — only
one spelling has been observed and nothing rules out agy reusing it for a
status error.
* fix(pi): fail a run whose agent_end reports an error
Pi 0.74.0 and earlier report a failure raised outside a model stream,
such as a provider API key command that fails, only on agent_end. The
stream carries no turn_end, error or auto_retry_end event, and print
mode exits 0, so the adapter recorded the run as completed with no
output. Pi resolves a command-backed API key again on every model
request, so the same path can also end a run part-way, after tool work.
Decode agent_end and record its last assistant message's stopReason
"error" through the existing turn-error guard, with errorMessage as the
reason. An automatic retry or a later successful turn still clears it,
as with turn_end.
* fix(pi): fail a run whose event stream overflows the scanner
The pi backend never checked scanner.Err(). A line over the shared 32 MiB bound ended the read loop, and the run then either recorded completed (when the rest of the line fit in the pipe buffer and pi exited) or hung until its timeout, with pi blocked writing to the unread pipe. agent_end repeats every message of the run, so on a long run it is the line most likely to overflow, and it can carry the run's terminal state.
Close stdout on a scanner error so pi cannot block on the full pipe, and fail the run with "pi stdout read error", keeping any provider error pi already reported. This matches the other stream backends.
* fix(agent): pass Hermes custom args before the acp subcommand
Hermes only accepts its global flags (--provider, --yolo, -m, ...) ahead
of a subcommand, so the daemon's `hermes acp <custom args>` exited with
an argparse usage error before the ACP handshake and every run of a
Hermes agent with custom_args failed. Assemble `<prefix> <custom args>
acp` instead, keep the profile resolver and overlay stripping on the
same argv, and drop a trailing custom arg left without its value so it
cannot capture `acp` and hang the task in interactive chat.
Fixes#8878 (MUL-7748)
Co-authored-by: multica-agent <[email protected]>
* fix(agent): keep acp's own flags after the Hermes subcommand
Hermes' acp subparser declares --accept-hooks, --version, --check,
--setup, --setup-browser and --yes/-y, and argparse only accepts them
after the subcommand: moving them ahead of `acp` turned an agent with
custom_args ["--yes"], which launched before, into a usage error.
Lay custom args out around `acp` instead — global flags (with their
values) before it, acp's own flags after it — and map profile
stripping back through that layout so custom args keep their
configured order.
Co-authored-by: multica-agent <[email protected]>
* fix(agent): resolve Hermes flag abbreviations like argparse
Hermes keeps argparse's allow_abbrev, so custom args must be classified
the way its parsers resolve them, not by string equality. Behind `acp`
a `--y` is `--yes`; moved in front, the root parser reads it as
`--yolo` and turns off dangerous-command approval, while `--ye` went
from valid to a usage error. Keep every token the acp subparser would
take as its own option (exact, abbreviated or ambiguous) behind the
subcommand, and treat abbreviated value flags (`--prov`) as value flags
when pairing and when guarding against a bare flag capturing `acp`.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
Since the fallback catalogs only feed the model picker (#8811), listing
current models there is cheap. Add the models the installed CLIs now
advertise but the static lists were missing:
- Claude: claude-opus-5-5 (Claude Code 2.1.280 list_models default)
- Codex: gpt-6-sol (low..ultra) and gpt-6-luna (low..max), both default
medium, from codex-cli 0.155.1 `debug models`; live discovery labels
them "GPT-6 Sol" / "GPT-6 Luna" like GPT-6 Astra
MUL-7691
Co-authored-by: multica-agent <[email protected]>
When model discovery fails, providers substitute a static catalog so the
picker stays usable. The daemon then validated saved thinking levels and
service tiers against that stand-in, silently dropping values the static
list did not know about — which forced hand-maintained model and per-model
effort tables to chase every new release.
Only a verified catalog (discovered, non-empty) may now reject, rewrite, or
drop a saved model, thinking level, or service tier. Fallback and empty
catalogs pass the saved values through to the CLI for every provider. The
checks that describe the binary rather than its models stay: a Claude CLI
without --effort (or without the saved value in its --effort list) still
drops the level, and Codex's explicit standard tier still follows the CLI
version.
- Catalog.Verified / Catalog.CLIThinkingLevels; missingFromFallbackCatalog removed
- claudeModelEffortAllow removed: the fallback picker offers the --help superset
- ModelKnownIncompatibleWithProvider no longer consults static lists (prefix-only, same result)
MUL-7691
Co-authored-by: multica-agent <[email protected]>
* feat(grok): support steering active Grok Build runs
* test(grok): cover steering before first agent output
* fix(grok): bound interject acknowledgement time
* fix(cursor): own Windows background shells by launch Job membership (MUL-7417)
captureCursorBackgroundProcess on Windows walked a machine-wide PID->PPID
snapshot to prove a reported shell descends from the launch and to find
its pre-existing children. Windows keeps a child's recorded PPID after
the parent exits and reuses the PID, so the walk could reach a process
owned by someone else; opening it failed with 'Access is denied' or
'not an owned descendant' and the whole capture aborted. The daemon then
fell back to the idle watchdog and a Cursor run that later went quiet
took the 10-minute watchdog path instead of completing. In CI this was
TestCursorBackgroundLifecycle/multiple failing about 2 runs in 43.
Take candidates from the launch's own Job Object instead
(QueryInformationJobObject / JobObjectBasicProcessIdList). Descendants
inherit the Job at creation and cannot leave it, so membership is the
ownership proof: the parent-chain walk goes away, the PPID snapshot is
used only to attach a member's pre-existing children, and a member that
exits or is reused between listing and open is skipped instead of
failing the capture. Creation ordering still tells a real child from
one that points at a reused parent PID.
Restore TestCaptureCursorBackgroundLateDescendant on Windows; it was
excluded for exactly this race.
ci.yml runs the Windows Cursor step with -count=20 on this PR only to
show the race is gone; it is reverted before merge.
Co-authored-by: multica-agent <[email protected]>
* ci: restore -count=1 on the Windows Cursor background step
The -count=20 stress run on this PR passed (run 35580440594, windows-execenv);
the per-merge step goes back to a single pass.
Co-authored-by: multica-agent <[email protected]>
* fix(cursor): complete the Job member list and refuse unopenable live members (MUL-7417)
Review follow-ups on #8638:
- cursorJobProcessIDs grew its buffer only on ERROR_MORE_DATA. The kernel
can also return success with NumberOfProcessIdsInList short of
NumberOfAssignedProcesses, which a Job over the initial capacity or one
growing during the query would hit; the short list then passed for the
membership and could reject the target or leave children out. Retry
until the counts agree, fail explicitly if they never do, and make the
initial capacity a package var so a three-process tree exercises the
growth path in a test.
- The child walk skipped every open failure as 'exited or reused'. A live
member whose DACL denies PROCESS_SET_QUOTA|PROCESS_TERMINATE also fails
to open, and skipping it left the capture reporting success with that
member outside the tool Job. Skip only PIDs proven to carry no member
(handle outside the launch Job, or no longer listed after a re-query,
confirmed by one more open); otherwise fail the capture so the caller
takes its existing watchdog fallback. OpenProcess goes through a
package var so a test can make a live member unopenable.
Windows-only tests cover both, plus the plain claim-and-terminate path
for a shell with a pre-existing child.
Co-authored-by: multica-agent <[email protected]>
* test(cursor): name the Job list growth test so the Windows CI filter runs it
The windows-execenv step selects 'TestCursorBackground|TestCaptureCursorBackground';
TestCursorJobProcessIDsGrowsPastInitialCapacity matched neither and never ran.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>