Skip to content

Agent capabilities apply on the next message, not on the next reload #121

Description

@Lancetnik

Problem

What the agent can do only reaches a chat when the agent object is rebuilt. The gateway builds one agent per profile at start (Gateway._agent) plus one per LLM config (_model_agents) and keeps them until ProfileManager.reload. So a change to skills, MCP servers, tools or Cards applies on the next reload at best — and some never reach a running profile:

Input Reaches a chat today
Persona, per-turn guidance, the rich-views switch, A2UI middleware, permissions/HITL every turn
LLM config, tools, Google tools, MCP servers, Docker sandbox, memory store, skills catalog on reload
A Card added, removed or switched on/off (the rich-views description, ADR 0039) never until reload — nothing triggers one
A skill the agent installs itself via install_skill, a skill edited on disk never until reload

Settings routes for voice, connections, profiles and ACP change inputs without reloading at all.

Root of it: AG2's SkillPlugin freezes both the <available_skills> catalog (a static prompt string) and the load_skill name set at construction, and AG2's dynamic prompts only run when a turn passes no prompt= — the gateway always passes one (it now prepends agent.system_prompt itself, see #117).

Direction (to be verified)

Build the agent per turn and keep only the long-lived handles in a per-profile cache.

Measured on a real profile (openai, local sandbox, no MCP): create_agent is 120–200 ms warm (~390 ms cold), and ~75% of that is re-parsing every Card file because resolve_a2ui_skill builds a fresh CardCatalog per build instead of sharing the gateway's fingerprinted one. With the catalog shared, a build is ~30–40 ms. AG2 already creates the LLM client per turn; MCP servers start lazily.

Per-turn construction must not recreate what outlives a turn:

  • MCP toolkits hold one live connection (300 s idle close) — rebuilding would respawn the server every turn and leave the old one alive for up to 5 min.
  • DockerEnvironment caches its container until process exit — one leaked container per turn.
  • ACP model configs (Claude Code / Codex) hold per-chat sessions in ACPConfig._sessions — a new config per turn loses the session and spawns a new subprocess.
  • Memory store — one SQLite connection per profile, not per turn.

Sketch:

  1. Gateway.send_message builds the turn's agent (_make_agent(cfg_for_turn)); drop _agent, _model_agents, the rebuild in _ensure_subscription_fresh and the rebuild half of reload().
  2. A per-profile resource cache, created in Gateway.start and passed into create_agent as a parameter (no globals, testable per AGENTS.md): the shared CardCatalog, the memory store, MCP toolkits keyed by server settings (removed/changed ones closed), DockerEnvironment keyed by image+network, model configs keyed by config id + token. close() closes all of it.
  3. Callers that hold an agent (require_agent(): ACP listener, voice tool names, A2UI server actions) get a builder instead.
  4. Remove the reload calls the routes make only to pick up a change.

Tests (no monkeypatch): a real Gateway on temp Paths with a fake model config that records its prompt — send, drop a Card / switch a skill off via SkillStateStore, send again, assert the second prompt's catalog changed; an MCP stub via write_stub counting spawns — two turns, one spawn; removing the server closes it.

Existing bugs found on the way (worth fixing regardless)

  • _aclose_agents closes only the model config, so every reload today leaks MCP servers (until idle timeout) and Docker containers (until exit).
  • The ACP listener captures the agent once at start (profile_manager.py) and never sees a reload.

Upstream (AG2) opportunities

Some of this may belong in AG2 rather than here:

Related

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

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions