Skip to content

condense ignores the agent provider: the reflector is wired to a global command with a hardcoded Claude model and Claude-only flags, so an all-OpenCode floor still burns a hidden Claude session #596

Description

@FinFalkenauge

Summary

The memory reflector (MemoryReflector / condense) does not use the agent's provider. It is wired to a single global command with a hardcoded claude fallback, a hardcoded Claude model, and Claude-CLI-specific flags. On a floor where every agent runs on OpenCode — which is the whole point of deploying OpenCode — the condense pass is still a hidden Claude session that consumes the Claude subscription.

So the one subsystem that was supposed to be provider-agnostic is the one that still requires Claude.

Evidence (0.4.6, Linux x64, read from app.asar)

1. The reflector is constructed with a global command, not a per-agent provider:

const reflector = new MemoryReflector(
  () => readConfig().harnessHome,
  () => readConfig().defaultCommand ?? "claude",   // <- single global value
  () => memory.env(),
  reflectSettings,
  (event) => { ... }
);

2. The constructor's own doc comment says it is Claude:

 * @param getCommand   The base `claude` command (only its binary name is used).

3. The model is hardcoded, not configurable:

const CONDENSE_MODEL = "claude-haiku-4-5";

4. The hidden call builds Claude-CLI flags regardless of which binary was resolved:

const args = [
  "--model", opts.model,          // claude-haiku-4-5
  "--permission-mode", "bypassPermissions",
  "--disallowedTools", ...disallowed
];

--permission-mode / --disallowedTools are Claude Code flags. They are passed whether the resolved binary is claude or something else.

5. The fallback is baked into the runner too:

const binary = (opts.command || "claude").trim().split(/\s+/)[0] || "claude";
const exe = resolveCommand(binary);

6. There is no provider/model control in the reflect settings at all — reflectSettings() exposes only enabled, intervalMs, byteTriggerPct, sectionTrigger, recentKeep, minBytes. Grepping the bundle for condenseProvider, condenseCommand, reflectCommand, memoryCommand returns zero occurrences.

Our situation

Config:

"defaultCommand": "claude",
"godProvider":   "opencode",
"godModel":      "local/deepseek-v4.1-flash:cloud"

Every non-archived agent has provider: opencode. Live process check: 5–7 opencode --model … processes, zero claude processes. Yet condense runs a hidden Claude session on a ~30-minute timer, and has been failing hourly:

condense-abort  reason=summarize-failed
detail: Error: condense: response contained no parseable JSON

Why it fails (the mechanism)

summarize() requires strict JSON:

Produce STRICT JSON: {"condensed": "<text>", "hoist": ["<line>", ...]}.

When the Claude subscription hits its weekly limit, claude -p returns plain text:

You've hit your weekly limit · resets Sep 25, 6am (UTC)

That is not JSON, so parseSummary() returns nothing and the call throws. Verified directly — both claude-haiku-4-5 and claude-sonnet-5 return the limit line (subscription pro).

Note: the limit is the symptom. Even with quota available, this path still (a) burns Claude usage on a floor that was deliberately moved to OpenCode, and (b) spends a hidden invocation every cycle it cannot succeed.

Why this matters

  1. It defeats the purpose of the OpenCode deployment. Operators move to OpenCode/self-hosted models specifically to get off the Claude subscription. Condensation is the one piece that never made the move, and it is the piece that runs unattended on a timer.
  2. It fails silently. Nothing in fleet.json distinguishes "condensing is failing" from "condensing is idle", and the abort shares its log line with the unrelated JSON-escaping failure in Memory condensation aborts ~80% of the time: the payload has to survive JSON string escaping #459 — three distinct causes collapse into no parseable JSON.
  3. It is not configurable away. With defaultCommand: "claude" the binary is Claude; pointing defaultCommand at opencode does not help either, because the model (claude-haiku-4-5) and the flags (--permission-mode, --disallowedTools) are Claude-specific. There is no supported way to say "condense with my configured provider".

Expected

  • The reflector should resolve the provider of the agent being condensed (or at minimum a configurable reflectProvider / reflectModel / reflectCommand), and
  • build the arguments from that provider's preset (providerPreset() already exists in the bundle and is used for spawning) instead of hardcoding Claude flags, and
  • detect a limit/quota response and record it as its own reason (e.g. rate-limited with the reset time) rather than retrying blind.

Related

Happy to test a patch — we have a reproducible floor and log evidence.

Activity

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