personal memory agent
0
fork

Configure Feed

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

Remove unused muse CLI command

The sol muse command was a user-facing wrapper for running agents that
was never documented or integrated into workflows. All agent execution
goes through Cortex (via cortex_request) or the web UI instead.

- Delete muse/cli.py (372 lines)
- Remove "muse" from COMMANDS and GROUPS in sol.py
- Remove "muse" from critical commands test

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

+1 -374
-371
muse/cli.py
··· 1 - #!/usr/bin/env python3 2 - # SPDX-License-Identifier: AGPL-3.0-only 3 - # Copyright (c) 2026 sol pbc 4 - 5 - """Muse CLI - Run AI agents directly or through Cortex. 6 - 7 - Usage: 8 - muse "prompt" # Run through Cortex (default) 9 - muse -p default "prompt" # Use specific persona 10 - muse --direct -p default "prompt" # Run directly, bypass Cortex 11 - echo "prompt" | muse # Read prompt from stdin 12 - """ 13 - 14 - from __future__ import annotations 15 - 16 - import argparse 17 - import asyncio 18 - import json 19 - import sys 20 - import threading 21 - from typing import Any, Dict, Optional, TextIO 22 - 23 - from think.callosum import CallosumConnection 24 - from think.utils import setup_cli 25 - 26 - # --- Event Formatting --- 27 - 28 - 29 - def format_event(event: Dict[str, Any], verbose: bool = False) -> Optional[str]: 30 - """Format an event for display. Returns None if event should be skipped.""" 31 - event_type = event.get("event") 32 - 33 - if event_type == "thinking": 34 - summary = event.get("summary", "") 35 - if not verbose and len(summary) > 200: 36 - summary = summary[:200] + "..." 37 - return f"💭 {summary}" 38 - 39 - elif event_type == "tool_start": 40 - tool = event.get("tool", "unknown") 41 - args = event.get("args", {}) 42 - if verbose: 43 - args_str = ", ".join(f"{k}={repr(v)}" for k, v in args.items()) 44 - else: 45 - # Compact args: truncate long values 46 - args_parts = [] 47 - for k, v in args.items(): 48 - v_str = repr(v) 49 - if len(v_str) > 30: 50 - v_str = v_str[:27] + "..." 51 - args_parts.append(f"{k}={v_str}") 52 - args_str = ", ".join(args_parts) 53 - return f"🔧 {tool}({args_str})" 54 - 55 - elif event_type == "tool_end": 56 - tool = event.get("tool", "unknown") 57 - if verbose: 58 - result = event.get("result", "") 59 - if isinstance(result, str) and len(result) > 100: 60 - result = result[:100] + "..." 61 - return f"✓ {tool} → {result}" 62 - return f"✓ {tool}" 63 - 64 - elif event_type == "error": 65 - error = event.get("error", "Unknown error") 66 - return f"❌ {error}" 67 - 68 - elif event_type == "start": 69 - # Skip start events in formatted output 70 - return None 71 - 72 - elif event_type == "finish": 73 - # Finish events handled separately (result goes to stdout) 74 - return None 75 - 76 - return None 77 - 78 - 79 - def display_event( 80 - event: Dict[str, Any], 81 - verbose: bool = False, 82 - json_output: bool = False, 83 - file: TextIO = sys.stderr, 84 - ) -> None: 85 - """Display an event to the given file handle.""" 86 - if json_output: 87 - # JSON mode: all events to stdout 88 - print(json.dumps(event), file=sys.stdout, flush=True) 89 - else: 90 - formatted = format_event(event, verbose) 91 - if formatted: 92 - print(formatted, file=file, flush=True) 93 - 94 - 95 - # --- Config Building --- 96 - 97 - 98 - def build_agent_config( 99 - prompt: str, 100 - persona: str = "default", 101 - provider: Optional[str] = None, 102 - **overrides: Any, 103 - ) -> Dict[str, Any]: 104 - """Build merged agent configuration from persona and overrides. 105 - 106 - Mirrors the config building logic in cortex.py for consistency. 107 - """ 108 - from muse.mcp import get_tools 109 - from muse.models import resolve_model_for_provider, resolve_provider 110 - from think.utils import get_agent 111 - 112 - # Load persona configuration 113 - config = get_agent(persona) 114 - 115 - # Apply any additional overrides 116 - config.update({k: v for k, v in overrides.items() if v is not None}) 117 - 118 - # Set prompt 119 - config["prompt"] = prompt 120 - config["persona"] = persona 121 - 122 - # Resolve provider and model from context 123 - # Context format: agent.{app}.{name} where app="system" for system agents 124 - if ":" in persona: 125 - app, name = persona.split(":", 1) 126 - else: 127 - app, name = "system", persona 128 - agent_context = f"agent.{app}.{name}" 129 - 130 - # Resolve default provider and model from context 131 - default_provider, model = resolve_provider(agent_context) 132 - 133 - # Provider can be overridden by parameter or persona config 134 - final_provider = provider or config.get("provider") or default_provider 135 - 136 - # If provider was overridden, re-resolve model for that provider 137 - if final_provider != default_provider: 138 - model = resolve_model_for_provider(agent_context, final_provider) 139 - 140 - config["provider"] = final_provider 141 - config["model"] = model 142 - 143 - # Expand tools if it's a string (tool pack name) 144 - tools_config = config.get("tools") 145 - if isinstance(tools_config, str): 146 - pack_names = [p.strip() for p in tools_config.split(",") if p.strip()] 147 - if not pack_names: 148 - pack_names = ["default"] 149 - 150 - expanded: list[str] = [] 151 - for pack in pack_names: 152 - try: 153 - for tool in get_tools(pack): 154 - if tool not in expanded: 155 - expanded.append(tool) 156 - except KeyError: 157 - # Invalid pack, use default 158 - for tool in get_tools("default"): 159 - if tool not in expanded: 160 - expanded.append(tool) 161 - 162 - config["tools"] = expanded 163 - 164 - return config 165 - 166 - 167 - # --- Cortex Mode --- 168 - 169 - 170 - def run_via_cortex( 171 - prompt: str, 172 - persona: str = "default", 173 - provider: Optional[str] = None, 174 - timeout: float = 300.0, 175 - verbose: bool = False, 176 - json_output: bool = False, 177 - ) -> str: 178 - """Run agent through Cortex service via Callosum. 179 - 180 - Returns the result text, or raises an exception on error/timeout. 181 - """ 182 - from muse.cortex_client import cortex_request 183 - 184 - # Submit request to Cortex 185 - config = {"provider": provider} if provider else {} 186 - agent_id = cortex_request(prompt, persona, provider=provider, config=config) 187 - 188 - # Track state 189 - result_text: Optional[str] = None 190 - error_text: Optional[str] = None 191 - finished = threading.Event() 192 - 193 - def on_message(message: Dict[str, Any]) -> None: 194 - nonlocal result_text, error_text 195 - 196 - # Filter for cortex tract and our agent_id 197 - if message.get("tract") != "cortex": 198 - return 199 - if message.get("agent_id") != agent_id: 200 - return 201 - 202 - event_type = message.get("event") 203 - 204 - # Display the event 205 - display_event(message, verbose, json_output) 206 - 207 - # Check for terminal events 208 - if event_type == "finish": 209 - result_text = message.get("result", "") 210 - finished.set() 211 - elif event_type == "error": 212 - error_text = message.get("error", "Unknown error") 213 - finished.set() 214 - 215 - # Connect to Callosum and listen for events 216 - conn = CallosumConnection() 217 - conn.start(callback=on_message) 218 - 219 - try: 220 - # Wait for completion with timeout 221 - if not finished.wait(timeout=timeout): 222 - raise TimeoutError(f"Agent {agent_id} timed out after {timeout}s") 223 - 224 - if error_text: 225 - raise RuntimeError(error_text) 226 - 227 - return result_text or "" 228 - finally: 229 - conn.stop() 230 - 231 - 232 - # --- Direct Mode --- 233 - 234 - 235 - def run_direct( 236 - prompt: str, 237 - persona: str = "default", 238 - provider: Optional[str] = None, 239 - verbose: bool = False, 240 - json_output: bool = False, 241 - ) -> str: 242 - """Run agent directly, bypassing Cortex. 243 - 244 - Returns the result text. 245 - """ 246 - # Build config 247 - config = build_agent_config(prompt, persona, provider) 248 - 249 - # Determine provider 250 - provider_name = config.get("provider", "openai") 251 - 252 - # Create event callback 253 - result_holder: Dict[str, Any] = {"result": None, "error": None} 254 - 255 - def on_event(event: Dict[str, Any]) -> None: 256 - display_event(event, verbose, json_output) 257 - if event.get("event") == "finish": 258 - result_holder["result"] = event.get("result", "") 259 - elif event.get("event") == "error": 260 - result_holder["error"] = event.get("error", "Unknown error") 261 - 262 - # Route to appropriate provider using registry 263 - from muse.providers import PROVIDER_REGISTRY, get_provider_module 264 - 265 - if provider_name in PROVIDER_REGISTRY: 266 - provider_mod = get_provider_module(provider_name) 267 - else: 268 - valid = ", ".join(sorted(PROVIDER_REGISTRY.keys())) 269 - raise ValueError( 270 - f"Unknown provider: {provider_name!r}. Valid providers: {valid}" 271 - ) 272 - 273 - # Run the agent 274 - asyncio.run(provider_mod.run_agent(config=config, on_event=on_event)) 275 - 276 - if result_holder["error"]: 277 - raise RuntimeError(result_holder["error"]) 278 - 279 - return result_holder["result"] or "" 280 - 281 - 282 - # --- Main --- 283 - 284 - 285 - def main() -> None: 286 - """CLI entry point.""" 287 - parser = argparse.ArgumentParser( 288 - description="Run AI agents directly or through Cortex", 289 - formatter_class=argparse.RawDescriptionHelpFormatter, 290 - epilog=""" 291 - Examples: 292 - muse "What time is it?" Run with default persona through Cortex 293 - muse -p joke_bot "tell me a joke" Run with specific persona 294 - muse --direct "prompt" Run directly, bypass Cortex 295 - echo "prompt" | muse Read prompt from stdin 296 - muse --json "prompt" | jq .event JSON output for scripting 297 - """, 298 - ) 299 - 300 - parser.add_argument("prompt", nargs="?", help="The prompt to send to the agent") 301 - parser.add_argument( 302 - "-p", "--persona", default="default", help="Agent persona (default: default)" 303 - ) 304 - parser.add_argument( 305 - "-b", "--provider", help="Override provider (openai, anthropic, google)" 306 - ) 307 - parser.add_argument( 308 - "--direct", action="store_true", help="Run directly, bypass Cortex" 309 - ) 310 - parser.add_argument("--json", action="store_true", help="Output raw JSONL events") 311 - parser.add_argument( 312 - "-t", 313 - "--timeout", 314 - type=float, 315 - default=300.0, 316 - help="Timeout in seconds (Cortex mode, default: 300)", 317 - ) 318 - 319 - # setup_cli adds -v/--verbose 320 - args = setup_cli(parser) 321 - 322 - # Get prompt from argument or stdin 323 - prompt = args.prompt 324 - if not prompt: 325 - if sys.stdin.isatty(): 326 - parser.error( 327 - "No prompt provided. Use positional argument or pipe via stdin." 328 - ) 329 - prompt = sys.stdin.read().strip() 330 - if not prompt: 331 - parser.error("Empty prompt from stdin") 332 - 333 - try: 334 - if args.direct: 335 - result = run_direct( 336 - prompt, 337 - persona=args.persona, 338 - provider=args.provider, 339 - verbose=args.verbose, 340 - json_output=args.json, 341 - ) 342 - else: 343 - result = run_via_cortex( 344 - prompt, 345 - persona=args.persona, 346 - provider=args.provider, 347 - timeout=args.timeout, 348 - verbose=args.verbose, 349 - json_output=args.json, 350 - ) 351 - 352 - # Print result to stdout (unless in JSON mode where it's already printed) 353 - if not args.json and result: 354 - print(result) 355 - 356 - except TimeoutError as e: 357 - print(f"❌ Timeout: {e}", file=sys.stderr) 358 - sys.exit(2) 359 - except RuntimeError as e: 360 - print(f"❌ Error: {e}", file=sys.stderr) 361 - sys.exit(1) 362 - except KeyboardInterrupt: 363 - print("\n❌ Interrupted", file=sys.stderr) 364 - sys.exit(130) 365 - except Exception as e: 366 - print(f"❌ Unexpected error: {e}", file=sys.stderr) 367 - sys.exit(1) 368 - 369 - 370 - if __name__ == "__main__": 371 - main()
-2
sol.py
··· 60 60 "observe-linux": "observe.linux.observer", 61 61 "observe-macos": "observe.macos.observer", 62 62 # muse package - AI agents and MCP 63 - "muse": "muse.cli", 64 63 "agents": "muse.agents", 65 64 "cortex": "muse.cortex", 66 65 "mcp": "muse.mcp", ··· 108 107 "observer", 109 108 ], 110 109 "Muse (AI agents)": [ 111 - "muse", 112 110 "agents", 113 111 "cortex", 114 112 "mcp",
+1 -1
tests/test_sol.py
··· 219 219 220 220 def test_critical_commands_registered(self): 221 221 """Test that critical commands are registered.""" 222 - critical = ["import", "insight", "dream", "indexer", "transcribe", "muse"] 222 + critical = ["import", "insight", "dream", "indexer", "transcribe"] 223 223 for cmd in critical: 224 224 assert cmd in sol.COMMANDS, f"Critical command '{cmd}' not registered"