Skip to content

rust mode: the module's on-disk model-chain fallback reads keys the plugin schema doesn't declare, so it is empty on every schema-valid config (and dreamer tasks read historian keys) #505

Description

@iceteaSA

Low severity and latent today. Filing because the seam is misleading, not because anything is broken right now.

All refs are against master 1712536e7.

What the fallback reads

When a request arrives without a model chain, the module uses binding.config.model_chain (lib.rs:11344, requested_model_chain.unwrap_or(&binding.config.model_chain)). That chain is built in config.rs:486-530 from exactly four JSON pointers:

  • /historian/module_model + /historian/module_fallback_models, or failing those
  • /historian/model + /historian/fallback_models

Why it's always empty on a schema-valid config

assets/magic-context.schema.json declares none of those four keys. historian's top-level properties are color, description, disable, disallowed_tools, maxSteps, maxTokens, mode, omp, opencode, permission, pi, prompt, temperature, tools, top_p, two_pass. The model lives only in the per-harness blocks: historian.opencode.model, .pi.model, .omp.model.

So a config written to the schema gives the module an empty on-disk chain. On a live per-harness config here, all four pointers evaluate to null. module_model isn't in the user-facing docs or CONFIGURATION.md either; I only found it in internal design docs and in the config.rs tests. Because additionalProperties is unset, the schema tolerates the key without declaring it.

Why nothing breaks today

TS hosts always send a non-empty chain, so the fallback is never reached:

  • historian: rust-mode-transform.ts:2642 sends historian_model_chain: resolvedHistorianModelChain(deps)
  • classify: classify.ts:500 sends model_chain whenever it's non-empty

It becomes live if a host ever sends an empty chain, or if a module consumer depends on the on-disk config. Then the request fails with "classify producer has no usable model" (lib.rs:11429) instead of using the configured models. I read that consequence from source. I have not exercised a no-chain request end to end.

Second half: dreamer tasks read the historian's keys

dreamer.run_task falls back to the same binding.config.model_chain, which is built only from /historian/*. A dreamer config (dreamer.opencode.model etc.) is never consulted on this path. If someone gives the dreamer a different chain from the historian and relies on the module fallback, the dreamer runs the historian's models, with nothing in the logs to show it.

Options, not prescribing

  1. Read the per-harness layout the schema actually defines (historian.<harness>.model, keyed by the binding's harness), and read dreamer.* for dreamer tasks.
  2. If module_model / module_fallback_models is the intended interface for non-TS consumers (the comment at config.rs:487-495 suggests it separates plugin-namespace ids from runner catalog ids), declare both in the schema and document them so users can find them.
  3. At minimum, log once when the fallback resolves to an empty chain, so it doesn't fail as "no usable model" without saying why.

Verification status

  • Pointer set and schema contents: read on master 1712536e7.
  • Empty result on a real per-harness config: measured.
  • Host-always-sends: read at both call sites.
  • Empty-chain failure path: read, not exercised.

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