ADR 0022: Declarative model policy

Field Value
Status Accepted
Date 2026-06-01
Deciders Project maintainers
Supersedes
Superseded by
Related ADRs ADR-0011 (Zod schema library), ADR-0008 (no auto-anything)

Summary

aidokit gains an optional, purely declarative model policy: .aidokit/model-policy.json. A new @aidokit/core Zod schema (ModelPolicySchema) describes it, loadModelPolicy(projectRoot) reads and validates it, and an opt-in aidokit doctor --model-policy check reports its status. The file maps a task type to a recommended model. aidokit recommends; it never selects, routes, or invokes a model. There is no orchestration, no routing layer, and zero LLM calls.

The file is not emitted at init — emitting it would change the byte-compare dogfood gate fixtures. It is a runtime artifact the user authors; aidokit only validates and surfaces it.


Context

Issue #7 asked for model routing inside the emitted workflow. True routing — aidokit choosing and dispatching a model per task — is out of scope: it implies LLM traffic and orchestration, both of which are permanent non-goals (aidokit makes zero LLM calls; the user's own CLI handles all model traffic).

The legitimate, in-scope kernel of #7 is recording a project's preferred model per task type so the user's agent runtime can consult it. That is a declarative data file plus validation — deterministic, hermetic, no network.

The shape mirrors existing data-driven catalogs (ADR-0007): a typed Zod schema, a one-file edit to change, validated at load time.


Decision

1. Schema in @aidokit/core

const ModelPolicyEntrySchema = z
  .object({
    model: z.string().min(1),
    reasoning: z.string().min(1).optional(),
    maxTokens: z.number().int().positive().optional(),
  })
  .strict();

const ModelPolicySchema = z
  .object({
    version: z.literal(1),
    policies: z.record(z.string().min(1), ModelPolicyEntrySchema),
    default: ModelPolicyEntrySchema.optional(),
  })
  .strict();

.strict() follows the catalog convention — unknown fields are rejected to catch typos at load time.

2. Loader + validator

3. Opt-in doctor check

A DoctorCheckSource (model-policy, requiresFlag: 'modelPolicy', flag --model-policy):

Opt-in so projects that don't use a policy file see no noise.

4. Not emitted at init

aidokit init does not write the file. The byte-compare dogfood gate (CLAUDE.md §10) compares emitted trees byte-for-byte against committed fixtures; emitting a new file would require regenerating every fixture for no behavioural gain. The policy is a runtime convenience, not part of the scaffolded surface.


Consequences

Positive

Negative

Neutral


Alternatives considered

Alternative 1: True model routing inside the emitted workflow

Rejected — implies LLM calls and orchestration from aidokit itself, both permanent non-goals.

Alternative 2: Emit a starter model-policy.json at init

Rejected — changes byte-compare fixtures across all adapters for no behavioural gain. The file is optional and user-authored.


Validation

This decision is validated when:


References

Internal