personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Unify provider routing with central registry and remove dead code

- Create PROVIDER_REGISTRY in muse/providers/__init__.py for centralized
provider→module mapping (google, openai, anthropic)
- Update muse/agents.py and muse/cli.py to use registry with explicit
error for unknown providers instead of silent fallbacks
- Standardize run_agent() to require model (set by Cortex) rather than
using hardcoded defaults per provider
- Fix token logging context format in cortex.py to use agent.{app}.{name}
- Remove dead code: _journal_emit, _close_journal_writer, JournalEventWriter,
unused _DEFAULT_MODEL and is_valid_provider
- Update all tests to include model in NDJSON inputs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+128 -56
+12 -19
muse/agents.py
··· 135 135 pass 136 136 137 137 138 - def _journal_emit(event: Event) -> None: 139 - """Emit event to stdout for cortex to capture.""" 140 - # No longer manages files - just ensure event goes to stdout 141 - pass 142 - 143 - 144 - def _close_journal_writer() -> None: 145 - """No-op - cortex manages all file handles.""" 146 - pass 147 - 148 - 149 138 class JSONEventCallback: 150 139 """Emit JSON events via a callback.""" 151 140 ··· 264 253 "ThinkingEvent", 265 254 "Event", 266 255 "JSONEventWriter", 267 - "JournalEventWriter", 268 256 "JSONEventCallback", 269 257 "format_tool_summary", 270 258 "parse_agent_events_to_turns", ··· 315 303 continue 316 304 317 305 # Extract provider to route to correct module 306 + from .providers import PROVIDER_REGISTRY, get_provider_module 307 + 318 308 provider = config.get("provider", "google") 319 309 320 310 # Set OpenAI key if needed ··· 327 317 328 318 app_logger.debug(f"Processing request: provider={provider}") 329 319 330 - # Route to appropriate provider 331 - if provider == "google": 332 - from .providers import google as provider_mod 333 - elif provider == "anthropic": 334 - from .providers import anthropic as provider_mod 335 - elif provider == "claude": 320 + # Route to appropriate provider module 321 + # "claude" is a special case (Claude Code SDK) handled separately 322 + if provider == "claude": 336 323 from . import claude as provider_mod 324 + elif provider in PROVIDER_REGISTRY: 325 + provider_mod = get_provider_module(provider) 337 326 else: 338 - from .providers import openai as provider_mod 327 + # Explicit error for unknown providers 328 + valid = ", ".join(sorted(PROVIDER_REGISTRY.keys()) + ["claude"]) 329 + raise ValueError( 330 + f"Unknown provider: {provider!r}. Valid providers: {valid}" 331 + ) 339 332 340 333 # Pass complete config to provider 341 334 await provider_mod.run_agent(
+2
muse/claude.py
··· 91 91 if not prompt: 92 92 raise ValueError("Missing 'prompt' in config") 93 93 94 + # Model has a default for Claude Code SDK since it bypasses tier-based resolution 95 + # Cortex sets provider="claude" but doesn't set model for this special case 94 96 model = config.get("model", _DEFAULT_MODEL) 95 97 max_turns = config.get("max_turns", 32) 96 98 persona = config.get("persona", "default")
+8 -7
muse/cli.py
··· 266 266 elif event.get("event") == "error": 267 267 result_holder["error"] = event.get("error", "Unknown error") 268 268 269 - # Route to appropriate provider 270 - if provider_name == "google": 271 - from muse.providers import google as provider_mod 272 - elif provider_name == "anthropic": 273 - from muse.providers import anthropic as provider_mod 274 - elif provider_name == "claude": 269 + # Route to appropriate provider using registry 270 + from muse.providers import PROVIDER_REGISTRY, get_provider_module 271 + 272 + if provider_name == "claude": 275 273 from muse import claude as provider_mod 274 + elif provider_name in PROVIDER_REGISTRY: 275 + provider_mod = get_provider_module(provider_name) 276 276 else: 277 - from muse.providers import openai as provider_mod 277 + valid = ", ".join(sorted(PROVIDER_REGISTRY.keys()) + ["claude"]) 278 + raise ValueError(f"Unknown provider: {provider_name!r}. Valid providers: {valid}") 278 279 279 280 # Run the agent 280 281 asyncio.run(provider_mod.run_agent(config=config, on_event=on_event))
+8 -1
muse/cortex.py
··· 545 545 546 546 model = original_request.get("model", "unknown") 547 547 persona = original_request.get("persona", "unknown") 548 - context = f"agent.{persona}.{agent.agent_id}" 548 + 549 + # Build context in same format as model resolution: 550 + # agent.{app}.{name} where app="system" for system agents 551 + if ":" in persona: 552 + app, name = persona.split(":", 1) 553 + else: 554 + app, name = "system", persona 555 + context = f"agent.{app}.{name}" 549 556 550 557 # Extract segment from config env if set 551 558 config = original_request.get("config", {})
+10 -18
muse/models.py
··· 689 689 ValueError 690 690 If the resolved provider is not supported. 691 691 """ 692 + from muse.providers import get_provider_module 693 + 692 694 # Allow model override via kwargs (used by callers with explicit model selection) 693 695 model_override = kwargs.pop("model", None) 694 696 ··· 696 698 if model_override: 697 699 model = model_override 698 700 699 - if provider == "google": 700 - from muse.providers.google import generate as provider_generate 701 - elif provider == "openai": 702 - from muse.providers.openai import generate as provider_generate 703 - elif provider == "anthropic": 704 - from muse.providers.anthropic import generate as provider_generate 705 - else: 706 - raise ValueError(f"Unsupported provider: {provider}") 701 + # Get provider module via registry (raises ValueError for unknown providers) 702 + provider_mod = get_provider_module(provider) 707 703 708 - return provider_generate( 704 + return provider_mod.generate( 709 705 contents=contents, 710 706 model=model, 711 707 temperature=temperature, ··· 767 763 ValueError 768 764 If the resolved provider is not supported. 769 765 """ 766 + from muse.providers import get_provider_module 767 + 770 768 # Allow model override via kwargs (used by Batch for explicit model selection) 771 769 model_override = kwargs.pop("model", None) 772 770 ··· 774 772 if model_override: 775 773 model = model_override 776 774 777 - if provider == "google": 778 - from muse.providers.google import agenerate as provider_agenerate 779 - elif provider == "openai": 780 - from muse.providers.openai import agenerate as provider_agenerate 781 - elif provider == "anthropic": 782 - from muse.providers.anthropic import agenerate as provider_agenerate 783 - else: 784 - raise ValueError(f"Unsupported provider: {provider}") 775 + # Get provider module via registry (raises ValueError for unknown providers) 776 + provider_mod = get_provider_module(provider) 785 777 786 - return await provider_agenerate( 778 + return await provider_mod.agenerate( 787 779 contents=contents, 788 780 model=model, 789 781 temperature=temperature,
+55
muse/providers/__init__.py
··· 15 15 - openai: OpenAI GPT models 16 16 - anthropic: Anthropic Claude models 17 17 """ 18 + 19 + from importlib import import_module 20 + from types import ModuleType 21 + from typing import Dict 22 + 23 + # --------------------------------------------------------------------------- 24 + # Provider Registry 25 + # --------------------------------------------------------------------------- 26 + # Central registry of supported providers and their module paths. 27 + # All registered providers must implement: 28 + # - generate(contents, model, ...) -> str 29 + # - agenerate(contents, model, ...) -> str 30 + # - run_agent(config, on_event) -> str 31 + # 32 + # The "claude" provider (Claude Code SDK) is intentionally excluded from this 33 + # registry as it uses a fundamentally different execution model (local CLI) 34 + # and is handled as a special case in muse/agents.py. 35 + # --------------------------------------------------------------------------- 36 + 37 + PROVIDER_REGISTRY: Dict[str, str] = { 38 + "google": "muse.providers.google", 39 + "openai": "muse.providers.openai", 40 + "anthropic": "muse.providers.anthropic", 41 + } 42 + 43 + 44 + def get_provider_module(provider: str) -> ModuleType: 45 + """Get the provider module for the given provider name. 46 + 47 + Parameters 48 + ---------- 49 + provider 50 + Provider name (e.g., "google", "openai", "anthropic"). 51 + 52 + Returns 53 + ------- 54 + ModuleType 55 + The provider module with generate, agenerate, and run_agent functions. 56 + 57 + Raises 58 + ------ 59 + ValueError 60 + If the provider is not registered. 61 + """ 62 + if provider not in PROVIDER_REGISTRY: 63 + valid = ", ".join(sorted(PROVIDER_REGISTRY.keys())) 64 + raise ValueError(f"Unknown provider: {provider!r}. Valid providers: {valid}") 65 + 66 + return import_module(PROVIDER_REGISTRY[provider]) 67 + 68 + 69 + __all__ = [ 70 + "PROVIDER_REGISTRY", 71 + "get_provider_module", 72 + ]
+5 -1
muse/providers/anthropic.py
··· 194 194 if not prompt: 195 195 raise ValueError("Missing 'prompt' in config") 196 196 197 - model = config.get("model", _DEFAULT_MODEL) 197 + # Model is required - Cortex always provides it via resolve_provider() 198 + model = config.get("model") 199 + if not model: 200 + raise ValueError("Missing 'model' in config - should be set by Cortex") 201 + 198 202 max_tokens = config.get("max_tokens", _DEFAULT_MAX_TOKENS) 199 203 disable_mcp = config.get("disable_mcp", False) 200 204 persona = config.get("persona", "default")
+6 -8
muse/providers/google.py
··· 20 20 from google import genai 21 21 from google.genai import types 22 22 23 - from muse.models import GEMINI_FLASH, resolve_provider 23 + from muse.models import GEMINI_FLASH 24 24 from think.utils import create_mcp_client 25 25 26 26 from ..agents import JSONEventCallback, ThinkingEvent ··· 312 312 # --------------------------------------------------------------------------- 313 313 314 314 315 - def _get_default_model() -> str: 316 - """Return the configured default model for agents.""" 317 - _, model = resolve_provider("agent.default") 318 - return model 319 - 320 - 321 315 class ToolLoggingHooks: 322 316 """Wrap ``session.call_tool`` to emit events.""" 323 317 ··· 417 411 if not prompt: 418 412 raise ValueError("Missing 'prompt' in config") 419 413 420 - model = config.get("model") or _get_default_model() 414 + # Model is required - Cortex always provides it via resolve_provider() 415 + model = config.get("model") 416 + if not model: 417 + raise ValueError("Missing 'model' in config - should be set by Cortex") 418 + 421 419 max_tokens = config.get("max_tokens", _DEFAULT_MAX_TOKENS) 422 420 disable_mcp = config.get("disable_mcp", False) 423 421 persona = config.get("persona", "default")
+5 -2
muse/providers/openai.py
··· 84 84 85 85 86 86 # Default values 87 - _DEFAULT_MODEL = GPT_5 88 87 _DEFAULT_MAX_TOKENS = 16384 89 88 _DEFAULT_MAX_TURNS = 64 90 89 ··· 178 177 if not prompt: 179 178 raise ValueError("Missing 'prompt' in config") 180 179 181 - model = config.get("model", _DEFAULT_MODEL) 180 + # Model is required - Cortex always provides it via resolve_provider() 181 + model = config.get("model") 182 + if not model: 183 + raise ValueError("Missing 'model' in config - should be set by Cortex") 184 + 182 185 max_tokens = config.get("max_tokens", _DEFAULT_MAX_TOKENS) 183 186 max_turns = config.get("max_turns", _DEFAULT_MAX_TURNS) 184 187 disable_mcp = config.get("disable_mcp", False)
+1
tests/integration/test_anthropic_provider.py
··· 54 54 { 55 55 "prompt": "what is 1+1? Just give me the number.", 56 56 "provider": "anthropic", 57 + "model": CLAUDE_SONNET_4, 57 58 "persona": "default", 58 59 "max_tokens": 100, 59 60 "disable_mcp": True,
+4
tests/test_anthropic.py
··· 137 137 { 138 138 "prompt": "hello", 139 139 "provider": "anthropic", 140 + "model": CLAUDE_SONNET_4, 140 141 "mcp_server_url": "http://localhost:5173/mcp", 141 142 } 142 143 ) ··· 177 178 { 178 179 "prompt": "hello", 179 180 "provider": "anthropic", 181 + "model": CLAUDE_SONNET_4, 180 182 "mcp_server_url": "http://localhost:5173/mcp", 181 183 } 182 184 ) ··· 226 228 { 227 229 "prompt": "hello", 228 230 "provider": "anthropic", 231 + "model": CLAUDE_SONNET_4, 229 232 "mcp_server_url": "http://localhost:5173/mcp", 230 233 } 231 234 ) ··· 265 268 { 266 269 "prompt": "hello", 267 270 "provider": "anthropic", 271 + "model": CLAUDE_SONNET_4, 268 272 "mcp_server_url": "http://localhost:5173/mcp", 269 273 } 270 274 )
+3
tests/test_google.py
··· 85 85 { 86 86 "prompt": "hello", 87 87 "provider": "google", 88 + "model": GEMINI_FLASH, 88 89 "disable_mcp": True, 89 90 } 90 91 ) ··· 123 124 { 124 125 "prompt": "hello", 125 126 "provider": "google", 127 + "model": GEMINI_FLASH, 126 128 "disable_mcp": True, 127 129 } 128 130 ) ··· 175 177 { 176 178 "prompt": "hello", 177 179 "provider": "google", 180 + "model": GEMINI_FLASH, 178 181 "mcp_server_url": "http://localhost:5175/mcp", 179 182 } 180 183 )
+1
tests/test_google_thinking.py
··· 87 87 { 88 88 "prompt": "hello", 89 89 "provider": "google", 90 + "model": GEMINI_FLASH, 90 91 "disable_mcp": True, 91 92 } 92 93 )
+8
tests/test_openai.py
··· 45 45 { 46 46 "prompt": "hello", 47 47 "provider": "openai", 48 + "model": GPT_5, 48 49 "mcp_server_url": "http://localhost:5173/mcp", 49 50 } 50 51 ) ··· 98 99 { 99 100 "prompt": "hello", 100 101 "provider": "openai", 102 + "model": GPT_5, 101 103 "mcp_server_url": "http://localhost:5173/mcp", 102 104 } 103 105 ) ··· 142 144 { 143 145 "prompt": "audit headers", 144 146 "provider": "openai", 147 + "model": GPT_5, 145 148 "persona": "investigator", 146 149 "agent_id": "999", 147 150 "mcp_server_url": "http://localhost:5173/mcp", ··· 177 180 { 178 181 "prompt": "hello", 179 182 "provider": "openai", 183 + "model": GPT_5, 180 184 "mcp_server_url": "http://localhost:5173/mcp", 181 185 } 182 186 ) ··· 229 233 { 230 234 "prompt": "hello", 231 235 "provider": "openai", 236 + "model": GPT_5, 232 237 "mcp_server_url": "http://localhost:5173/mcp", 233 238 } 234 239 ) ··· 287 292 { 288 293 "prompt": "hello", 289 294 "provider": "openai", 295 + "model": GPT_5, 290 296 "mcp_server_url": "http://localhost:5173/mcp", 291 297 } 292 298 ) ··· 335 341 { 336 342 "prompt": "hello", 337 343 "provider": "openai", 344 + "model": GPT_5, 338 345 "mcp_server_url": "http://localhost:5173/mcp", 339 346 } 340 347 ) ··· 397 404 { 398 405 "prompt": "hello", 399 406 "provider": "openai", 407 + "model": GPT_5, 400 408 "mcp_server_url": "http://localhost:5173/mcp", 401 409 } 402 410 )