personal memory agent
0
fork

Configure Feed

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

Consolidate muse/ package into think/

Move all AI agent orchestration code from the standalone muse/ package
into think/ to simplify the codebase architecture. This reduces the
number of top-level packages and consolidates related functionality.

Changes:
- Move muse/*.py modules to think/ (agents, batch, cortex, models, mcp)
- Move muse/agents/, providers/, resources/, tools/ subdirectories
- Update all imports from muse.* to think.*
- Update pyproject.toml package configuration
- Merge MUSE.md content into THINK.md
- Fix AGENT_DIR path in think/utils.py
- Update documentation references throughout
- Remove unused imports (Optional, load_dotenv, Callable)

All 1043 unit tests pass.

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

+377 -383
+7 -9
AGENTS.md
··· 7 7 **solstone** is a Python-based AI-driven desktop journaling toolkit that provides: 8 8 9 9 * **observe/** - Multimodal capture (audio + visual) and AI-powered analysis 10 - * **think/** - Data post-processing, summarization, and intelligent insights 10 + * **think/** - Data post-processing, AI agent orchestration, and intelligent insights 11 11 * **convey/** - Web application for navigating and interacting with captured content (extensible via apps/) 12 - * **muse/** - AI agent system and MCP tooling 13 12 14 13 The project uses a modular architecture where each package can operate independently while sharing common utilities and data formats through the journal system. 15 14 ··· 25 24 26 25 * **Entities**: Extracted information (people, projects, concepts) tracked over time across transcripts and interactions. Entities are associated with facets and enable semantic navigation. 27 26 28 - * **Agents**: AI processors with configurable personas that analyze content, extract insights, and respond to queries. See [docs/MUSE.md](docs/MUSE.md) for the agent system and [docs/CORTEX.md](docs/CORTEX.md) for eventing. 27 + * **Agents**: AI processors with configurable personas that analyze content, extract insights, and respond to queries. See [docs/THINK.md](docs/THINK.md) for the agent system and [docs/CORTEX.md](docs/CORTEX.md) for eventing. 29 28 30 29 * **Callosum**: Message bus that enables asynchronous communication between components. See [docs/CALLOSUM.md](docs/CALLOSUM.md). 31 30 ··· 39 38 solstone/ 40 39 ├── sol.py # Unified CLI entry point (run: sol <command>) 41 40 ├── observe/ # Multimodal capture & AI analysis 42 - ├── think/ # Data post-processing & AI analysis 41 + ├── think/ # Data post-processing, AI agents & MCP tooling 43 42 ├── convey/ # Web app frontend & backend 44 43 ├── apps/ # Convey app extensions (see docs/APPS.md) 45 - ├── muse/ # AI agent system and MCP tooling 46 44 ├── tests/ # Pytest test suites 47 45 ├── fixtures/ # Test data (mock journal) 48 46 ├── docs/ # All documentation (*.md files) ··· 77 75 **Component Communication**: 78 76 * Callosum message bus enables async communication between services 79 77 * Cortex orchestrates AI agent execution via `sol cortex`, spawning agent subprocesses with persona configurations 80 - * See [docs/MUSE.md](docs/MUSE.md) for agent system details and [docs/CORTEX.md](docs/CORTEX.md) for the eventing protocol 78 + * See [docs/THINK.md](docs/THINK.md) for agent system details and [docs/CORTEX.md](docs/CORTEX.md) for the eventing protocol 81 79 82 80 **Command Reference**: 83 81 The unified CLI is `sol`. Run `sol` to see status and available commands. Use `sol <command>` for subcommands or `sol <module.path>` for direct module access. ··· 171 169 * Update README files for new functionality 172 170 * Code comments explain "why" not "what" 173 171 * Function signatures should include type hints; highlight gaps when touching older modules 174 - * **All docs in `docs/`**: Browse for JOURNAL.md, APPS.md, CORTEX.md, CALLOSUM.md, MUSE.md, and more 172 + * **All docs in `docs/`**: Browse for JOURNAL.md, APPS.md, CORTEX.md, CALLOSUM.md, THINK.md, and more 175 173 * **App/UI work**: [docs/APPS.md](docs/APPS.md) is required reading before modifying `apps/` 176 174 177 175 --- ··· 224 222 * **Entry Points**: `sol.py` `COMMANDS` dict 225 223 * **Test Fixtures**: `fixtures/journal/` - complete mock journal 226 224 * **Live Logs**: `$JOURNAL_PATH/health/<service>.log` 227 - * **Agent Personas**: `muse/agents/*.txt` + `*.json` (apps can add their own, see [docs/APPS.md](docs/APPS.md)) 228 - * **Insight Templates**: `think/insights/*.txt` + `*.json` (apps can add their own, see [docs/APPS.md](docs/APPS.md)) 225 + * **Agent Personas**: `think/agents/*.md` (apps can add their own, see [docs/APPS.md](docs/APPS.md)) 226 + * **Insight Templates**: `think/insights/*.md` (apps can add their own, see [docs/APPS.md](docs/APPS.md)) 229 227 * **Scratch Space**: `scratch/` - git-ignored local workspace 230 228 231 229 ### Getting Help
+1 -1
README.md
··· 31 31 raw media JSON extracts Flask web UI 32 32 (flac, webm) (jsonl) 33 33 | 34 - muse (AI agents) 34 + think (AI agents) 35 35 ``` 36 36 37 37 ## Getting Started
+2 -2
apps/agents/routes.py
··· 100 100 return None 101 101 102 102 try: 103 - from muse.models import calc_token_cost 103 + from think.models import calc_token_cost 104 104 105 105 cost_data = calc_token_cost({"model": model, "usage": usage}) 106 106 if cost_data: ··· 117 117 runtime_seconds, thinking_count, tool_count, cost. 118 118 Returns None if file cannot be parsed. 119 119 """ 120 - from muse.cortex_client import get_agent_end_state 120 + from think.cortex_client import get_agent_end_state 121 121 122 122 try: 123 123 with open(agent_file, "r") as f:
+6 -6
apps/chat/routes.py
··· 13 13 from apps.utils import get_app_storage_path 14 14 from convey.config import get_selected_facet 15 15 from convey.utils import load_json, save_json 16 - from muse.models import generate 16 + from think.models import generate 17 17 18 18 logger = logging.getLogger(__name__) 19 19 ··· 109 109 Returns: 110 110 Error message if API key is not set, None if valid 111 111 """ 112 - from muse.providers import PROVIDER_METADATA 112 + from think.providers import PROVIDER_METADATA 113 113 114 114 key_name = PROVIDER_METADATA.get(provider, {}).get("env_key", "GOOGLE_API_KEY") 115 115 if not os.getenv(key_name): ··· 151 151 is_continuation = False 152 152 153 153 if continue_chat and (chats_dir / f"{continue_chat}.json").exists(): 154 - from muse.cortex_client import get_agent_thread 154 + from think.cortex_client import get_agent_thread 155 155 156 156 # Derive thread from chat_id (which equals first agent_id) 157 157 try: ··· 238 238 Derives thread from agent files, then hydrates events from all agents. 239 239 For active chats, client should subscribe to WebSocket for real-time updates. 240 240 """ 241 - from muse.cortex_client import ( 241 + from think.cortex_client import ( 242 242 get_agent_end_state, 243 243 get_agent_status, 244 244 get_agent_thread, ··· 329 329 Returns: 330 330 Chat metadata JSON or 404 if not found 331 331 """ 332 - from muse.cortex_client import get_agent_thread 332 + from think.cortex_client import get_agent_thread 333 333 334 334 try: 335 335 # Derive thread from agent - first element is the root/chat_id ··· 425 425 Returns: 426 426 JSON with agent_id of the new retry attempt 427 427 """ 428 - from muse.cortex_client import ( 428 + from think.cortex_client import ( 429 429 get_agent_end_state, 430 430 get_agent_thread, 431 431 read_agent_events,
+2 -2
apps/chat/tools.py
··· 9 9 10 10 from fastmcp import Context 11 11 12 - from muse.mcp import HINTS, register_tool 13 12 from think.facets import _get_actor_info 13 + from think.mcp import HINTS, register_tool 14 14 from think.utils import get_journal 15 15 16 16 # Declare pack membership - add send_message to journal pack ··· 55 55 journal_path = get_journal() 56 56 57 57 # Create a synthetic agent (just the agent JSONL file) 58 - from muse.cortex_client import create_synthetic_agent 58 + from think.cortex_client import create_synthetic_agent 59 59 60 60 agent_id = create_synthetic_agent(result=body) 61 61
+1 -1
apps/entities/tools.py
··· 13 13 14 14 from fastmcp import Context 15 15 16 - from muse.mcp import HINTS, register_tool 17 16 from think.entities import ( 18 17 ObservationNumberError, 19 18 add_observation, ··· 26 25 validate_aka_uniqueness, 27 26 ) 28 27 from think.facets import log_tool_action 28 + from think.mcp import HINTS, register_tool 29 29 30 30 # Declare tool pack - creates the "entities" pack with all entity tools 31 31 TOOL_PACKS = {
+1 -1
apps/insights/routes.py
··· 14 14 from flask import Blueprint, jsonify, redirect, render_template, url_for 15 15 16 16 from convey.utils import DATE_RE, format_date 17 - from muse.models import get_usage_cost 17 + from think.models import get_usage_cost 18 18 from think.utils import day_dirs, day_path, get_insight_topic, get_insights 19 19 20 20 insights_bp = Blueprint(
+4 -4
apps/settings/routes.py
··· 254 254 - api_keys: Boolean status for each provider's API key 255 255 """ 256 256 try: 257 - from muse.models import ( 257 + from think.models import ( 258 258 DEFAULT_PROVIDER, 259 259 DEFAULT_TIER, 260 260 get_context_registry, 261 261 ) 262 - from muse.providers import get_provider_list 262 + from think.providers import get_provider_list 263 263 264 264 config = get_journal_config() 265 265 providers_config = config.get("providers", {}) ··· 318 318 Setting a context to null removes the override. 319 319 """ 320 320 try: 321 - from muse.models import get_context_registry 322 - from muse.providers import PROVIDER_REGISTRY 321 + from think.models import get_context_registry 322 + from think.providers import PROVIDER_REGISTRY 323 323 324 324 request_data = request.get_json() 325 325 if not request_data:
+1 -1
apps/todos/routes.py
··· 610 610 if not agent_id: 611 611 return jsonify({"status": "none", "agent_id": None}) 612 612 613 - from muse.cortex_client import cortex_agents 613 + from think.cortex_client import cortex_agents 614 614 615 615 todo_path = _todo_path(day, facet) 616 616
+1 -1
apps/todos/tools.py
··· 15 15 from fastmcp.resources import TextResource 16 16 17 17 from apps.todos import todo 18 - from muse.mcp import HINTS, mcp, register_tool 19 18 from think.facets import log_tool_action 19 + from think.mcp import HINTS, mcp, register_tool 20 20 21 21 # Declare tool pack - creates the "todo" pack with all todo tools 22 22 TOOL_PACKS = {
+1 -1
apps/tokens/routes.py
··· 14 14 15 15 from convey import state 16 16 from convey.utils import DATE_RE 17 - from muse.models import calc_token_cost, get_model_provider, iter_token_log 17 + from think.models import calc_token_cost, get_model_provider, iter_token_log 18 18 19 19 tokens_bp = Blueprint( 20 20 "app:tokens",
+1 -1
apps/transcripts/routes.py
··· 24 24 from apps.utils import log_app_action 25 25 from convey import emit, state 26 26 from convey.utils import DATE_RE, error_response, format_date, success_response 27 - from muse.models import get_usage_cost 28 27 from observe.hear import format_audio 29 28 from observe.screen import format_screen 30 29 from observe.utils import AUDIO_EXTENSIONS, VIDEO_EXTENSIONS 31 30 from think.cluster import cluster_scan, cluster_segments 31 + from think.models import get_usage_cost 32 32 from think.utils import day_dirs, day_path 33 33 from think.utils import segment_key as validate_segment_key 34 34
+1 -1
convey/utils.py
··· 106 106 ValueError: If config is invalid 107 107 Exception: On agent spawn failure 108 108 """ 109 - from muse.cortex_client import cortex_request 109 + from think.cortex_client import cortex_request 110 110 111 111 return cortex_request( 112 112 prompt=prompt,
+6 -6
docs/APPS.md
··· 241 241 242 242 **Key Points:** 243 243 - Only create `tools.py` if your app needs custom AI agent tools 244 - - Tools use the `@register_tool` decorator from `muse.mcp` 244 + - Tools use the `@register_tool` decorator from `think.mcp` 245 245 - Automatically discovered and loaded at server startup 246 246 - Errors in one app's tools don't prevent other apps from loading 247 247 - Tools become available to all AI agents via the MCP protocol 248 248 249 249 **Required imports:** 250 250 ```python 251 - from muse.mcp import register_tool, HINTS 251 + from think.mcp import register_tool, HINTS 252 252 ``` 253 253 254 254 **Decorator usage:** Apply `@register_tool(annotations=HINTS)` to plain functions that return dict responses. Functions should include type hints and docstrings for AI agent context. ··· 256 256 **Discovery behavior:** The MCP server scans `apps/*/tools.py` at startup, imports modules, and registers decorated functions. Private apps (directories starting with `_`) are skipped. 257 257 258 258 **Reference implementations:** 259 - - Discovery logic: `muse/mcp.py` - `_discover_app_tools()` function 259 + - Discovery logic: `think/mcp.py` - `_discover_app_tools()` function 260 260 - Testing: `tests/integration/test_app_tool_discovery.py` - Error handling and edge cases 261 261 - App tool examples: `apps/todos/tools.py`, `apps/entities/tools.py` - Tool implementation patterns 262 - - Core tool example: `muse/tools/search.py` - Search tool implementation 262 + - Core tool example: `think/tools/search.py` - Search tool implementation 263 263 264 264 --- 265 265 ··· 330 330 - Keys are namespaced as `{app}:{agent}` (e.g., `my_app:helper`) 331 331 - Agents inherit all system agent capabilities (tools, scheduling, handoffs, multi-facet) 332 332 333 - **Metadata format:** Same schema as system agents in `muse/agents/*.md` - JSON frontmatter includes `title`, `provider`, `model`, `tools`, `schedule`, `priority`, and `multi_facet` fields. See [CORTEX.md](CORTEX.md) for agent configuration details. 333 + **Metadata format:** Same schema as system agents in `think/agents/*.md` - JSON frontmatter includes `title`, `provider`, `model`, `tools`, `schedule`, `priority`, and `multi_facet` fields. See [CORTEX.md](CORTEX.md) for agent configuration details. 334 334 335 335 **Template variables:** Agent prompts can use template variables like `$name`, `$preferred`, and pronoun variables. See [PROMPT_TEMPLATES.md](PROMPT_TEMPLATES.md) for the complete template system documentation. 336 336 337 337 **Reference implementations:** 338 - - System agent examples: `muse/agents/*.md` 338 + - System agent examples: `think/agents/*.md` 339 339 - Discovery logic: `think/utils.py` - `get_agents()`, `get_agent()` 340 340 341 341 #### Instructions Configuration
+3 -3
docs/CALLOSUM.md
··· 35 35 > **Note:** This registry is kept intentionally high-level. For detailed field schemas and current implementation, always refer to the source files listed - they are the authoritative reference. 36 36 37 37 ### `cortex` - Agent execution events 38 - **Source:** `muse/cortex.py` 38 + **Source:** `think/cortex.py` 39 39 **Events:** `request`, `start`, `thinking`, `tool_start`, `tool_end`, `finish`, `error`, `agent_updated`, `info`, `status` 40 40 **Details:** See [CORTEX.md](CORTEX.md) for agent lifecycle, personas, and event schemas 41 41 ··· 194 194 195 195 For agent requests, use the cortex client: 196 196 ```python 197 - from muse.cortex_client import cortex_request 197 + from think.cortex_client import cortex_request 198 198 agent_id = cortex_request(prompt="...", persona="default") 199 199 ``` 200 200 201 - See `muse/cortex_client.py` for the full API. 201 + See `think/cortex_client.py` for the full API.
+8 -8
docs/CORTEX.md
··· 30 30 31 31 ## Request Format 32 32 33 - Requests are created via `cortex_request()` from `muse.cortex_client`, which broadcasts to Callosum. The request message follows this format: 33 + Requests are created via `cortex_request()` from `think.cortex_client`, which broadcasts to Callosum. The request message follows this format: 34 34 35 35 ```json 36 36 { 37 37 "event": "request", 38 38 "ts": 1234567890123, // Required: millisecond timestamp (must match filename) 39 39 "prompt": "Analyze this code for security issues", // Required: the task or question 40 - "persona": "default", // Optional: agent persona from muse/agents/*.md 40 + "persona": "default", // Optional: agent persona from think/agents/*.md 41 41 "provider": "openai", // Optional: override provider (openai, google, anthropic) 42 42 "max_tokens": 8192, // Optional: token limit (if supported) 43 43 "disable_mcp": false, // Optional: disable MCP tools for this request ··· 225 225 226 226 ## Agent Personas 227 227 228 - Agents use persona configurations stored in the `muse/agents/` directory. Each persona is a `.md` file containing: 228 + Agents use persona configurations stored in the `think/agents/` directory. Each persona is a `.md` file containing: 229 229 - JSON frontmatter with metadata and configuration 230 230 - The agent-specific prompt and instructions in the content 231 231 ··· 238 238 3. Request parameters override persona defaults in the merged configuration 239 239 4. The full configuration is passed to the agent process 240 240 241 - Personas define specialized behaviors, tool usage patterns, and facet expertise. Available personas can be discovered using the `get_agents()` function or by listing files in the `muse/agents/` directory. 241 + Personas define specialized behaviors, tool usage patterns, and facet expertise. Available personas can be discovered using the `get_agents()` function or by listing files in the `think/agents/` directory. 242 242 243 243 ### Persona Configuration Options 244 244 ··· 293 293 - **OpenAI, Anthropic, Google**: Full MCP tool support via HTTP transport 294 294 295 295 ### Tool Discovery 296 - MCP tools are provided by the `muse.mcp_tools` FastMCP server, which: 296 + MCP tools are provided by the `think.mcp_tools` FastMCP server, which: 297 297 - Runs inside Cortex as a background HTTP service 298 298 - Shares its URL directly with agent runs (`mcp_server_url`) so no discovery file is needed 299 299 - Exposes journal search and retrieval capabilities ··· 303 303 304 304 The system supports multiple AI providers, each implementing the same event interface: 305 305 306 - - **OpenAI** (`muse/providers/openai.py`): GPT models with OpenAI Agents SDK 307 - - **Google** (`muse/providers/google.py`): Gemini models with Google AI SDK 308 - - **Anthropic** (`muse/providers/anthropic.py`): Claude models with Anthropic SDK 306 + - **OpenAI** (`think/providers/openai.py`): GPT models with OpenAI Agents SDK 307 + - **Google** (`think/providers/google.py`): Gemini models with Google AI SDK 308 + - **Anthropic** (`think/providers/anthropic.py`): Claude models with Anthropic SDK 309 309 310 310 All providers: 311 311 - Emit JSON events to stdout (one per line)
+2 -2
docs/JOURNAL.md
··· 299 299 | 2 | flash | Balanced performance and cost (default) | 300 300 | 3 | lite | Fastest and cheapest, for simple tasks | 301 301 302 - System defaults map tiers to models for each provider. See `muse/models.py` for current tier-to-model mappings (`PROVIDER_DEFAULTS` constant). 302 + System defaults map tiers to models for each provider. See `think/models.py` for current tier-to-model mappings (`PROVIDER_DEFAULTS` constant). 303 303 304 304 If a requested tier is unavailable for a provider, the system falls back to more capable tiers (e.g., tier 3 → tier 2 → tier 1). 305 305 ··· 724 724 - `unread` – Boolean flag for unread status (used for badge counts) 725 725 - `archived` – Boolean flag for archived status 726 726 727 - **Synthetic agents** are created by background processes using `muse.cortex_client.create_synthetic_agent()`. These appear as completed agents in the chat interface but only contain a single `finish` event: 727 + **Synthetic agents** are created by background processes using `think.cortex_client.create_synthetic_agent()`. These appear as completed agents in the chat interface but only contain a single `finish` event: 728 728 729 729 ```jsonl 730 730 {"event": "finish", "result": "Message content in markdown format", "ts": 1755450767962}
-54
docs/MUSE.md
··· 1 - # Muse Module 2 - 3 - AI agent system and MCP tooling for solstone. 4 - 5 - ## Commands 6 - 7 - | Command | Purpose | 8 - |---------|---------| 9 - | `sol cortex` | Agent orchestration service | 10 - | `sol mcp` | MCP tool server (runs inside Cortex) | 11 - | `sol agents` | Direct agent invocation (testing only) | 12 - 13 - ## Architecture 14 - 15 - ``` 16 - Cortex (orchestrator) 17 - ├── Callosum connection (events) 18 - ├── MCP HTTP server (tools) 19 - └── Agent subprocess management 20 - 21 - Providers (openai, google, anthropic) 22 - ``` 23 - 24 - ## Providers 25 - 26 - | Provider | Module | Features | 27 - |----------|--------|----------| 28 - | OpenAI | `muse/providers/openai.py` | GPT models via Agents SDK | 29 - | Google | `muse/providers/google.py` | Gemini models | 30 - | Anthropic | `muse/providers/anthropic.py` | Claude via Anthropic SDK | 31 - 32 - Providers implement `generate()`, `agenerate()`, and `run_agent()` functions. See [PROVIDERS.md](PROVIDERS.md) for implementation details. 33 - 34 - ## Key Components 35 - 36 - - **cortex.py** - Central agent manager, file watcher, event distribution 37 - - **cortex_client.py** - Client functions: `cortex_request()`, `cortex_agents()` 38 - - **mcp.py** - FastMCP server with journal search tools 39 - - **agents.py** - CLI entry point and shared event types 40 - - **models.py** - Unified `generate()`/`agenerate()` API, provider routing, token logging 41 - - **batch.py** - `Batch` class for concurrent LLM requests with dynamic queuing 42 - 43 - ## Agent Personas 44 - 45 - System prompts in `muse/agents/*.txt` with metadata in matching `.json` files. Apps can add custom agents in `apps/{app}/agents/`. 46 - 47 - JSON metadata supports `title`, `provider`, `model`, `tools`, `schedule`, `priority`, `multi_facet`, and `instructions` keys. See [APPS.md](APPS.md#instructions-configuration) for the `instructions` schema that controls system prompts, facet context, and source filtering. 48 - 49 - ## Documentation 50 - 51 - - [PROVIDERS.md](PROVIDERS.md) - Provider implementation guide 52 - - [CORTEX.md](CORTEX.md) - Full API, event schemas, request format 53 - - [CALLOSUM.md](CALLOSUM.md) - Message bus protocol 54 - - [THINK.md](THINK.md) - Cortex usage examples
+2 -2
docs/PROMPT_TEMPLATES.md
··· 136 136 Agent prompts are split into two parts: 137 137 138 138 1. **System instruction** - `think/journal.md` (shared across all agents, cacheable) 139 - 2. **User instruction** - Agent-specific `.md` file (e.g., `muse/agents/default.md`) 139 + 2. **User instruction** - Agent-specific `.md` file (e.g., `think/agents/default.md`) 140 140 141 141 The system instruction establishes the journal partnership context. The user instruction defines the agent's specific role and capabilities. 142 142 ··· 186 186 | Template files | `think/templates/*.md` | 187 187 | Test coverage | `tests/test_template_substitution.py` | 188 188 | Insight prompts | `think/insights/*.md` | 189 - | Agent prompts | `muse/agents/*.md` | 189 + | Agent prompts | `think/agents/*.md` |
+23 -23
docs/PROVIDERS.md
··· 1 1 # Provider Implementation Guide 2 2 3 - Guide for implementing new AI providers in the muse module. 3 + Guide for implementing new AI providers in the think module. 4 4 5 - For a high-level overview of the muse module, see [MUSE.md](MUSE.md). 5 + For a high-level overview of the think module, see [THINK.md](THINK.md). 6 6 7 7 ## Required Exports 8 8 9 - Each provider module in `muse/providers/` must export three functions: 9 + Each provider module in `think/providers/` must export three functions: 10 10 11 11 | Function | Purpose | 12 12 |----------|---------| ··· 14 14 | `agenerate()` | Asynchronous text generation | 15 15 | `run_agent()` | Agentic execution with MCP tool support | 16 16 17 - See `muse/providers/__init__.py` for the canonical export list and `muse/providers/google.py` as a reference implementation. 17 + See `think/providers/__init__.py` for the canonical export list and `think/providers/google.py` as a reference implementation. 18 18 19 19 Each provider module must also define `__all__` exporting these three functions. 20 20 ··· 49 49 return _client 50 50 ``` 51 51 52 - **Settings app integration:** Add your provider to `PROVIDER_METADATA` in `muse/providers/__init__.py` with `label` and `env_key` fields. The settings UI dynamically builds provider dropdowns from the registry. Add corresponding API key UI fields in `apps/settings/workspace.html` for user configuration. 52 + **Settings app integration:** Add your provider to `PROVIDER_METADATA` in `think/providers/__init__.py` with `label` and `env_key` fields. The settings UI dynamically builds provider dropdowns from the registry. Add corresponding API key UI fields in `apps/settings/workspace.html` for user configuration. 53 53 54 54 ## generate() / agenerate() 55 55 56 - These functions handle direct LLM text generation. The unified API in `muse/models.py` routes requests to provider-specific implementations. 56 + These functions handle direct LLM text generation. The unified API in `think/models.py` routes requests to provider-specific implementations. 57 57 58 58 **Function signature:** 59 59 ```python ··· 106 106 ) -> str: 107 107 ``` 108 108 109 - **Config dict fields** (see `muse/agents.py` `main_async()` for routing logic): 109 + **Config dict fields** (see `think/agents.py` `main_async()` for routing logic): 110 110 - `prompt`: User's input (required) 111 111 - `model`: Model identifier 112 112 - `max_tokens`: Output token limit ··· 121 121 122 122 **Event emission:** 123 123 124 - Providers must emit events via the `on_event` callback. See `muse/agents.py` for TypedDict definitions: 124 + Providers must emit events via the `on_event` callback. See `think/agents.py` for TypedDict definitions: 125 125 126 126 | Event | When | 127 127 |-------|------| ··· 132 132 | `FinishEvent` | Agent run completes successfully | 133 133 | `ErrorEvent` | Error occurs | 134 134 135 - Use `JSONEventCallback` from `muse/agents.py` to wrap the callback and auto-add timestamps. 135 + Use `JSONEventCallback` from `think/agents.py` to wrap the callback and auto-add timestamps. 136 136 137 137 **Finish event format:** 138 138 ··· 176 176 177 177 When `continue_from` is provided, load conversation history using: 178 178 ```python 179 - from muse.agents import parse_agent_events_to_turns 179 + from think.agents import parse_agent_events_to_turns 180 180 181 181 turns = parse_agent_events_to_turns(config["continue_from"]) 182 182 # Returns [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}, ...] ··· 187 187 All generation calls must log token usage for cost tracking. 188 188 189 189 ```python 190 - from muse.models import log_token_usage 190 + from think.models import log_token_usage 191 191 192 192 log_token_usage(model=model, usage=usage_dict, context=context) 193 193 ``` ··· 238 238 239 239 **Dynamic discovery:** Categories and agents can express their own tier/label/group in their JSON configs: 240 240 - Categories: `observe/categories/*.json` - add `tier`, `label`, `group` fields 241 - - System agents: `muse/agents/*.json` - add `tier`, `label`, `group` fields 241 + - System agents: `think/agents/*.json` - add `tier`, `label`, `group` fields 242 242 - App agents: `apps/*/agents/*.json` - add `tier`, `label`, `group` fields 243 243 244 244 These are discovered at runtime and merged with static defaults. Use `get_context_registry()` to get the complete context map including discovered entries. 245 245 246 - See `CONTEXT_DEFAULTS` in `muse/models.py` for static context patterns (non-discoverable contexts like `observe.detect.*`, `insight.*`). 246 + See `CONTEXT_DEFAULTS` in `think/models.py` for static context patterns (non-discoverable contexts like `observe.detect.*`, `insight.*`). 247 247 248 - **Resolution** (handled by `muse/models.py` `resolve_provider()`): 248 + **Resolution** (handled by `think/models.py` `resolve_provider()`): 249 249 1. Exact match in journal.json `providers.contexts` 250 250 2. Glob pattern match (fnmatch) with specificity ranking 251 251 3. Dynamic context registry (static defaults + discovered categories/agents) ··· 278 278 - 2 = FLASH (balanced) 279 279 - 3 = LITE (fast/cheap) 280 280 281 - See `fixtures/journal/config/journal.json` for a complete example and `muse/models.py` `PROVIDER_DEFAULTS` for tier-to-model mappings. 281 + See `fixtures/journal/config/journal.json` for a complete example and `think/models.py` `PROVIDER_DEFAULTS` for tier-to-model mappings. 282 282 283 283 ## Testing 284 284 ··· 302 302 303 303 ## Batch Processing 304 304 305 - The `Batch` class in `muse/batch.py` automatically works with all providers via the unified `agenerate()` API. No provider-specific batch implementation is needed - just ensure your `agenerate()` works correctly. 305 + The `Batch` class in `think/batch.py` automatically works with all providers via the unified `agenerate()` API. No provider-specific batch implementation is needed - just ensure your `agenerate()` works correctly. 306 306 307 307 ## OpenAI-Compatible Providers 308 308 ··· 322 322 ## Checklist for New Providers 323 323 324 324 **Core implementation:** 325 - 1. Create `muse/providers/<name>.py` with `__all__ = ["generate", "agenerate", "run_agent"]` 325 + 1. Create `think/providers/<name>.py` with `__all__ = ["generate", "agenerate", "run_agent"]` 326 326 2. Implement `generate()`, `agenerate()`, `run_agent()` following signatures above 327 327 328 - **Model constants** in `muse/models.py`: 328 + **Model constants** in `think/models.py`: 329 329 3. Add model constants using the pattern `{PROVIDER}_{TIER}` (e.g., `DO_LLAMA_70B`, `DO_MISTRAL_NEMO`) 330 330 - Existing examples: `GEMINI_FLASH`, `GPT_5`, `CLAUDE_SONNET_4` 331 331 4. Add provider tier mappings to `PROVIDER_DEFAULTS` dict 332 332 5. Update `get_model_provider()` to detect your models by prefix (critical for cost tracking) 333 333 334 334 **Routing:** 335 - 6. Add `elif provider == "<name>"` cases in `muse/models.py`: 335 + 6. Add `elif provider == "<name>"` cases in `think/models.py`: 336 336 - `generate()` function (around line 622) 337 337 - `agenerate()` function (around line 700) 338 - 7. Add routing case in `muse/agents.py` `main_async()` (around line 331) 338 + 7. Add routing case in `think/agents.py` `main_async()` (around line 331) 339 339 340 340 **Settings UI:** 341 - 8. Add provider to `PROVIDER_METADATA` in `muse/providers/__init__.py` with `label` and `env_key` 341 + 8. Add provider to `PROVIDER_METADATA` in `think/providers/__init__.py` with `label` and `env_key` 342 342 9. Add API key UI field in `apps/settings/workspace.html` 343 343 344 344 **Testing:** ··· 347 347 12. Add test contexts to `fixtures/journal/config/journal.json` 348 348 349 349 **Documentation:** 350 - 13. Update `muse/providers/__init__.py` docstring 351 - 14. Update `docs/MUSE.md` providers table 350 + 13. Update `think/providers/__init__.py` docstring 351 + 14. Update `docs/THINK.md` providers table 352 352 15. Update `docs/CORTEX.md` valid provider values
+60 -5
docs/THINK.md
··· 89 89 To spawn agents programmatically, use the cortex_client functions: 90 90 91 91 ```python 92 - from muse.cortex_client import cortex_request 92 + from think.cortex_client import cortex_request 93 93 from think.callosum import CallosumConnection 94 94 95 95 # Create a request ··· 128 128 129 129 ### Provider modules 130 130 131 - Each provider lives in `muse/providers/` and exposes a common interface: 131 + Each provider lives in `think/providers/` and exposes a common interface: 132 132 133 133 - `generate()` - Sync text generation 134 134 - `agenerate()` - Async text generation 135 135 - `run_agent()` - Agent execution with MCP tools and event streaming 136 136 137 - For direct LLM calls, use `muse.models.generate()` or `muse.models.agenerate()` 137 + For direct LLM calls, use `think.models.generate()` or `think.models.agenerate()` 138 138 which automatically routes to the configured provider based on context. 139 139 140 140 ## Insight map keys ··· 157 157 158 158 ### Using cortex_client 159 159 160 - The `muse.cortex_client` module provides functions for interacting with Cortex: 160 + The `think.cortex_client` module provides functions for interacting with Cortex: 161 161 162 162 ```python 163 - from muse.cortex_client import cortex_request, cortex_agents 163 + from think.cortex_client import cortex_request, cortex_agents 164 164 165 165 # Create an agent request 166 166 request_file = cortex_request( ··· 173 173 agents_info = cortex_agents(limit=10, agent_type="live") 174 174 print(f"Found {agents_info['live_count']} running agents") 175 175 ``` 176 + # Muse Module 177 + 178 + AI agent system and MCP tooling for solstone. 179 + 180 + ## Commands 181 + 182 + | Command | Purpose | 183 + |---------|---------| 184 + | `sol cortex` | Agent orchestration service | 185 + | `sol mcp` | MCP tool server (runs inside Cortex) | 186 + | `sol agents` | Direct agent invocation (testing only) | 187 + 188 + ## Architecture 189 + 190 + ``` 191 + Cortex (orchestrator) 192 + ├── Callosum connection (events) 193 + ├── MCP HTTP server (tools) 194 + └── Agent subprocess management 195 + 196 + Providers (openai, google, anthropic) 197 + ``` 198 + 199 + ## Providers 200 + 201 + | Provider | Module | Features | 202 + |----------|--------|----------| 203 + | OpenAI | `think/providers/openai.py` | GPT models via Agents SDK | 204 + | Google | `think/providers/google.py` | Gemini models | 205 + | Anthropic | `think/providers/anthropic.py` | Claude via Anthropic SDK | 206 + 207 + Providers implement `generate()`, `agenerate()`, and `run_agent()` functions. See [PROVIDERS.md](PROVIDERS.md) for implementation details. 208 + 209 + ## Key Components 210 + 211 + - **cortex.py** - Central agent manager, file watcher, event distribution 212 + - **cortex_client.py** - Client functions: `cortex_request()`, `cortex_agents()` 213 + - **mcp.py** - FastMCP server with journal search tools 214 + - **agents.py** - CLI entry point and shared event types 215 + - **models.py** - Unified `generate()`/`agenerate()` API, provider routing, token logging 216 + - **batch.py** - `Batch` class for concurrent LLM requests with dynamic queuing 217 + 218 + ## Agent Personas 219 + 220 + System prompts in `think/agents/*.md` (markdown with JSON frontmatter). Apps can add custom agents in `apps/{app}/agents/`. 221 + 222 + JSON metadata supports `title`, `provider`, `model`, `tools`, `schedule`, `priority`, `multi_facet`, and `instructions` keys. See [APPS.md](APPS.md#instructions-configuration) for the `instructions` schema that controls system prompts, facet context, and source filtering. 223 + 224 + ## Documentation 225 + 226 + - [PROVIDERS.md](PROVIDERS.md) - Provider implementation guide 227 + - [CORTEX.md](CORTEX.md) - Full API, event schemas, request format 228 + - [CALLOSUM.md](CALLOSUM.md) - Message bus protocol 229 + - [THINK.md](THINK.md) - Cortex usage examples 230 +
-1
muse/README.md
··· 1 - ../docs/MUSE.md
-4
muse/__init__.py
··· 1 - # SPDX-License-Identifier: AGPL-3.0-only 2 - # Copyright (c) 2026 sol pbc 3 - 4 - """Muse - AI Agent orchestration and execution system for solstone."""
+2 -2
muse/agents.py think/agents.py
··· 18 18 19 19 from think.utils import setup_cli 20 20 21 - LOG = logging.getLogger("muse.agents") 21 + LOG = logging.getLogger("think.agents") 22 22 23 23 24 24 def setup_logging(verbose: bool = False) -> logging.Logger: ··· 203 203 """ 204 204 import logging 205 205 206 - from muse.cortex_client import read_agent_events 206 + from think.cortex_client import read_agent_events 207 207 208 208 logger = logging.getLogger(__name__) 209 209
muse/agents/daily_news.md think/agents/daily_news.md
muse/agents/decisionalizer.md think/agents/decisionalizer.md
muse/agents/default.md think/agents/default.md
muse/agents/facet_newsletter.md think/agents/facet_newsletter.md
muse/agents/joke_bot.md think/agents/joke_bot.md
muse/batch.py think/batch.py
+7 -7
muse/cortex.py think/cortex.py
··· 98 98 if self.mcp_thread and self.mcp_thread.is_alive(): 99 99 return 100 100 101 - from muse.mcp import mcp 101 + from think.mcp import mcp 102 102 103 103 host = os.getenv("SOLSTONE_MCP_HOST", "127.0.0.1") 104 104 port = int(os.getenv("SOLSTONE_MCP_PORT", "6270")) ··· 291 291 # Validate and link continue_from if specified 292 292 continue_from = request.get("continue_from") 293 293 if continue_from: 294 - from muse.cortex_client import get_agent_status 294 + from think.cortex_client import get_agent_status 295 295 296 296 status = get_agent_status(continue_from) 297 297 if status != "completed": ··· 317 317 self.logger.info(f"Linked continuation: {continue_from} -> {agent_id}") 318 318 319 319 # Load persona and merge with request 320 - from muse.mcp import get_tools 320 + from think.mcp import get_tools 321 321 from think.utils import get_agent 322 322 323 323 persona = request.get("persona", "default") ··· 331 331 332 332 # Resolve provider and model from context 333 333 # Context format: agent.{app}.{name} where app="system" for system agents 334 - from muse.models import resolve_model_for_provider, resolve_provider 334 + from think.models import resolve_model_for_provider, resolve_provider 335 335 336 336 if ":" in persona: 337 337 app, name = persona.split(":", 1) ··· 549 549 usage_data = event.get("usage") 550 550 if usage_data and original_request: 551 551 try: 552 - from muse.models import log_token_usage 552 + from think.models import log_token_usage 553 553 554 554 model = original_request.get("model", "unknown") 555 555 persona = original_request.get("persona", "unknown") ··· 754 754 ) -> None: 755 755 """Spawn a handoff agent from a completed agent's result.""" 756 756 try: 757 - from muse.cortex_client import cortex_request 757 + from think.cortex_client import cortex_request 758 758 759 759 if not handoff: 760 760 self.logger.debug( ··· 883 883 from datetime import datetime 884 884 from typing import Any 885 885 886 - from muse.models import calc_token_cost 886 + from think.models import calc_token_cost 887 887 888 888 _ = context # Reserved for future context support 889 889 meta: dict[str, Any] = {}
+1 -1
muse/cortex_client.py think/cortex_client.py
··· 8 8 import os 9 9 import time 10 10 from pathlib import Path 11 - from typing import Any, Callable, Dict, Optional 11 + from typing import Any, Dict, Optional 12 12 13 13 from think.callosum import callosum_send 14 14 from think.utils import get_journal
+7 -7
muse/mcp.py think/mcp.py
··· 5 5 """MCP server for solstone journal assistant. 6 6 7 7 This module creates the FastMCP server instance and registers all tools and resources 8 - from the muse/tools/ and muse/resources/ directories. 8 + from the think/tools/ and think/resources/ directories. 9 9 10 - Tool modules are located in muse/tools/ and contain plain functions. 11 - Resource handlers are located in muse/resources/ and use @mcp.resource decorators. 10 + Tool modules are located in think/tools/ and contain plain functions. 11 + Resource handlers are located in think/resources/ and use @mcp.resource decorators. 12 12 """ 13 13 14 14 from typing import Any, Callable, TypeVar ··· 55 55 56 56 # Import and register tool modules 57 57 # These imports trigger the registration of tools via the @register_tool decorator 58 - from muse.tools import facets, search 58 + from think.tools import facets, search 59 59 60 60 # Register search tools 61 61 search_journal = register_tool(annotations=HINTS)(search.search_journal) ··· 66 66 facet_news = register_tool(annotations=HINTS)(facets.facet_news) 67 67 68 68 # Register resource tool (get_resource moved from messaging) 69 - from muse.tools.messaging import get_resource as get_resource_impl 69 + from think.tools.messaging import get_resource as get_resource_impl 70 70 71 71 get_resource = register_tool(annotations=HINTS)(get_resource_impl) 72 72 73 73 # Import resource modules - these self-register via @mcp.resource decorators 74 - from muse.resources import insights, media, transcripts # noqa: F401 74 + from think.resources import insights, media, transcripts # noqa: F401 75 75 76 76 77 77 # Phase 2: App-level tool discovery ··· 85 85 Apps can define tools using the @register_tool decorator: 86 86 87 87 # apps/myapp/tools.py 88 - from muse.mcp import register_tool, HINTS 88 + from think.mcp import register_tool, HINTS 89 89 90 90 @register_tool(annotations=HINTS) 91 91 def my_tool(arg: str) -> dict:
+8 -8
muse/models.py think/models.py
··· 112 112 # - app.chat.title -> apps module, chat app, title operation 113 113 # 114 114 # DYNAMIC DISCOVERY: 115 - # Categories (observe/categories/*.json) and agents (muse/agents/*.json, 115 + # Categories (observe/categories/*.json) and agents (think/agents/*.json, 116 116 # apps/*/agents/*.json) can express tier/label/group in their JSON configs. 117 117 # These are discovered at runtime and merged with the static defaults below. 118 118 # 119 119 # When adding new contexts: 120 - # 1. Use module prefix matching the package (observe, think, app, muse) 120 + # 1. Use module prefix matching the package (observe, think, app) 121 121 # 2. Add specific operations as suffixes when granular control is needed 122 122 # 3. Use wildcards sparingly - prefer explicit entries for clarity 123 123 # 4. If not listed here, context falls back to DEFAULT_TIER (FLASH) ··· 217 217 def _discover_agent_contexts() -> Dict[str, Dict[str, Any]]: 218 218 """Discover agent context defaults from JSON config files. 219 219 220 - Scans system agents (muse/agents/*.json) and app agents (apps/*/agents/*.json) 220 + Scans system agents (think/agents/*.md) and app agents (apps/*/agents/*.md) 221 221 for tier/label/group metadata. This is a lightweight scan that only reads 222 222 the JSON metadata, not the full agent configuration. 223 223 ··· 229 229 """ 230 230 contexts = {} 231 231 232 - # System agents from muse/agents/ 232 + # System agents from think/agents/ 233 233 agents_dir = Path(__file__).parent / "agents" 234 234 if agents_dir.exists(): 235 235 for md_path in agents_dir.glob("*.md"): ··· 625 625 line_num = caller_frame.f_lineno 626 626 627 627 # Clean up module name 628 - for prefix in ["think.", "observe.", "convey.", "muse."]: 628 + for prefix in ["think.", "observe.", "convey."]: 629 629 if module_name.startswith(prefix): 630 630 module_name = module_name[len(prefix) :] 631 631 break ··· 964 964 ValueError 965 965 If the resolved provider is not supported. 966 966 """ 967 - from muse.providers import get_provider_module 967 + from think.providers import get_provider_module 968 968 969 969 # Allow model override via kwargs (used by callers with explicit model selection) 970 970 model_override = kwargs.pop("model", None) ··· 1038 1038 ValueError 1039 1039 If the resolved provider is not supported. 1040 1040 """ 1041 - from muse.providers import get_provider_module 1041 + from think.providers import get_provider_module 1042 1042 1043 1043 # Allow model override via kwargs (used by Batch for explicit model selection) 1044 1044 model_override = kwargs.pop("model", None) ··· 1070 1070 "DEFAULT_PROVIDER", 1071 1071 "CONTEXT_DEFAULTS", 1072 1072 "get_context_registry", 1073 - # Model constants (used by muse backends for defaults) 1073 + # Model constants (used by provider backends for defaults) 1074 1074 "GEMINI_FLASH", 1075 1075 "GPT_5", 1076 1076 "CLAUDE_SONNET_4",
+4 -4
muse/providers/__init__.py think/providers/__init__.py
··· 1 1 # SPDX-License-Identifier: AGPL-3.0-only 2 2 # Copyright (c) 2026 sol pbc 3 3 4 - """AI provider backends for muse. 4 + """AI provider backends for think. 5 5 6 6 This package contains provider-specific implementations for LLM generation 7 7 and agent execution. Each provider module exposes: ··· 31 31 # --------------------------------------------------------------------------- 32 32 33 33 PROVIDER_REGISTRY: Dict[str, str] = { 34 - "google": "muse.providers.google", 35 - "openai": "muse.providers.openai", 36 - "anthropic": "muse.providers.anthropic", 34 + "google": "think.providers.google", 35 + "openai": "think.providers.openai", 36 + "anthropic": "think.providers.anthropic", 37 37 } 38 38 39 39 # ---------------------------------------------------------------------------
+5 -5
muse/providers/anthropic.py think/providers/anthropic.py
··· 47 47 ToolUseBlock, 48 48 ) 49 49 50 - from muse.models import CLAUDE_SONNET_4 50 + from think.models import CLAUDE_SONNET_4 51 51 from think.utils import create_mcp_client 52 52 53 53 from ..agents import JSONEventCallback, ThinkingEvent ··· 544 544 545 545 See module docstring for parameter details. 546 546 """ 547 - from muse.models import log_token_usage 547 + from think.models import log_token_usage 548 548 549 549 client = _get_anthropic_client() 550 550 messages = _convert_contents_to_messages(contents) ··· 578 578 if timeout_s: 579 579 request_kwargs["timeout"] = timeout_s 580 580 581 - from muse.models import IncompleteJSONError 581 + from think.models import IncompleteJSONError 582 582 583 583 response = client.messages.create(**request_kwargs) 584 584 ··· 618 618 619 619 See module docstring for parameter details. 620 620 """ 621 - from muse.models import log_token_usage 621 + from think.models import log_token_usage 622 622 623 623 client = _get_async_anthropic_client() 624 624 messages = _convert_contents_to_messages(contents) ··· 652 652 if timeout_s: 653 653 request_kwargs["timeout"] = timeout_s 654 654 655 - from muse.models import IncompleteJSONError 655 + from think.models import IncompleteJSONError 656 656 657 657 response = await client.messages.create(**request_kwargs) 658 658
+4 -4
muse/providers/google.py think/providers/google.py
··· 43 43 from google.genai import errors as google_errors 44 44 from google.genai import types 45 45 46 - from muse.models import GEMINI_FLASH 46 + from think.models import GEMINI_FLASH 47 47 from think.utils import create_mcp_client 48 48 49 49 from ..agents import JSONEventCallback, ThinkingEvent ··· 142 142 if response is None: 143 143 raise ValueError("No response from model") 144 144 145 - from muse.models import IncompleteJSONError 145 + from think.models import IncompleteJSONError 146 146 147 147 # Check for error conditions in candidates 148 148 finish_reason = _extract_finish_reason(response) ··· 291 291 292 292 See module docstring for parameter details. 293 293 """ 294 - from muse.models import log_token_usage 294 + from think.models import log_token_usage 295 295 296 296 cached_content = kwargs.get("cached_content") 297 297 client = kwargs.get("client") ··· 336 336 337 337 See module docstring for parameter details. 338 338 """ 339 - from muse.models import log_token_usage 339 + from think.models import log_token_usage 340 340 341 341 cached_content = kwargs.get("cached_content") 342 342 client = kwargs.get("client")
+6 -6
muse/providers/openai.py think/providers/openai.py
··· 67 67 68 68 # Agent configuration is now loaded via get_agent() in cortex.py 69 69 70 - from muse.models import GPT_5 70 + from think.models import GPT_5 71 71 72 72 from ..agents import JSONEventCallback, ThinkingEvent 73 73 ··· 104 104 _DEFAULT_MAX_TOKENS = 16384 105 105 _DEFAULT_MAX_TURNS = 64 106 106 107 - LOG = logging.getLogger("muse.providers.openai") 107 + LOG = logging.getLogger("think.providers.openai") 108 108 109 109 # Default reasoning config for all GPT-5 models 110 110 _DEFAULT_REASONING = Reasoning(effort="medium", summary="detailed") ··· 595 595 596 596 See module docstring for parameter details. 597 597 """ 598 - from muse.models import log_token_usage 598 + from think.models import log_token_usage 599 599 600 600 client = _get_openai_client() 601 601 messages = _convert_contents_to_messages(contents, system_instruction) ··· 616 616 if timeout_s: 617 617 request_kwargs["timeout"] = timeout_s 618 618 619 - from muse.models import IncompleteJSONError 619 + from think.models import IncompleteJSONError 620 620 621 621 response = client.chat.completions.create(**request_kwargs) 622 622 ··· 656 656 657 657 See module docstring for parameter details. 658 658 """ 659 - from muse.models import log_token_usage 659 + from think.models import log_token_usage 660 660 661 661 client = _get_async_openai_client() 662 662 messages = _convert_contents_to_messages(contents, system_instruction) ··· 677 677 if timeout_s: 678 678 request_kwargs["timeout"] = timeout_s 679 679 680 - from muse.models import IncompleteJSONError 680 + from think.models import IncompleteJSONError 681 681 682 682 response = await client.chat.completions.create(**request_kwargs) 683 683
muse/resources/__init__.py think/resources/__init__.py
+1 -1
muse/resources/insights.py think/resources/insights.py
··· 7 7 8 8 from fastmcp.resources import TextResource 9 9 10 - from muse.mcp import mcp 10 + from think.mcp import mcp 11 11 from think.utils import get_journal 12 12 13 13
+1 -1
muse/resources/media.py think/resources/media.py
··· 7 7 8 8 from fastmcp.resources import FileResource 9 9 10 - from muse.mcp import mcp 10 + from think.mcp import mcp 11 11 from think.utils import get_journal, get_raw_file 12 12 13 13
+1 -1
muse/resources/transcripts.py think/resources/transcripts.py
··· 7 7 8 8 from fastmcp.resources import TextResource 9 9 10 - from muse.mcp import mcp 11 10 from think.cluster import cluster_range 11 + from think.mcp import mcp 12 12 13 13 14 14 def _get_transcript_resource(
muse/tools/__init__.py think/tools/__init__.py
+1 -1
muse/tools/facets.py think/tools/facets.py
··· 3 3 4 4 """MCP tools for facet management. 5 5 6 - Note: These functions are registered as MCP tools by muse/mcp.py 6 + Note: These functions are registered as MCP tools by think/mcp.py 7 7 They can also be imported and called directly for testing or internal use. 8 8 """ 9 9
+2 -2
muse/tools/messaging.py think/tools/messaging.py
··· 3 3 4 4 """MCP tools for resource operations. 5 5 6 - Note: These functions are registered as MCP tools by muse/mcp.py 6 + Note: These functions are registered as MCP tools by think/mcp.py 7 7 They can also be imported and called directly for testing or internal use. 8 8 """ 9 9 ··· 34 34 text resources. 35 35 """ 36 36 # Import here to avoid circular import at module load time 37 - from muse.mcp import mcp 37 + from think.mcp import mcp 38 38 39 39 try: 40 40 # Use the resource manager directly - bypasses Context initialization issues
+1 -1
muse/tools/search.py think/tools/search.py
··· 3 3 4 4 """MCP tools for search operations. 5 5 6 - Note: These functions are registered as MCP tools by muse/mcp.py 6 + Note: These functions are registered as MCP tools by think/mcp.py 7 7 They can also be imported and called directly for testing or internal use. 8 8 """ 9 9
+2 -2
observe/describe.py
··· 373 373 output_path : Optional[Path] 374 374 Path to write JSONL output (when None, no output file is written) 375 375 """ 376 - from muse.batch import Batch 377 - from muse.models import resolve_provider 376 + from think.batch import Batch 377 + from think.models import resolve_provider 378 378 379 379 # Load config for max_extractions 380 380 config = get_config()
+1 -1
observe/enrich.py
··· 21 21 import numpy as np 22 22 from google.genai import types 23 23 24 - from muse.models import generate 25 24 from observe.utils import audio_to_flac_bytes 25 + from think.models import generate 26 26 from think.utils import load_prompt 27 27 28 28 logger = logging.getLogger(__name__)
+1 -1
observe/extract.py
··· 204 204 Exception 205 205 If AI selection fails (will trigger fallback in caller). 206 206 """ 207 - from muse.models import generate 207 + from think.models import generate 208 208 from think.utils import load_prompt 209 209 210 210 # Build extraction guidance with config overrides
+3 -3
observe/transcribe/gemini.py
··· 25 25 import numpy as np 26 26 from google.genai import types 27 27 28 - from muse.models import generate 29 28 from observe.utils import audio_to_flac_bytes 29 + from think.models import generate 30 30 from think.utils import load_prompt 31 31 32 32 logger = logging.getLogger(__name__) ··· 218 218 types.Part.from_bytes(data=audio_bytes, mime_type="audio/flac"), 219 219 ] 220 220 221 - # Call Gemini via muse.models.generate() 221 + # Call Gemini via think.models.generate() 222 222 response_text = generate( 223 223 contents=contents, 224 224 context="observe.transcribe.gemini", ··· 268 268 Returns: 269 269 Dict with model info for JSONL metadata 270 270 """ 271 - # Model is resolved by muse.models based on context 271 + # Model is resolved by think.models based on context 272 272 # We report "gemini" as the model family 273 273 return { 274 274 "model": "gemini",
+2 -2
observe/transcribe/main.py
··· 33 33 - Automatically loads recent entity names as custom vocabulary for improved recognition 34 34 35 35 Gemini backend settings (transcribe.gemini): 36 - - No configuration needed (model resolved by muse.models context system) 36 + - No configuration needed (model resolved by think.models context system) 37 37 - Includes speaker diarization 38 38 39 39 Platform optimizations (Whisper): ··· 741 741 if entity_names: 742 742 backend_config["entities"] = entity_names 743 743 elif backend == "gemini": 744 - # Gemini backend - model resolved by muse.models based on context 744 + # Gemini backend - model resolved by think.models based on context 745 745 # Entity names handled by enrich step, not passed to transcription 746 746 backend_config = {} 747 747 else:
+2 -3
pyproject.toml
··· 98 98 "Bug Tracker" = "https://github.com/solpbc/solstone/issues" 99 99 100 100 [tool.setuptools.packages.find] 101 - include = ["apps*", "think*", "convey*", "observe*", "muse*"] 101 + include = ["apps*", "think*", "convey*", "observe*"] 102 102 103 103 [tool.setuptools] 104 104 py-modules = ["sol"] 105 105 106 106 [tool.setuptools.package-data] 107 107 apps = ["*/templates/*.html", "*/agents/*.md", "*/insights/*.md"] 108 - think = ["*.md", "insights/*.md", "templates/*.md"] 109 - muse = ["agents/*.md"] 108 + think = ["*.md", "insights/*.md", "templates/*.md", "agents/*.md"] 110 109 observe = ["*.md", "categories/*.md", "transcribe/*.md"] 111 110 convey = [ 112 111 "templates/*.html",
+4 -4
sol.py
··· 59 59 "observer": "observe.observer", 60 60 "observe-linux": "observe.linux.observer", 61 61 "observe-macos": "observe.macos.observer", 62 - # muse package - AI agents and MCP 63 - "agents": "muse.agents", 64 - "cortex": "muse.cortex", 65 - "mcp": "muse.mcp", 62 + # AI agents and MCP (formerly muse package) 63 + "agents": "think.agents", 64 + "cortex": "think.cortex", 65 + "mcp": "think.mcp", 66 66 # convey package - web UI 67 67 "convey": "convey.cli", 68 68 "restart": "convey.restart",
+2 -2
tests/agents_stub.py
··· 7 7 8 8 9 9 def install_agents_stub(): 10 - """Install a dummy `agents` package with the pieces used by `muse.providers.openai`.""" 10 + """Install a dummy `agents` package with the pieces used by `think.providers.openai`.""" 11 11 agents_stub = types.ModuleType("agents") 12 12 agents_mcp_stub = types.ModuleType("agents.mcp") 13 13 agents_mcp_server_stub = types.ModuleType("agents.mcp.server") ··· 111 111 sys.modules["agents.items"] = agents_items_stub 112 112 sys.modules["agents.run"] = agents_run_stub 113 113 sys.modules["agents.model_settings"] = agents_model_settings_stub 114 - sys.modules.pop("muse.providers.openai", None) 114 + sys.modules.pop("think.providers.openai", None) 115 115 116 116 return last_kwargs, DummyRunner
+2 -2
tests/integration/conftest.py
··· 46 46 return None 47 47 48 48 monkeypatch.setattr( 49 - "muse.cortex.CortexService._start_mcp_server", 49 + "think.cortex.CortexService._start_mcp_server", 50 50 _noop_start, 51 51 ) 52 52 monkeypatch.setattr( 53 - "muse.cortex.CortexService._wait_for_mcp_server", 53 + "think.cortex.CortexService._wait_for_mcp_server", 54 54 lambda *args, **kwargs: None, 55 55 )
+3 -3
tests/integration/test_anthropic_provider.py
··· 11 11 import pytest 12 12 from dotenv import load_dotenv 13 13 14 - from muse.models import CLAUDE_SONNET_4 14 + from think.models import CLAUDE_SONNET_4 15 15 16 16 17 17 def get_fixtures_env(): ··· 234 234 pytest.skip("ANTHROPIC_API_KEY not found in fixtures/.env file") 235 235 236 236 # Import provider directly for this test 237 - from muse.models import IncompleteJSONError 238 - from muse.providers import anthropic as anthropic_provider 237 + from think.models import IncompleteJSONError 238 + from think.providers import anthropic as anthropic_provider 239 239 240 240 # Request JSON output with very small token limit to force truncation 241 241 with pytest.raises(IncompleteJSONError) as exc_info:
+6 -6
tests/integration/test_app_tool_discovery.py
··· 21 21 """ 22 22 os.environ["JOURNAL_PATH"] = str(integration_journal_path) 23 23 24 - from muse.mcp import _discover_app_tools, mcp 24 + from think.mcp import _discover_app_tools, mcp 25 25 26 26 # Call discovery - should not raise exceptions 27 27 _discover_app_tools() ··· 59 59 import importlib 60 60 import logging 61 61 62 - logger = logging.getLogger("muse.mcp") 62 + logger = logging.getLogger("think.mcp") 63 63 64 64 # Clear any existing handlers and set up fresh logging 65 65 logger.handlers.clear() ··· 73 73 # Test that attempting to import a non-existent module is handled gracefully 74 74 try: 75 75 caplog.clear() 76 - with caplog.at_level(logging.ERROR, logger="muse.mcp"): 76 + with caplog.at_level(logging.ERROR, logger="think.mcp"): 77 77 # Simulate what _discover_app_tools does with a broken import 78 78 try: 79 79 importlib.import_module("apps.nonexistent_broken_app.tools") ··· 110 110 111 111 logging.basicConfig(level=logging.DEBUG) 112 112 113 - from muse.mcp import _discover_app_tools 113 + from think.mcp import _discover_app_tools 114 114 115 115 with caplog.at_level(logging.DEBUG): 116 116 # Should not raise an exception ··· 145 145 tools_py.write_text( 146 146 '''"""Private app tools.""" 147 147 from typing import Any 148 - from muse.mcp import register_tool, HINTS 148 + from think.mcp import register_tool, HINTS 149 149 150 150 @register_tool(annotations=HINTS) 151 151 def private_tool() -> dict[str, Any]: ··· 161 161 sys.path.insert(0, str(tmp_path)) 162 162 163 163 try: 164 - from muse.mcp import _discover_app_tools, mcp 164 + from think.mcp import _discover_app_tools, mcp 165 165 166 166 _discover_app_tools() 167 167
+3 -3
tests/integration/test_batch.py
··· 11 11 12 12 import pytest 13 13 14 - from muse.batch import Batch 15 - from muse.models import GEMINI_FLASH, GEMINI_LITE 14 + from think.batch import Batch 15 + from think.models import GEMINI_FLASH, GEMINI_LITE 16 16 17 17 # Default context for integration tests - uses Google provider 18 18 TEST_CONTEXT = "test.batch.integration" ··· 288 288 @pytest.mark.requires_api 289 289 async def test_batch_client_reuse(): 290 290 """Test that client is reused across requests in batch (Google-specific).""" 291 - from muse.providers.google import get_or_create_client 291 + from think.providers.google import get_or_create_client 292 292 293 293 # Create shared client 294 294 client = get_or_create_client()
+2 -2
tests/integration/test_cortex.py
··· 11 11 12 12 import pytest 13 13 14 - from muse.cortex import CortexService 15 - from muse.cortex_client import cortex_agents, cortex_request 16 14 from think.callosum import CallosumServer 15 + from think.cortex import CortexService 16 + from think.cortex_client import cortex_agents, cortex_request 17 17 18 18 19 19 @pytest.fixture
+3 -3
tests/integration/test_google_provider.py
··· 11 11 import pytest 12 12 from dotenv import load_dotenv 13 13 14 - from muse.models import GEMINI_FLASH, GEMINI_PRO 14 + from think.models import GEMINI_FLASH, GEMINI_PRO 15 15 16 16 17 17 def get_fixtures_env(): ··· 226 226 pytest.skip("GOOGLE_API_KEY not found in fixtures/.env file") 227 227 228 228 # Import provider directly for this test 229 - from muse.models import IncompleteJSONError 230 - from muse.providers import google as google_provider 229 + from think.models import IncompleteJSONError 230 + from think.providers import google as google_provider 231 231 232 232 # Request JSON output with very small token limit to force truncation 233 233 with pytest.raises(IncompleteJSONError) as exc_info:
+3 -3
tests/integration/test_mcp_server.py
··· 25 25 os.environ["JOURNAL_PATH"] = str(integration_journal_path) 26 26 27 27 # Import after setting JOURNAL_PATH 28 - from muse.mcp import TOOL_PACKS, mcp 28 + from think.mcp import TOOL_PACKS, mcp 29 29 30 30 # Get all registered tools 31 31 tools = await mcp.get_tools() ··· 223 223 async def test_mcp_tool_packs_coverage(integration_journal_path): 224 224 """Verify all tool packs have their tools registered. 225 225 226 - This test ensures that the TOOL_PACKS dictionary in muse/mcp.py 226 + This test ensures that the TOOL_PACKS dictionary in think/mcp.py 227 227 accurately reflects what's actually registered, which is important 228 228 for agents that use tool packs. 229 229 """ 230 230 os.environ["JOURNAL_PATH"] = str(integration_journal_path) 231 231 232 - from muse.mcp import TOOL_PACKS, mcp 232 + from think.mcp import TOOL_PACKS, mcp 233 233 234 234 tools = await mcp.get_tools() 235 235 registered_tool_names = set(tools.keys())
+3 -3
tests/integration/test_openai_provider.py
··· 11 11 import pytest 12 12 from dotenv import load_dotenv 13 13 14 - from muse.models import GPT_5_MINI 14 + from think.models import GPT_5_MINI 15 15 16 16 17 17 def get_fixtures_env(): ··· 303 303 pytest.skip("OPENAI_API_KEY not found in fixtures/.env file") 304 304 305 305 # Import provider directly for this test 306 - from muse.models import IncompleteJSONError 307 - from muse.providers import openai as openai_provider 306 + from think.models import IncompleteJSONError 307 + from think.providers import openai as openai_provider 308 308 309 309 # Request JSON output with small token limit to force truncation 310 310 with pytest.raises(IncompleteJSONError) as exc_info:
+6 -6
tests/test_agents_history.py
··· 3 3 4 4 """Tests for agents conversation history functions.""" 5 5 6 - from muse.agents import format_tool_summary, parse_agent_events_to_turns 6 + from think.agents import format_tool_summary, parse_agent_events_to_turns 7 7 8 8 9 9 def test_format_tool_summary(): ··· 45 45 {"event": "finish", "result": "I'm doing well, thank you!"}, 46 46 ] 47 47 48 - monkeypatch.setattr("muse.cortex_client.read_agent_events", mock_read_events) 48 + monkeypatch.setattr("think.cortex_client.read_agent_events", mock_read_events) 49 49 50 50 turns = parse_agent_events_to_turns("test-conversation") 51 51 ··· 65 65 {"event": "finish", "result": "It's sunny in NYC."}, 66 66 ] 67 67 68 - monkeypatch.setattr("muse.cortex_client.read_agent_events", mock_read_events) 68 + monkeypatch.setattr("think.cortex_client.read_agent_events", mock_read_events) 69 69 70 70 turns = parse_agent_events_to_turns("test-conversation") 71 71 ··· 89 89 # No finish event for second turn - assistant response is incomplete 90 90 ] 91 91 92 - monkeypatch.setattr("muse.cortex_client.read_agent_events", mock_read_events) 92 + monkeypatch.setattr("think.cortex_client.read_agent_events", mock_read_events) 93 93 94 94 turns = parse_agent_events_to_turns("test-conversation") 95 95 ··· 110 110 def mock_read_events(conversation_id): 111 111 raise FileNotFoundError("Log not found") 112 112 113 - monkeypatch.setattr("muse.cortex_client.read_agent_events", mock_read_events) 113 + monkeypatch.setattr("think.cortex_client.read_agent_events", mock_read_events) 114 114 115 115 turns = parse_agent_events_to_turns("missing-conversation") 116 116 ··· 137 137 {"event": "finish", "result": "Final answer"}, 138 138 ] 139 139 140 - monkeypatch.setattr("muse.cortex_client.read_agent_events", mock_read_events) 140 + monkeypatch.setattr("think.cortex_client.read_agent_events", mock_read_events) 141 141 142 142 turns = parse_agent_events_to_turns("test-conversation") 143 143
+16 -14
tests/test_agents_ndjson.py
··· 1 1 # SPDX-License-Identifier: AGPL-3.0-only 2 2 # Copyright (c) 2026 sol pbc 3 3 4 - """Tests for NDJSON-only input in muse.agents.""" 4 + """Tests for NDJSON-only input in think.agents.""" 5 5 6 6 import asyncio 7 7 import json ··· 11 11 12 12 import pytest 13 13 14 - from muse.models import GPT_5 14 + from think.models import GPT_5 15 15 16 16 17 17 @pytest.fixture ··· 59 59 60 60 This ensures tests are not fragile to changes in default provider. 61 61 """ 62 - # Mock providers in muse.providers (google, openai, anthropic) 62 + # Mock providers in think.providers (google, openai, anthropic) 63 63 for provider_name in ("openai", "anthropic", "google"): 64 64 mock_module = MagicMock() 65 65 mock_module.run_agent = mock_run_agent 66 - monkeypatch.setitem(sys.modules, f"muse.providers.{provider_name}", mock_module) 66 + monkeypatch.setitem( 67 + sys.modules, f"think.providers.{provider_name}", mock_module 68 + ) 67 69 68 70 monkeypatch.setitem(sys.modules, "agents", MagicMock()) 69 71 ··· 88 90 89 91 mock_all_providers(monkeypatch) 90 92 91 - from muse.agents import main_async 93 + from think.agents import main_async 92 94 93 - with patch("muse.agents.setup_cli", return_value=mock_args): 95 + with patch("think.agents.setup_cli", return_value=mock_args): 94 96 with patch("agents.set_default_openai_key"): 95 97 asyncio.run(main_async()) 96 98 ··· 142 144 143 145 mock_all_providers(monkeypatch) 144 146 145 - from muse.agents import main_async 147 + from think.agents import main_async 146 148 147 - with patch("muse.agents.setup_cli", return_value=mock_args): 149 + with patch("think.agents.setup_cli", return_value=mock_args): 148 150 with patch("agents.set_default_openai_key"): 149 151 asyncio.run(main_async()) 150 152 ··· 177 179 178 180 mock_all_providers(monkeypatch) 179 181 180 - from muse.agents import main_async 182 + from think.agents import main_async 181 183 182 - with patch("muse.agents.setup_cli", return_value=mock_args): 184 + with patch("think.agents.setup_cli", return_value=mock_args): 183 185 with patch("agents.set_default_openai_key"): 184 186 asyncio.run(main_async()) 185 187 ··· 212 214 213 215 mock_all_providers(monkeypatch) 214 216 215 - from muse.agents import main_async 217 + from think.agents import main_async 216 218 217 - with patch("muse.agents.setup_cli", return_value=mock_args): 219 + with patch("think.agents.setup_cli", return_value=mock_args): 218 220 with patch("agents.set_default_openai_key"): 219 221 asyncio.run(main_async()) 220 222 ··· 242 244 243 245 mock_all_providers(monkeypatch) 244 246 245 - from muse.agents import main_async 247 + from think.agents import main_async 246 248 247 - with patch("muse.agents.setup_cli", return_value=mock_args): 249 + with patch("think.agents.setup_cli", return_value=mock_args): 248 250 with patch("agents.set_default_openai_key"): 249 251 asyncio.run(main_async()) 250 252
+16 -16
tests/test_anthropic.py
··· 8 8 import types 9 9 from types import SimpleNamespace 10 10 11 - from muse.models import CLAUDE_SONNET_4 12 11 from tests.agents_stub import install_agents_stub 12 + from think.models import CLAUDE_SONNET_4 13 13 14 14 15 15 async def run_main(mod, argv, stdin_data=None): ··· 159 159 _setup_anthropic_stub(monkeypatch) 160 160 _setup_fastmcp_stub(monkeypatch) 161 161 install_agents_stub() 162 - sys.modules.pop("muse.providers.anthropic", None) 163 - importlib.reload(importlib.import_module("muse.providers.anthropic")) 164 - mod = importlib.reload(importlib.import_module("muse.agents")) 162 + sys.modules.pop("think.providers.anthropic", None) 163 + importlib.reload(importlib.import_module("think.providers.anthropic")) 164 + mod = importlib.reload(importlib.import_module("think.agents")) 165 165 166 166 journal = tmp_path / "journal" 167 167 journal.mkdir() ··· 200 200 _setup_anthropic_stub(monkeypatch) 201 201 _setup_fastmcp_stub(monkeypatch) 202 202 install_agents_stub() 203 - sys.modules.pop("muse.providers.anthropic", None) 204 - importlib.reload(importlib.import_module("muse.providers.anthropic")) 205 - mod = importlib.reload(importlib.import_module("muse.agents")) 203 + sys.modules.pop("think.providers.anthropic", None) 204 + importlib.reload(importlib.import_module("think.providers.anthropic")) 205 + mod = importlib.reload(importlib.import_module("think.agents")) 206 206 207 207 journal = tmp_path / "journal" 208 208 journal.mkdir() ··· 245 245 _setup_anthropic_stub(monkeypatch, with_thinking=True) 246 246 _setup_fastmcp_stub(monkeypatch) 247 247 install_agents_stub() 248 - sys.modules.pop("muse.providers.anthropic", None) 249 - importlib.reload(importlib.import_module("muse.providers.anthropic")) 250 - mod = importlib.reload(importlib.import_module("muse.agents")) 248 + sys.modules.pop("think.providers.anthropic", None) 249 + importlib.reload(importlib.import_module("think.providers.anthropic")) 250 + mod = importlib.reload(importlib.import_module("think.agents")) 251 251 252 252 journal = tmp_path / "journal" 253 253 journal.mkdir() ··· 288 288 _setup_anthropic_stub(monkeypatch, with_redacted_thinking=True) 289 289 _setup_fastmcp_stub(monkeypatch) 290 290 install_agents_stub() 291 - sys.modules.pop("muse.providers.anthropic", None) 292 - importlib.reload(importlib.import_module("muse.providers.anthropic")) 293 - mod = importlib.reload(importlib.import_module("muse.agents")) 291 + sys.modules.pop("think.providers.anthropic", None) 292 + importlib.reload(importlib.import_module("think.providers.anthropic")) 293 + mod = importlib.reload(importlib.import_module("think.agents")) 294 294 295 295 journal = tmp_path / "journal" 296 296 journal.mkdir() ··· 329 329 _setup_anthropic_stub(monkeypatch, error=True) 330 330 _setup_fastmcp_stub(monkeypatch) 331 331 install_agents_stub() 332 - sys.modules.pop("muse.providers.anthropic", None) 333 - importlib.reload(importlib.import_module("muse.providers.anthropic")) 334 - mod = importlib.reload(importlib.import_module("muse.agents")) 332 + sys.modules.pop("think.providers.anthropic", None) 333 + importlib.reload(importlib.import_module("think.providers.anthropic")) 334 + mod = importlib.reload(importlib.import_module("think.agents")) 335 335 336 336 journal = tmp_path / "journal" 337 337 journal.mkdir()
+1 -1
tests/test_app_agents.py
··· 73 73 74 74 assert agent_name == "default" 75 75 assert agent_dir.name == "agents" 76 - assert agent_dir.parent.name == "muse" 76 + assert agent_dir.parent.name == "think" 77 77 78 78 79 79 def test_resolve_agent_path_app_agent():
+16 -16
tests/test_batch.py
··· 8 8 9 9 import pytest 10 10 11 - from muse.batch import Batch, BatchRequest 12 - from muse.models import GEMINI_FLASH, GEMINI_LITE 11 + from think.batch import Batch, BatchRequest 12 + from think.models import GEMINI_FLASH, GEMINI_LITE 13 13 14 14 15 15 def test_batch_request_creation(): ··· 50 50 51 51 52 52 @pytest.mark.asyncio 53 - @patch("muse.batch.agenerate", new_callable=AsyncMock) 53 + @patch("think.batch.agenerate", new_callable=AsyncMock) 54 54 async def test_batch_basic(mock_agenerate): 55 55 """Test basic batch execution with single request.""" 56 56 mock_agenerate.return_value = "Response 1" ··· 80 80 81 81 82 82 @pytest.mark.asyncio 83 - @patch("muse.batch.agenerate", new_callable=AsyncMock) 83 + @patch("think.batch.agenerate", new_callable=AsyncMock) 84 84 async def test_batch_with_model_override(mock_agenerate): 85 85 """Test batch with explicit model override.""" 86 86 mock_agenerate.return_value = "Response" ··· 102 102 103 103 104 104 @pytest.mark.asyncio 105 - @patch("muse.batch.agenerate", new_callable=AsyncMock) 105 + @patch("think.batch.agenerate", new_callable=AsyncMock) 106 106 async def test_batch_multiple_requests(mock_agenerate): 107 107 """Test batch with multiple requests.""" 108 108 mock_agenerate.side_effect = ["Response 1", "Response 2", "Response 3"] ··· 141 141 142 142 143 143 @pytest.mark.asyncio 144 - @patch("muse.batch.agenerate", new_callable=AsyncMock) 144 + @patch("think.batch.agenerate", new_callable=AsyncMock) 145 145 async def test_batch_error_handling(mock_agenerate): 146 146 """Test that errors are captured in request.error.""" 147 147 mock_agenerate.side_effect = ValueError("API error") ··· 164 164 165 165 166 166 @pytest.mark.asyncio 167 - @patch("muse.batch.agenerate", new_callable=AsyncMock) 167 + @patch("think.batch.agenerate", new_callable=AsyncMock) 168 168 async def test_batch_dynamic_adding(mock_agenerate): 169 169 """Test adding requests dynamically during iteration.""" 170 170 mock_agenerate.return_value = "Response" ··· 197 197 198 198 199 199 @pytest.mark.asyncio 200 - @patch("muse.batch.agenerate", new_callable=AsyncMock) 200 + @patch("think.batch.agenerate", new_callable=AsyncMock) 201 201 async def test_batch_retry_pattern(mock_agenerate): 202 202 """Test retry pattern - add failed request back with different model.""" 203 203 # First call fails, second succeeds ··· 240 240 241 241 242 242 @pytest.mark.asyncio 243 - @patch("muse.batch.agenerate", new_callable=AsyncMock) 243 + @patch("think.batch.agenerate", new_callable=AsyncMock) 244 244 async def test_batch_factory_method(mock_agenerate): 245 245 """Test that batch.create() factory method works correctly.""" 246 246 mock_agenerate.return_value = "Response" ··· 265 265 266 266 267 267 @pytest.mark.asyncio 268 - @patch("muse.batch.agenerate", new_callable=AsyncMock) 268 + @patch("think.batch.agenerate", new_callable=AsyncMock) 269 269 async def test_batch_can_add_after_draining(mock_agenerate): 270 270 """Test that adding after draining works (reusable batch).""" 271 271 mock_agenerate.side_effect = ["Response 1", "Response 2"] ··· 298 298 299 299 300 300 @pytest.mark.asyncio 301 - @patch("muse.batch.agenerate", new_callable=AsyncMock) 301 + @patch("think.batch.agenerate", new_callable=AsyncMock) 302 302 async def test_batch_empty_batch(mock_agenerate): 303 303 """Test that empty batch (no requests) completes immediately.""" 304 304 batch = Batch() ··· 311 311 312 312 313 313 @pytest.mark.asyncio 314 - @patch("muse.batch.agenerate", new_callable=AsyncMock) 314 + @patch("think.batch.agenerate", new_callable=AsyncMock) 315 315 async def test_batch_concurrency_limit(mock_agenerate): 316 316 """Test that semaphore limits concurrent requests.""" 317 317 # Track concurrent calls ··· 352 352 353 353 354 354 @pytest.mark.asyncio 355 - @patch("muse.batch.agenerate", new_callable=AsyncMock) 355 + @patch("think.batch.agenerate", new_callable=AsyncMock) 356 356 async def test_batch_update_method(mock_agenerate): 357 357 """Test batch.update() method for modifying and re-adding requests.""" 358 358 # Track which model was used in each call ··· 420 420 421 421 422 422 @pytest.mark.asyncio 423 - @patch("muse.batch.agenerate", new_callable=AsyncMock) 423 + @patch("think.batch.agenerate", new_callable=AsyncMock) 424 424 async def test_batch_timeout_passthrough(mock_agenerate): 425 425 """Test that timeout_s is passed through to agenerate.""" 426 426 mock_agenerate.return_value = "Response" ··· 444 444 445 445 446 446 @pytest.mark.asyncio 447 - @patch("muse.batch.agenerate", new_callable=AsyncMock) 447 + @patch("think.batch.agenerate", new_callable=AsyncMock) 448 448 async def test_batch_client_passthrough(mock_agenerate): 449 449 """Test that client is passed through to agenerate for Google connection reuse.""" 450 450 mock_agenerate.return_value = "Response" ··· 468 468 469 469 470 470 @pytest.mark.asyncio 471 - @patch("muse.batch.agenerate", new_callable=AsyncMock) 471 + @patch("think.batch.agenerate", new_callable=AsyncMock) 472 472 async def test_batch_cached_content_passthrough(mock_agenerate): 473 473 """Test that cached_content is passed through to agenerate.""" 474 474 mock_agenerate.return_value = "Response"
+18 -18
tests/test_cortex.py
··· 10 10 11 11 import pytest 12 12 13 - from muse.models import GPT_5 13 + from think.models import GPT_5 14 14 15 15 16 16 @pytest.fixture ··· 28 28 @pytest.fixture 29 29 def cortex_service(mock_journal, monkeypatch): 30 30 """Create a CortexService instance for testing.""" 31 - from muse.cortex import CortexService 31 + from think.cortex import CortexService 32 32 33 33 monkeypatch.setattr(CortexService, "_start_mcp_server", lambda self: None) 34 34 monkeypatch.setattr( ··· 41 41 42 42 def test_agent_process_creation(): 43 43 """Test AgentProcess class initialization and methods.""" 44 - from muse.cortex import AgentProcess 44 + from think.cortex import AgentProcess 45 45 46 46 mock_process = MagicMock() 47 47 mock_process.poll.return_value = None # Running ··· 69 69 assert cortex_service.agents_dir.exists() 70 70 71 71 72 - @patch("muse.cortex.subprocess.Popen") 73 - @patch("muse.cortex.threading.Thread") 74 - @patch("muse.cortex.threading.Timer") 72 + @patch("think.cortex.subprocess.Popen") 73 + @patch("think.cortex.threading.Thread") 74 + @patch("think.cortex.threading.Timer") 75 75 def test_spawn_agent(mock_timer, mock_thread, mock_popen, cortex_service, mock_journal): 76 76 """Test spawning an agent subprocess.""" 77 77 mock_process = MagicMock() ··· 139 139 mock_timer_instance.start.assert_called_once() 140 140 141 141 142 - @patch("muse.cortex.subprocess.Popen") 142 + @patch("think.cortex.subprocess.Popen") 143 143 def test_spawn_agent_with_handoff_from(mock_popen, cortex_service, mock_journal): 144 144 """Test spawning an agent with handoff_from parameter.""" 145 145 mock_process = MagicMock() ··· 161 161 "handoff_from": "parent123", 162 162 } 163 163 164 - with patch("muse.cortex.threading.Thread"): 164 + with patch("think.cortex.threading.Thread"): 165 165 cortex_service._spawn_agent(agent_id, file_path, request) 166 166 167 167 # Check handoff_from was included in NDJSON ··· 174 174 """Test monitoring stdout with JSON events.""" 175 175 from io import StringIO 176 176 177 - from muse.cortex import AgentProcess 177 + from think.cortex import AgentProcess 178 178 179 179 agent_id = "123456789" 180 180 log_path = mock_journal / "agents" / f"{agent_id}_active.jsonl" ··· 210 210 """Test monitoring stdout with non-JSON output.""" 211 211 from io import StringIO 212 212 213 - from muse.cortex import AgentProcess 213 + from think.cortex import AgentProcess 214 214 215 215 agent_id = "123456789" 216 216 log_path = mock_journal / "agents" / f"{agent_id}_active.jsonl" ··· 241 241 """Test monitoring stdout with handoff in finish event.""" 242 242 from io import StringIO 243 243 244 - from muse.cortex import AgentProcess 244 + from think.cortex import AgentProcess 245 245 246 246 agent_id = "123456789" 247 247 log_path = mock_journal / "agents" / f"{agent_id}_active.jsonl" ··· 276 276 """Test monitoring stdout when process exits without finish event.""" 277 277 from io import StringIO 278 278 279 - from muse.cortex import AgentProcess 279 + from think.cortex import AgentProcess 280 280 281 281 agent_id = "123456789" 282 282 log_path = mock_journal / "agents" / f"{agent_id}_active.jsonl" ··· 305 305 """Test monitoring stderr for errors.""" 306 306 from io import StringIO 307 307 308 - from muse.cortex import AgentProcess 308 + from think.cortex import AgentProcess 309 309 310 310 agent_id = "123456789" 311 311 log_path = mock_journal / "agents" / f"{agent_id}_active.jsonl" ··· 402 402 "max_turns": 5, 403 403 } 404 404 405 - with patch("muse.cortex_client.cortex_request") as mock_request: 405 + with patch("think.cortex_client.cortex_request") as mock_request: 406 406 mock_request.return_value = ( 407 407 mock_journal / "agents" / "987654321000_active.jsonl" 408 408 ) ··· 427 427 "prompt": "Review this analysis", # Explicit prompt 428 428 } 429 429 430 - with patch("muse.cortex_client.cortex_request") as mock_request: 430 + with patch("think.cortex_client.cortex_request") as mock_request: 431 431 cortex_service._spawn_handoff(parent_id, result, handoff) 432 432 433 433 # Check cortex_request was called with explicit prompt ··· 443 443 444 444 def test_get_status(cortex_service): 445 445 """Test getting service status.""" 446 - from muse.cortex import AgentProcess 446 + from think.cortex import AgentProcess 447 447 448 448 # Empty status 449 449 status = cortex_service.get_status() ··· 547 547 548 548 def test_monitor_stdout_with_save(cortex_service, mock_journal): 549 549 """Test monitor_stdout saves result when save field is present.""" 550 - from muse.cortex import AgentProcess 550 + from think.cortex import AgentProcess 551 551 552 552 # Create agent with save in request 553 553 agent_id = "save_test" ··· 589 589 590 590 def test_monitor_stdout_with_save_and_day(cortex_service, mock_journal): 591 591 """Test monitor_stdout saves result to specific day when day field is present.""" 592 - from muse.cortex import AgentProcess 592 + from think.cortex import AgentProcess 593 593 594 594 # Create agent with save and day in request 595 595 agent_id = "save_day_test"
+3 -3
tests/test_cortex_client.py
··· 13 13 14 14 import pytest 15 15 16 - from muse.cortex_client import ( 16 + from think.callosum import CallosumConnection, CallosumServer 17 + from think.cortex_client import ( 17 18 cortex_agents, 18 19 cortex_request, 19 20 get_agent_end_state, 20 21 get_agent_status, 21 22 get_agent_thread, 22 23 ) 23 - from muse.models import GPT_5 24 - from think.callosum import CallosumConnection, CallosumServer 24 + from think.models import GPT_5 25 25 26 26 27 27 @pytest.fixture
+1 -1
tests/test_detect_transcript.py
··· 77 77 '[{"start_at": "14:30:00", "line": 1}, {"start_at": "14:35:00", "line": 3}]' 78 78 ) 79 79 80 - monkeypatch.setattr("muse.models.generate", mock_generate) 80 + monkeypatch.setattr("think.models.generate", mock_generate) 81 81 82 82 # Now requires start_time argument 83 83 result = mod.detect_transcript_segment("a\nb\nc\nd", "14:30:00")
+6 -6
tests/test_extract.py
··· 166 166 categories = {"code": {"description": "Code editors"}} 167 167 168 168 # Mock generate to return specific frame IDs 169 - with patch("muse.models.generate") as mock_generate: 169 + with patch("think.models.generate") as mock_generate: 170 170 mock_generate.return_value = "[1, 3, 5, 7]" 171 171 result = select_frames_for_extraction( 172 172 frames, max_extractions=5, categories=categories ··· 182 182 categories = {"code": {"description": "Code editors"}} 183 183 184 184 # Mock generate to return some invalid IDs 185 - with patch("muse.models.generate") as mock_generate: 185 + with patch("think.models.generate") as mock_generate: 186 186 mock_generate.return_value = "[1, 3, 99, 100, 5]" # 99, 100 are invalid 187 187 result = select_frames_for_extraction( 188 188 frames, max_extractions=10, categories=categories ··· 200 200 201 201 # Mock generate to return way too many IDs 202 202 many_ids = list(range(1, 51)) # 50 IDs 203 - with patch("muse.models.generate") as mock_generate: 203 + with patch("think.models.generate") as mock_generate: 204 204 mock_generate.return_value = str(many_ids) 205 205 result = select_frames_for_extraction( 206 206 frames, max_extractions=5, categories=categories ··· 216 216 categories = {"code": {"description": "Code editors"}} 217 217 218 218 # Mock generate to raise an exception 219 - with patch("muse.models.generate") as mock_generate: 219 + with patch("think.models.generate") as mock_generate: 220 220 mock_generate.side_effect = Exception("API error") 221 221 result = select_frames_for_extraction( 222 222 frames, max_extractions=5, categories=categories ··· 232 232 frames = _make_frames(10) 233 233 categories = {"code": {"description": "Code editors"}} 234 234 235 - with patch("muse.models.generate") as mock_generate: 235 + with patch("think.models.generate") as mock_generate: 236 236 mock_generate.return_value = "not valid json" 237 237 result = select_frames_for_extraction( 238 238 frames, max_extractions=5, categories=categories ··· 247 247 """Test that AI selection is skipped when categories is None.""" 248 248 frames = _make_frames(10) 249 249 250 - with patch("muse.models.generate") as mock_generate: 250 + with patch("think.models.generate") as mock_generate: 251 251 result = select_frames_for_extraction( 252 252 frames, max_extractions=5, categories=None 253 253 )
+10 -10
tests/test_formatters.py
··· 481 481 482 482 def test_format_agent_direct(self): 483 483 """Test format_agent function directly.""" 484 - from muse.cortex import format_agent 484 + from think.cortex import format_agent 485 485 486 486 entries = [ 487 487 { ··· 513 513 514 514 def test_format_agent_with_thinking(self): 515 515 """Test agent formatting with thinking event.""" 516 - from muse.cortex import format_agent 516 + from think.cortex import format_agent 517 517 518 518 entries = [ 519 519 {"event": "start", "ts": 1700000000000, "agent_id": "test"}, ··· 535 535 536 536 def test_format_agent_tool_pairing(self): 537 537 """Test that tool_start/tool_end are paired by call_id.""" 538 - from muse.cortex import format_agent 538 + from think.cortex import format_agent 539 539 540 540 entries = [ 541 541 {"event": "start", "ts": 1700000000000, "agent_id": "test"}, ··· 566 566 567 567 def test_format_agent_truncates_long_results(self): 568 568 """Test that tool results are truncated at 500 chars.""" 569 - from muse.cortex import format_agent 569 + from think.cortex import format_agent 570 570 571 571 long_result = "x" * 1000 # 1000 chars 572 572 ··· 598 598 599 599 def test_format_agent_unpaired_tool_start(self): 600 600 """Test that unpaired tool_start shows 'did not complete'.""" 601 - from muse.cortex import format_agent 601 + from think.cortex import format_agent 602 602 603 603 entries = [ 604 604 {"event": "start", "ts": 1700000000000, "agent_id": "test"}, ··· 624 624 625 625 def test_format_agent_error_event(self): 626 626 """Test formatting of error events.""" 627 - from muse.cortex import format_agent 627 + from think.cortex import format_agent 628 628 629 629 entries = [ 630 630 {"event": "start", "ts": 1700000000000, "agent_id": "test"}, ··· 646 646 647 647 def test_format_agent_skipped_entries(self): 648 648 """Test that entries without 'event' field are skipped and reported.""" 649 - from muse.cortex import format_agent 649 + from think.cortex import format_agent 650 650 651 651 entries = [ 652 652 {"event": "start", "ts": 1700000000000, "agent_id": "test"}, ··· 662 662 663 663 def test_format_agent_agent_updated(self): 664 664 """Test formatting of agent_updated events.""" 665 - from muse.cortex import format_agent 665 + from think.cortex import format_agent 666 666 667 667 entries = [ 668 668 {"event": "start", "ts": 1700000000000, "agent_id": "test"}, ··· 677 677 678 678 def test_format_agent_continue_event(self): 679 679 """Test formatting of continue events.""" 680 - from muse.cortex import format_agent 680 + from think.cortex import format_agent 681 681 682 682 entries = [ 683 683 {"event": "start", "ts": 1700000000000, "agent_id": "test"}, ··· 692 692 693 693 def test_format_agent_handoff_from_header(self): 694 694 """Test that handoff_from appears in header.""" 695 - from muse.cortex import format_agent 695 + from think.cortex import format_agent 696 696 697 697 entries = [ 698 698 {
+10 -10
tests/test_google.py
··· 7 7 import sys 8 8 from types import SimpleNamespace 9 9 10 - from muse.models import GEMINI_FLASH 11 - from muse.providers.google import ( 10 + from tests.agents_stub import install_agents_stub 11 + from tests.conftest import setup_google_genai_stub 12 + from think.models import GEMINI_FLASH 13 + from think.providers.google import ( 12 14 _extract_finish_reason, 13 15 _format_completion_message, 14 16 ) 15 - from tests.agents_stub import install_agents_stub 16 - from tests.conftest import setup_google_genai_stub 17 17 18 18 19 19 async def run_main(mod, argv, stdin_data=None): ··· 28 28 def test_google_main(monkeypatch, tmp_path, capsys): 29 29 setup_google_genai_stub(monkeypatch, with_thinking=False) 30 30 install_agents_stub() 31 - sys.modules.pop("muse.providers.google", None) 32 - importlib.reload(importlib.import_module("muse.providers.google")) 33 - mod = importlib.reload(importlib.import_module("muse.agents")) 31 + sys.modules.pop("think.providers.google", None) 32 + importlib.reload(importlib.import_module("think.providers.google")) 33 + mod = importlib.reload(importlib.import_module("think.agents")) 34 34 35 35 journal = tmp_path / "journal" 36 36 journal.mkdir() ··· 78 78 "think.utils.create_mcp_client", lambda _url=None: ErrorClient() 79 79 ) 80 80 81 - sys.modules.pop("muse.providers.google", None) 82 - importlib.reload(importlib.import_module("muse.providers.google")) 83 - mod = importlib.reload(importlib.import_module("muse.agents")) 81 + sys.modules.pop("think.providers.google", None) 82 + importlib.reload(importlib.import_module("think.providers.google")) 83 + mod = importlib.reload(importlib.import_module("think.agents")) 84 84 85 85 journal = tmp_path / "journal" 86 86 journal.mkdir()
+4 -4
tests/test_google_thinking.py
··· 6 6 import json 7 7 import sys 8 8 9 - from muse.models import GEMINI_FLASH 10 9 from tests.agents_stub import install_agents_stub 11 10 from tests.conftest import setup_google_genai_stub 11 + from think.models import GEMINI_FLASH 12 12 13 13 14 14 async def run_main(mod, argv, stdin_data=None): ··· 24 24 setup_google_genai_stub(monkeypatch, with_thinking=True) 25 25 install_agents_stub() 26 26 27 - sys.modules.pop("muse.providers.google", None) 28 - importlib.reload(importlib.import_module("muse.providers.google")) 29 - mod = importlib.reload(importlib.import_module("muse.agents")) 27 + sys.modules.pop("think.providers.google", None) 28 + importlib.reload(importlib.import_module("think.providers.google")) 29 + mod = importlib.reload(importlib.import_module("think.agents")) 30 30 31 31 journal = tmp_path / "journal" 32 32 journal.mkdir()
+4 -4
tests/test_journal_index.py
··· 417 417 418 418 def test_mcp_search_journal_returns_counts(): 419 419 """Test MCP wrapper returns counts aggregation.""" 420 - from muse.tools.search import search_journal 420 + from think.tools.search import search_journal 421 421 422 422 # Use fixtures journal 423 423 os.environ["JOURNAL_PATH"] = "fixtures/journal" ··· 439 439 440 440 def test_mcp_search_journal_returns_query_echo(): 441 441 """Test MCP wrapper returns query echo.""" 442 - from muse.tools.search import search_journal 442 + from think.tools.search import search_journal 443 443 444 444 os.environ["JOURNAL_PATH"] = "fixtures/journal" 445 445 ··· 453 453 454 454 def test_mcp_search_journal_results_include_path(): 455 455 """Test MCP wrapper results include path and idx.""" 456 - from muse.tools.search import search_journal 456 + from think.tools.search import search_journal 457 457 458 458 os.environ["JOURNAL_PATH"] = "fixtures/journal" 459 459 ··· 469 469 """Test day bucketing logic.""" 470 470 from datetime import datetime, timedelta 471 471 472 - from muse.tools.search import _bucket_day_counts 472 + from think.tools.search import _bucket_day_counts 473 473 474 474 today = datetime.now() 475 475
+3 -3
tests/test_models.py
··· 5 5 6 6 import pytest 7 7 8 - from muse.models import ( 8 + from think.models import ( 9 9 CLAUDE_HAIKU_4, 10 10 CLAUDE_OPUS_4, 11 11 CLAUDE_SONNET_4, ··· 397 397 """Test that registry includes discovered agent contexts.""" 398 398 registry = get_context_registry() 399 399 400 - # Should have agent entries (from muse/agents/*.json and apps/*/agents/*.json) 400 + # Should have agent entries (from think/agents/*.md and apps/*/agents/*.md) 401 401 agent_contexts = [k for k in registry if k.startswith("agent.")] 402 402 403 403 # Should have more than just the fallback pattern ··· 459 459 2. Re-run this test 460 460 3. If still failing, the model may be too new for genai-prices 461 461 462 - See muse/models.py model constants section for more details. 462 + See think/models.py model constants section for more details. 463 463 """ 464 464 # Collect all unique models from PROVIDER_DEFAULTS 465 465 all_models = set()
+18 -18
tests/test_openai.py
··· 7 7 import sys 8 8 from types import SimpleNamespace 9 9 10 - from muse.models import GPT_5 11 10 from tests.agents_stub import install_agents_stub 11 + from think.models import GPT_5 12 12 13 13 14 14 async def run_main(mod, argv, stdin_data=None): ··· 30 30 last_kwargs, DummyRunner = _setup_openai_mocks(monkeypatch, tmp_path) 31 31 DummyRunner.events_to_stream = [] # Reset for this test 32 32 33 - importlib.reload(importlib.import_module("muse.providers.openai")) 34 - mod = importlib.reload(importlib.import_module("muse.agents")) 33 + importlib.reload(importlib.import_module("think.providers.openai")) 34 + mod = importlib.reload(importlib.import_module("think.agents")) 35 35 36 36 journal = tmp_path / "journal" 37 37 journal.mkdir() ··· 83 83 84 84 DummyRunner.events_to_stream = [reasoning_event] 85 85 86 - importlib.reload(importlib.import_module("muse.providers.openai")) 87 - mod = importlib.reload(importlib.import_module("muse.agents")) 86 + importlib.reload(importlib.import_module("think.providers.openai")) 87 + mod = importlib.reload(importlib.import_module("think.agents")) 88 88 89 89 journal = tmp_path / "journal" 90 90 journal.mkdir() ··· 130 130 DummyRunner.events_to_stream = [] 131 131 last_kwargs.clear() 132 132 133 - importlib.reload(importlib.import_module("muse.providers.openai")) 134 - mod = importlib.reload(importlib.import_module("muse.agents")) 133 + importlib.reload(importlib.import_module("think.providers.openai")) 134 + mod = importlib.reload(importlib.import_module("think.agents")) 135 135 136 136 journal = tmp_path / "journal" 137 137 journal.mkdir() ··· 163 163 last_kwargs, DummyRunner = _setup_openai_mocks(monkeypatch, tmp_path) 164 164 DummyRunner.events_to_stream = [] # Reset for this test 165 165 166 - importlib.reload(importlib.import_module("muse.providers.openai")) 167 - mod = importlib.reload(importlib.import_module("muse.agents")) 166 + importlib.reload(importlib.import_module("think.providers.openai")) 167 + mod = importlib.reload(importlib.import_module("think.agents")) 168 168 169 169 journal = tmp_path / "journal" 170 170 journal.mkdir() ··· 218 218 219 219 DummyRunner.events_to_stream = [reasoning_event] 220 220 221 - importlib.reload(importlib.import_module("muse.providers.openai")) 222 - mod = importlib.reload(importlib.import_module("muse.agents")) 221 + importlib.reload(importlib.import_module("think.providers.openai")) 222 + mod = importlib.reload(importlib.import_module("think.agents")) 223 223 224 224 journal = tmp_path / "journal" 225 225 journal.mkdir() ··· 275 275 # Override run_streamed to return error stream 276 276 DummyRunner.run_streamed = lambda *a, **k: ErrorStreamResult() 277 277 278 - importlib.reload(importlib.import_module("muse.providers.openai")) 279 - mod = importlib.reload(importlib.import_module("muse.agents")) 278 + importlib.reload(importlib.import_module("think.providers.openai")) 279 + mod = importlib.reload(importlib.import_module("think.agents")) 280 280 281 281 journal = tmp_path / "journal" 282 282 journal.mkdir() ··· 326 326 327 327 DummyRunner.events_to_stream = [reasoning_event] 328 328 329 - importlib.reload(importlib.import_module("muse.providers.openai")) 330 - mod = importlib.reload(importlib.import_module("muse.agents")) 329 + importlib.reload(importlib.import_module("think.providers.openai")) 330 + mod = importlib.reload(importlib.import_module("think.agents")) 331 331 332 332 journal = tmp_path / "journal" 333 333 journal.mkdir() ··· 389 389 390 390 DummyRunner.events_to_stream = [tool_call_event, tool_output_event] 391 391 392 - importlib.reload(importlib.import_module("muse.providers.openai")) 393 - mod = importlib.reload(importlib.import_module("muse.agents")) 392 + importlib.reload(importlib.import_module("think.providers.openai")) 393 + mod = importlib.reload(importlib.import_module("think.agents")) 394 394 395 395 journal = tmp_path / "journal" 396 396 journal.mkdir() ··· 445 445 Verifies the fix for the bug where content type was 'text' (Chat Completions API) 446 446 instead of 'input_text'/'output_text' (Responses API). 447 447 """ 448 - from muse.providers.openai import _convert_turns_to_items 448 + from think.providers.openai import _convert_turns_to_items 449 449 450 450 # Test user message uses input_text 451 451 user_turns = [{"role": "user", "content": "Hello, world!"}]
+1 -1
tests/test_planner.py
··· 41 41 def mock_generate(**kwargs): 42 42 return "plan" 43 43 44 - monkeypatch.setattr("muse.models.generate", mock_generate) 44 + monkeypatch.setattr("think.models.generate", mock_generate) 45 45 result = mod.generate_plan("do something") 46 46 assert result == "plan" 47 47
+2
think/__init__.py
··· 1 1 # SPDX-License-Identifier: AGPL-3.0-only 2 2 # Copyright (c) 2026 sol pbc 3 3 4 + """Think - Data processing, AI agent orchestration, and analysis for solstone.""" 5 + 4 6 from .detect_created import detect_created 5 7 from .detect_transcript import detect_transcript_json, detect_transcript_segment 6 8 from .planner import generate_plan
+1 -1
think/detect_created.py
··· 85 85 # Debug: write content to temp file 86 86 _debug_write_content(markdown, path) 87 87 88 - from muse.models import generate 88 + from think.models import generate 89 89 90 90 response_text = generate( 91 91 contents=markdown,
+2 -2
think/detect_transcript.py
··· 134 134 contents = f"START_TIME: {start_time}\n{numbered}" 135 135 logging.info(f"Starting transcript segmentation (start: {start_time})...") 136 136 137 - from muse.models import generate 137 + from think.models import generate 138 138 139 139 try: 140 140 response_text = generate( ··· 174 174 # Prepend SEGMENT_START for the prompt 175 175 contents = f"SEGMENT_START: {segment_start}\n{text}" 176 176 177 - from muse.models import generate 177 + from think.models import generate 178 178 179 179 response_text = generate( 180 180 contents=contents,
+1 -1
think/dream.py
··· 8 8 from datetime import datetime, timedelta 9 9 from pathlib import Path 10 10 11 - from muse.cortex_client import cortex_request, get_agent_status 12 11 from think.callosum import CallosumConnection 12 + from think.cortex_client import cortex_request, get_agent_status 13 13 from think.facets import get_active_facets, get_facets 14 14 from think.runner import run_task 15 15 from think.utils import (
+1 -1
think/formatters.py
··· 116 116 # Note: agents/*_active.jsonl is excluded - only completed agents are formatted 117 117 FORMATTERS: dict[str, tuple[str, str]] = { 118 118 # JSONL formatters 119 - "agents/*.jsonl": ("muse.cortex", "format_agent"), 119 + "agents/*.jsonl": ("think.cortex", "format_agent"), 120 120 "config/actions/*.jsonl": ("think.facets", "format_logs"), 121 121 "facets/*/entities/*.jsonl": ("think.entities.formatting", "format_entities"), 122 122 "facets/*/events/*.jsonl": ("think.events", "format_events"),
+3 -3
think/insight.py
··· 10 10 from google import genai 11 11 from google.genai import types 12 12 13 - from muse.models import generate 14 13 from think.cluster import cluster, cluster_period, cluster_segments_multi 14 + from think.models import generate 15 15 from think.utils import ( 16 16 PromptNotFoundError, 17 17 _load_insight_metadata, ··· 180 180 181 181 # Try to use cache if display name provided 182 182 # Note: caching is Google-specific, so we check provider first 183 - from muse.models import resolve_provider 183 + from think.models import resolve_provider 184 184 185 185 provider, model = resolve_provider(context) 186 186 ··· 426 426 prompt = insight_prompt.text 427 427 428 428 # Resolve provider for display (must match context used in send_insight) 429 - from muse.models import resolve_provider 429 + from think.models import resolve_provider 430 430 431 431 display_output_type = "json" if output_format == "json" else "markdown" 432 432 _, model = resolve_provider(f"insight.{insight_key}.{display_output_type}")
+1 -1
think/insights/anticipation.py
··· 12 12 import logging 13 13 from pathlib import Path 14 14 15 - from muse.models import generate 16 15 from think.facets import facet_summaries 17 16 from think.insights import ( 18 17 compute_source_insight, 19 18 should_skip_extraction, 20 19 write_events_jsonl, 21 20 ) 21 + from think.models import generate 22 22 from think.utils import get_insight_topic, load_prompt 23 23 24 24
+1 -1
think/insights/occurrence.py
··· 12 12 import logging 13 13 from pathlib import Path 14 14 15 - from muse.models import generate 16 15 from think.facets import facet_summaries 17 16 from think.insights import ( 18 17 compute_source_insight, 19 18 should_skip_extraction, 20 19 write_events_jsonl, 21 20 ) 21 + from think.models import generate 22 22 from think.utils import get_insight_topic, load_prompt 23 23 24 24
+2 -5
think/planner.py
··· 7 7 import os 8 8 import sys 9 9 from pathlib import Path 10 - from typing import Optional 11 - 12 - from dotenv import load_dotenv 13 10 14 11 from .utils import load_prompt, setup_cli 15 12 ··· 18 15 """Return formatted MCP tools information for the prompt.""" 19 16 20 17 try: 21 - from muse.mcp import mcp 18 + from think.mcp import mcp 22 19 23 20 tools = await mcp.get_tools() 24 21 if not tools: ··· 61 58 62 59 def generate_plan(request: str) -> str: 63 60 """Return a detailed agent plan for ``request`` using configured provider.""" 64 - from muse.models import generate 61 + from think.models import generate 65 62 66 63 return generate( 67 64 contents=request,
+4 -4
think/utils.py
··· 22 22 DATE_RE = re.compile(r"\d{8}") 23 23 _journal_path_cache: str | None = None 24 24 25 - AGENT_DIR = Path(__file__).parent.parent / "muse" / "agents" 25 + AGENT_DIR = Path(__file__).parent / "agents" 26 26 TEMPLATES_DIR = Path(__file__).parent / "templates" 27 27 28 28 # Cached raw template content loaded from think/templates/*.md ··· 973 973 app, agent_name = persona.split(":", 1) 974 974 agent_dir = Path(__file__).parent.parent / "apps" / app / "agents" 975 975 else: 976 - # System agent: "default" -> muse/agents/default 976 + # System agent: "default" -> think/agents/default 977 977 agent_dir = AGENT_DIR 978 978 agent_name = persona 979 979 return agent_dir, agent_name ··· 1321 1321 def get_agents() -> dict[str, dict[str, Any]]: 1322 1322 """Load agent metadata from system and app directories. 1323 1323 1324 - Scans both system agents (muse/agents/) and app agents (apps/*/agents/). 1324 + Scans both system agents (think/agents/) and app agents (apps/*/agents/). 1325 1325 System agents use simple keys like "default", while app agents are 1326 1326 namespaced as "app:agent" (e.g., "chat:helper"). 1327 1327 ··· 1334 1334 """ 1335 1335 agents = {} 1336 1336 1337 - # System agents from muse/agents/ 1337 + # System agents from think/agents/ 1338 1338 if AGENT_DIR.exists(): 1339 1339 for md_path in sorted(AGENT_DIR.glob("*.md")): 1340 1340 agent_id = md_path.stem