Skip to content

Epic: embed Mistral Vibe as a hosted ACP agent (vibe-acp) — successor to the vibe-direct wire emulation #1709

Closed

Description

@dezren39

Direction

Drew (2026-11-05): "eventually vibe direct needs to be embedded per that project" — Vibe's end state in our stack is embedded the way Mistral's own project intends: the real vibe agent hosted by the fork, not wire-emulated by it.

Current state — the shim

vibe-direct today is a wire-protocol provider in packages/ai (packages/ai/src/providers/vibe.ts + vibe-auth.ts, landed in #1653):

  • Reuses the MistralChat route against https://api.mistral.ai/v1 (the same wire the binary uses), rebranded to provider id vibe, with Vibe's model presets and request defaults (temperature 1.0, reasoning_effort "high").
  • Stamps requests with the binary's attribution (metadata.call_source = "vibe_code").
  • Resolves the credential the way the binary does: config → env MISTRAL_API_KEY → ~/.vibe/.env → OS keyring (service ai.mistral.vibe).
  • The vibe provider: native wire-protocol emulation of the Mistral Vibe binary (no ACP host, no subprocess) #1653 commit says it plainly: "No vibe subprocess, no ACP host." It emulates the binary's requests; the binary's agent (tools, skills, subagents, session semantics) is not in the loop.

Deployed route (developing-today/code .opencode/opencode.jsonc): model mistral-vibe-cli-latest, providers ["vibe-direct", "mistral"], vibe-direct attempted first.

Vibe's intended embedding surface (primary sources)

Mistral documents ACP as the embedding path:

Installed on the laptop (vibe 3.1.0, verified 2026-11-05): three entrypoints —

  • vibe — interactive CLI + headless programmatic mode (-p/--prompt, --output json|streaming, --output-dir writes export.json with token usage/cost).
  • vibe-acp — "Run Mistral Vibe in ACP mode": JSON-RPC 2.0 over stdio, protocol v1.
  • vibe-app-server — an app-server surface ("Unified Harness connections", --multiplex) — noted for the design record; ACP is the documented, registry-published path and the one this epic builds on.

What the host provides over ACP (observed live, see spike): the client spawns vibe-acp, drives initialize → session/new → session/prompt; the agent streams session/update (message chunks, tool calls, usage updates), delegates approvals back via session/request_permission, and exposes session modes (ask / accept-edits / auto-approve) and config options (model, reasoning effort) for the host to present and set. Session state (journal, resume) stays the agent's, under ~/.vibe.

Spike result (2026-11-05, laptop amd-6) — PROVEN

A minimal ACP host (211-line Python client, stdlib only) drove one real end-to-end turn through vibe-acp on the Vibe plan credential:

  • initialize → protocolVersion 1, agent @mistralai/mistral-vibe 3.1.0; capabilities: loadSession, prompt image + embeddedContext, session list/fork/close. No authenticate call needed — the keyring credential was already live.
  • session/new → session 48475654-f48c-f816-04aa-56fd06364bd4; model config option mistral-medium-3.5 (the model behind alias mistral-vibe-cli-latest), effort high, default mode accept-edits.
  • session/prompt (instructed to run echo VIBE-EMBED-SPIKE-OK via its bash tool) → tool call bash: echo VIBE-EMBED-SPIKE-OK, reply exactly MARKER=VIBE-EMBED-SPIKE-OK, stopReason: end_turn, usage: 16,864 total tokens (16,643 in / 221 out, 3,072 cached read) in the prompt response, plus streaming usage_update notifications.
  • Artifacts on the laptop: ~/work/spikes/vibe-acp-embed-2026-11-05/ (acp_client.py, transcript.jsonl, summary.json, run.log).

The embedded contract works tonight, unmodified, on the plan credential — no console cookies involved.

Target architecture

The fork hosts vibe-acp as an embedded agent runtime: a session in the fork maps to an ACP session in a supervised vibe-acp process; prompts/streaming/tool calls flow over ACP; host approvals answer session/request_permission; modes + model/effort surface as session config; usage lands in the quota pipeline with call_source=vibe_code parity. The embedded route registers switchboard-style beside vibe-direct (definition present, enablement a flag) and the shim retires only after parity acceptance.

Auth rule (Drew, standing): the embedded path rides the keyring/OAuth credential with its own refresh — never a browser console cookie. Anything browser-born in any provider must have auto-renew built in; Vibe's keyring credential already satisfies this and the design must keep it that way.

Children

  • ACP client runtime: host vibe-acp (lifecycle, streaming, session mapping)
  • Permission + mode bridge (session/request_permission → fork approvals; modes/model/effort config options)
  • Embedded auth: keyring credential lifecycle + refresh for the hosted process
  • Usage attribution: ACP usage → quota gatherer, vibe_code parity (adjacent: quota: mistral browser-dependent usage read (Vibe plan) — producer + watchdog adapter #1 openchamber#1630)
  • Switchboard slice: embedded route disabled-by-default beside vibe-direct; parity acceptance + cutover criteria

Design note: ~/workspace/scratch/vibe-embed-2026-11-05/design.md (fleet scratch, 2026-11-05).

Activity

  1. dezren39 commented on Nov 5, 2026

    @dezren39
    Author

    Children filed:

    Spike evidence summary and primary-source citations are in the epic body; full design note in fleet scratch (~/workspace/scratch/vibe-embed-2026-11-05/design.md).

  2. dezren39 commented on Nov 5, 2026

    @dezren39
    Author

    Spike update (same night, 2026-11-05): the permission round-trip is now PROVEN, and two records need reconciling.

    Run 2 — ask mode, safe command. session/set_mode → ask accepted. Echo-marker turn: session 4795ea5b-15fb-2b5e-8880-9ababaf20069, 0 permission requests, 16,837 tokens. (Mode list also includes a fourth mode, smart-approve.)

    Run 3 — ask mode, risky command. Create + rm a file from an untrusted cwd: the agent issued exactly one session/request_permission with options allow_once / allow_always (session) / allow_always_permanent / reject_once (Deny). Host answered allow_always; the command executed and verified its own deletion; reply DONE-RM-TEST; 16,791 tokens. Approval delegation is risk-classified: echo never asks (any mode seen), rm asks. Deny path still unproven — stays in #1711.

    Journal finding. The agent's session journal lives at ~/.vibe/logs/session/unified/<sessionId>/generations/<gen>/. For spike session 48475654…, runtime-state.json records session_metadata.active_model = mistral-large-4-0-3 — while the ACP config option presents the model as mistral-medium-3.5 ("mistral-vibe-cli-latest"). The preset↔backing-model mapping needs pinning before #1713 keys attribution on a name.

    Design note updated: ~/workspace/scratch/vibe-embed-2026-11-05/design.md (§3 follow-up runs + journal, §7 open questions).

  3. dezren39 commented on Nov 5, 2026

    @dezren39
    Author

    CLOSED — direction corrected by Drew (2026-11-05 01:13 CST): 'no we emulate and don't actually need vibe at all except maybe for certain login calls. same for all direct.' The architecture for vibe-direct and all *-direct providers is EMULATION of the first-party wire, not ACP embedding. The spike evidence in this epic stands as protocol research. Doctrine + vibe-direct compliance record + the equivalent audit for the other direct providers: #1717.

  4. dezren39 commented on Nov 5, 2026

    @dezren39
    Author

    Requirement handed over from the quota single-source program (developing-today/code#6125, Drew's order 2026-11-05 01:25 CST): vibe-direct must own a QUOTA function.

    Drew: "if other direct lanes don't handle all functions including quota then fix them too tonight don't wait. renewal, everything. dashboard and all other sources should rely on opencode quota subcommand only, 1 single source for quota info."

    Quota is a provider function like any other, and under the emulation doctrine (Drew, 2026-11-05 01:13) it is implemented IN the provider, emulating the first-party surface. For vibe-direct that surface is the Mistral admin console budget read — the figures exist and were verified live tonight:

    • Endpoint: GET admin.mistral.ai/api/local-trpc/billing.budget → {api_budget: {usage_percentage: 3.157, …}, vibe_budget: {usage_percentage: 0.0246, …}, reset_at: 2026-12-01} against budgets API $15/mo, Vibe $150/mo.
    • Console truth (admin.mistral.ai/subscription, "INCLUDED MONTHLY ALLOWANCE"): Included API usage $0.47 of $15 · Included Vibe Code usage $0.04 of $150, both reset the 1st of the month.
    • Auth is the Ory session cookie (.mistral.ai) ONLY — the keyring API key gets a 302 on this endpoint. Cookie expires 2027-02-02. Its renewal classification belongs to the provider-auth auto-renewal registry (Drew's standing rule, 2026-11-05 01:10): record how this cookie renews before it expires; do not let the quota function die silently with it.

    Acceptance: vibe-direct's quota implementation returns both budgets as windows in the shared result shape ({usedPercent, resetAt, valueLabel with the dollar figures}), and opencode quota --json (the new subcommand, tracked on code#6125) reports vibe-direct/mistral figures matching the console truth above (small drift from new usage is fine). The gatherer-side console-fetch design previously recorded on openchamber#1630 is superseded — the fetch lives in the provider now, never in the gatherer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions