🐾 fix: resolve registry capabilities per agent, not once globally #6

Merged
r merged 1 commits from fix/registry-per-agent-capabilities into main 2026-08-03 16:29:44 +00:00
Owner

What

_build_registry computed a single capabilities dict from the global default_model and attached it to every agent entry, ignoring the per-agent model: / model_capabilities: overrides that server.py actually applies at startup (_start_agent overwrites AgentConfig.model for every agent in the module). Any deployment using an override would advertise the wrong model to Daedalus.

Second, smaller bug: when model_capabilities is absent but default_model is set, the registry published context_window: null / max_output_tokens: null — while _register_one_model registered that same model with an effective 131072 / 16384. The registry now advertises what was actually registered.

Changes

  • registry.py: _load_model_capabilities()_load_fastagent_defaults() (returns the raw globals) plus a new _resolve_capabilities(agent, default_model, default_capabilities) called inside the agent loop.
  • server.py: 131072 / 16384 hoisted to DEFAULT_CONTEXT_WINDOW / DEFAULT_MAX_OUTPUT_TOKENS and imported by registry.py, so the two cannot drift — that agreement is the whole point of the fix. No import cycle: server.py imports pallas.registry lazily inside a function, never at module scope.
  • tests/test_registry.py: new, 9 tests — first coverage for registry.py.
  • Docs: the agents.yaml field table omitted agents.<name>.model and agents.<name>.model_capabilities entirely; the Capabilities section and pallas_integration.md both claimed capabilities are published only when model_capabilities is configured, which was wrong before and after.

Preserved: the provider prefix stays stripped (anthropic.claude-opus-4-7claude-opus-4-7), and fastagent.config.yaml is still re-read per request.

Behaviour delta

Config Before After
global model + full capabilities correct unchanged
agent model: override advertises global model (wrong) advertises agent's model
agent model_capabilities: override advertises global block advertises agent's block
default_model, no model_capabilities null / null 131072 / 16384
no config, no agent model key absent key absent
no config, agent has model: key absent capabilities published

Please sanity-check the last row against your deployments — it's a deliberate consequence of the agent-model-falls-back-to-global rule, not an accident, but it is newly-published output.

Verification

pytest tests/ -q76 passed (67 existing + 9 new).

Diffed real output against ~/git/mentor's actual agents.yaml + fastagent.config.yaml, old code vs new:

OLD  ca.helu.mentor/alan → {"model": "Qwen3.6-27B-Q5_K_M", "vision": false, "context_window": null,   "max_output_tokens": null}
NEW  ca.helu.mentor/alan → {"model": "Qwen3.6-27B-Q5_K_M", "vision": false, "context_window": 131072, "max_output_tokens": 16384}

Model name unchanged, nulls replaced with the effective values. No checked-in agents.yaml in the estate uses a per-agent model: today, so no currently-published model name changes.

Notes

  • pyproject.toml version deliberately not bumped.
  • Pairs with a Daedalus PR that surfaces model / context window / max output in the agent info panel — these values are what it renders.

🤖 Generated with Claude Code

## What `_build_registry` computed a single `capabilities` dict from the global `default_model` and attached it to **every** agent entry, ignoring the per-agent `model:` / `model_capabilities:` overrides that `server.py` actually applies at startup (`_start_agent` overwrites `AgentConfig.model` for every agent in the module). Any deployment using an override would advertise the wrong model to Daedalus. Second, smaller bug: when `model_capabilities` is absent but `default_model` is set, the registry published `context_window: null` / `max_output_tokens: null` — while `_register_one_model` registered that same model with an effective `131072` / `16384`. The registry now advertises what was actually registered. ## Changes - `registry.py`: `_load_model_capabilities()` → `_load_fastagent_defaults()` (returns the raw globals) plus a new `_resolve_capabilities(agent, default_model, default_capabilities)` called **inside** the agent loop. - `server.py`: `131072` / `16384` hoisted to `DEFAULT_CONTEXT_WINDOW` / `DEFAULT_MAX_OUTPUT_TOKENS` and imported by `registry.py`, so the two cannot drift — that agreement is the whole point of the fix. No import cycle: `server.py` imports `pallas.registry` lazily inside a function, never at module scope. - `tests/test_registry.py`: new, 9 tests — first coverage for `registry.py`. - Docs: the `agents.yaml` field table omitted `agents.<name>.model` and `agents.<name>.model_capabilities` entirely; the Capabilities section and `pallas_integration.md` both claimed capabilities are published only when `model_capabilities` is configured, which was wrong before *and* after. Preserved: the provider prefix stays stripped (`anthropic.claude-opus-4-7` → `claude-opus-4-7`), and `fastagent.config.yaml` is still re-read per request. ## Behaviour delta | Config | Before | After | |---|---|---| | global model + full capabilities | correct | unchanged | | agent `model:` override | advertises global model (**wrong**) | advertises agent's model | | agent `model_capabilities:` override | advertises global block | advertises agent's block | | `default_model`, no `model_capabilities` | `null` / `null` | `131072` / `16384` | | no config, no agent model | key absent | key absent | | no config, agent has `model:` | key absent | capabilities published | **Please sanity-check the last row against your deployments** — it's a deliberate consequence of the agent-model-falls-back-to-global rule, not an accident, but it is newly-published output. ## Verification `pytest tests/ -q` → **76 passed** (67 existing + 9 new). Diffed real output against `~/git/mentor`'s actual `agents.yaml` + `fastagent.config.yaml`, old code vs new: ``` OLD ca.helu.mentor/alan → {"model": "Qwen3.6-27B-Q5_K_M", "vision": false, "context_window": null, "max_output_tokens": null} NEW ca.helu.mentor/alan → {"model": "Qwen3.6-27B-Q5_K_M", "vision": false, "context_window": 131072, "max_output_tokens": 16384} ``` Model name unchanged, nulls replaced with the effective values. No checked-in `agents.yaml` in the estate uses a per-agent `model:` today, so no currently-published model name changes. ## Notes - `pyproject.toml` version deliberately **not** bumped. - Pairs with a Daedalus PR that surfaces model / context window / max output in the agent info panel — these values are what it renders. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
r added 1 commit 2026-08-03 16:23:02 +00:00
_build_registry built one capabilities dict from the global default_model and
attached it to every entry, so any agent with an `agents.<name>.model` or
`model_capabilities` override was advertised under the wrong model — even
though server.py applies those overrides at startup. Resolve capabilities per
agent, mirroring _register_unknown_models.

Also stop emitting null context_window / max_output_tokens when
model_capabilities is absent: _register_one_model registers the model with
131072 / 16384, so the registry now advertises those effective values. The
defaults are hoisted to module constants in server.py so the two cannot drift.

Verified against mentor's config: capabilities go from
{"context_window": null, "max_output_tokens": null} to {131072, 16384}, model
name unchanged. No checked-in deployment uses per-agent overrides today, so no
currently-published model changes.

Adds tests/test_registry.py (9 tests, first coverage for registry.py) and
documents agents.<name>.model / model_capabilities, which the agents.yaml
field table omitted entirely.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
r merged commit 0ae0d55a8d into main 2026-08-03 16:29:44 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: r/pallas#6