diff --git a/Makefile b/Makefile index 5982d00ba..9ef760ed6 100644 --- a/Makefile +++ b/Makefile @@ -66,17 +66,17 @@ install: .installed # Directories where AI coding agents look for skills SKILL_DIRS := .agents/skills .claude/skills -# Discover SKILL.md files in muse/ and apps/*/muse/, symlink into agent skill dirs +# Discover SKILL.md files in talent/ and apps/*/talent/, symlink into agent skill dirs skills: @# Collect all skill directories (containing SKILL.md) @SKILLS=""; \ - for skill_md in muse/*/SKILL.md apps/*/muse/*/SKILL.md; do \ + for skill_md in talent/*/SKILL.md apps/*/talent/*/SKILL.md; do \ [ -f "$$skill_md" ] || continue; \ skill_dir=$$(dirname "$$skill_md"); \ skill_name=$$(basename "$$skill_dir"); \ if echo "$$SKILLS" | grep -qw "$$skill_name"; then \ echo "Error: duplicate skill name '$$skill_name' found in $$skill_dir" >&2; \ - echo "Each skill directory name must be unique across muse/ and apps/*/muse/." >&2; \ + echo "Each skill directory name must be unique across talent/ and apps/*/talent/." >&2; \ exit 1; \ fi; \ SKILLS="$$SKILLS $$skill_name"; \ @@ -88,7 +88,7 @@ skills: done; \ done; \ count=0; \ - for skill_md in muse/*/SKILL.md apps/*/muse/*/SKILL.md; do \ + for skill_md in talent/*/SKILL.md apps/*/talent/*/SKILL.md; do \ [ -f "$$skill_md" ] || continue; \ skill_dir=$$(dirname "$$skill_md"); \ skill_name=$$(basename "$$skill_dir"); \ diff --git a/README.md b/README.md index 98ed2eb19..995453483 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Python 3.10+, Linux + macOS, AGPL-3.0-only, maintained by [sol pbc](https://solp ``` - **observe** — receives captured audio and screen activity from standalone observers (solstone-linux, solstone-tmux, solstone-macos) via remote ingest. processes FLAC audio, WebM screen recordings, and timestamped metadata. -- **think** — transcribes audio (faster-whisper), analyzes screen captures, extracts entities, detects meetings, and indexes everything into SQLite. runs 30 configurable agent/generator templates from `muse/`. +- **think** — transcribes audio (faster-whisper), analyzes screen captures, extracts entities, detects meetings, and indexes everything into SQLite. runs 30 configurable agent/generator templates from `talent/`. - **cortex** — orchestrates agent execution. receives events, dispatches agents, writes results back to the journal. - **callosum** — async message bus connecting all services. enables event-driven coordination between observe, think, cortex, and convey. - **convey** — Flask-based web interface with 17 pluggable apps for navigating journal data. diff --git a/apps/agents/routes.py b/apps/agents/routes.py index 913658ea5..fed5df73b 100644 --- a/apps/agents/routes.py +++ b/apps/agents/routes.py @@ -18,7 +18,7 @@ from convey import state from convey.utils import DATE_RE, format_date from think.facets import get_facets from think.models import calc_agent_cost -from think.muse import get_muse_configs, get_output_path +from think.talent import get_talent_configs, get_output_path from think.utils import updated_days agents_bp = Blueprint( @@ -319,13 +319,13 @@ def _get_agents_for_day(day: str, facet_filter: str | None = None) -> list[dict] @lru_cache(maxsize=1) def _build_agents_meta() -> dict[str, dict[str, Any]]: - """Build agent metadata dict from all muse configs. + """Build agent metadata dict from all talent configs. Returns dict mapping agent name to metadata with capability fields - for frontend display. Cached for process lifetime since muse configs + for frontend display. Cached for process lifetime since talent configs are static. """ - configs = get_muse_configs(include_disabled=True) + configs = get_talent_configs(include_disabled=True) agents: dict[str, dict[str, Any]] = {} for name, config in configs.items(): @@ -545,7 +545,7 @@ def api_preview_prompt(name: str) -> Any: } """ try: - from think.muse import get_agent + from think.talent import get_agent config = get_agent(name) diff --git a/apps/calendar/routes.py b/apps/calendar/routes.py index f6234a531..63550d7fb 100644 --- a/apps/calendar/routes.py +++ b/apps/calendar/routes.py @@ -54,9 +54,9 @@ def calendar_day_events(day: str) -> Any: return "", 404 from think.indexer.journal import get_events - from think.muse import get_muse_configs + from think.talent import get_talent_configs - generators = get_muse_configs(type="generate") + generators = get_talent_configs(type="generate") # Get full event objects from source files raw_events = get_events(day) diff --git a/apps/calendar/muse/calendar/SKILL.md b/apps/calendar/talent/calendar/SKILL.md similarity index 100% rename from apps/calendar/muse/calendar/SKILL.md rename to apps/calendar/talent/calendar/SKILL.md diff --git a/apps/entities/muse/entities.md b/apps/entities/talent/entities.md similarity index 100% rename from apps/entities/muse/entities.md rename to apps/entities/talent/entities.md diff --git a/apps/entities/muse/entities/SKILL.md b/apps/entities/talent/entities/SKILL.md similarity index 100% rename from apps/entities/muse/entities/SKILL.md rename to apps/entities/talent/entities/SKILL.md diff --git a/apps/entities/muse/entities_review.md b/apps/entities/talent/entities_review.md similarity index 100% rename from apps/entities/muse/entities_review.md rename to apps/entities/talent/entities_review.md diff --git a/apps/entities/muse/entity_assist.md b/apps/entities/talent/entity_assist.md similarity index 100% rename from apps/entities/muse/entity_assist.md rename to apps/entities/talent/entity_assist.md diff --git a/apps/entities/muse/entity_describe.md b/apps/entities/talent/entity_describe.md similarity index 100% rename from apps/entities/muse/entity_describe.md rename to apps/entities/talent/entity_describe.md diff --git a/apps/entities/muse/entity_observer.md b/apps/entities/talent/entity_observer.md similarity index 100% rename from apps/entities/muse/entity_observer.md rename to apps/entities/talent/entity_observer.md diff --git a/apps/health/muse/health/SKILL.md b/apps/health/talent/health/SKILL.md similarity index 86% rename from apps/health/muse/health/SKILL.md rename to apps/health/talent/health/SKILL.md index 607955fb5..74e2b5fc6 100644 --- a/apps/health/muse/health/SKILL.md +++ b/apps/health/talent/health/SKILL.md @@ -15,7 +15,7 @@ description: > Use these commands to check service health, view logs, and inspect agent runs. -**Typical workflow**: `sol health` to check service status → `sol health logs` to inspect recent log output → `sol muse logs` to review agent runs → `sol muse log ` for run details. +**Typical workflow**: `sol health` to check service status → `sol health logs` to inspect recent log output → `sol talent logs` to review agent runs → `sol talent log ` for run details. ## status @@ -66,7 +66,7 @@ sol health logs -f ## agent runs ```bash -sol muse logs [AGENT] [-c COUNT] [--day YYYYMMDD] [--daily] [--errors] [--summary] +sol talent logs [AGENT] [-c COUNT] [--day YYYYMMDD] [--daily] [--errors] [--summary] ``` List recent agent runs. @@ -85,23 +85,23 @@ Output columns: agent_id, time, name, status, runtime, cost, events, tools, outp Examples: ```bash -sol muse logs -sol muse logs activity -c 10 -sol muse logs --daily -sol muse logs --daily --summary -sol muse logs --day 20260228 -sol muse logs --daily --errors +sol talent logs +sol talent logs activity -c 10 +sol talent logs --daily +sol talent logs --daily --summary +sol talent logs --day 20260228 +sol talent logs --daily --errors ``` ## agent run detail ```bash -sol muse log [--json] [--full] +sol talent log [--json] [--full] ``` Show events for a single agent run. -- `ID`: agent run ID (from `sol muse logs` output). +- `ID`: agent run ID (from `sol talent logs` output). - `--json`: raw JSONL events. - `--full`: expanded event detail (no truncation). @@ -110,9 +110,9 @@ Without flags, shows a one-line-per-event timeline: timestamp, event type, detai Examples: ```bash -sol muse log 1700000000001 -sol muse log 1700000000001 --json -sol muse log 1700000000001 --full +sol talent log 1700000000001 +sol talent log 1700000000001 --json +sol talent log 1700000000001 --full ``` ## journal layout @@ -170,8 +170,8 @@ Which services write where: ### `sol health` returns "Connection refused" or times out The supervisor is not running. Check if `sol supervisor` is active. The owner may need to start solstone with `sol start` or `make dev`. -### Agent run shows "error" status in `sol muse logs` -Run `sol muse log --full` to see the complete event timeline including the error. Common causes: +### Agent run shows "error" status in `sol talent logs` +Run `sol talent log --full` to see the complete event timeline including the error. Common causes: - API key issues (rate limits, expired keys) - Prompt too large (context overflow) - Network connectivity @@ -182,4 +182,4 @@ Run `sol muse log --full` to see the complete event timeline including the 3. Check if the stream is active: `sol streams` ### High agent costs -Run `sol muse logs --summary` for aggregated cost view. Filter by agent: `sol muse logs --summary`. +Run `sol talent logs --summary` for aggregated cost view. Filter by agent: `sol talent logs --summary`. diff --git a/apps/home/events.py b/apps/home/events.py index 844b9741d..e95075bc7 100644 --- a/apps/home/events.py +++ b/apps/home/events.py @@ -50,7 +50,7 @@ def record_triage_exchange(ctx: EventContext) -> None: path=path, user_message=user_message, agent_response=result, - muse=name, + talent=name, agent_id=agent_id, ) except Exception: diff --git a/apps/settings/routes.py b/apps/settings/routes.py index b4a593018..fc4dc28af 100644 --- a/apps/settings/routes.py +++ b/apps/settings/routes.py @@ -319,7 +319,7 @@ def get_providers() -> Any: - cogitate: Current cogitate provider, tier, and backup - contexts: Configured context overrides from journal.json - context_defaults: Context registry with labels/groups for UI - (includes muse configs with type, schedule, disabled, extract) + (includes talent configs with type, schedule, disabled, extract) - api_keys: Boolean status for each provider's API key - auth: Per-provider auth mode for cogitate ("platform" or "api_key") """ @@ -328,7 +328,7 @@ def get_providers() -> Any: TYPE_DEFAULTS, get_context_registry, ) - from think.muse import get_muse_configs + from think.talent import get_talent_configs from think.providers import get_provider_list config = get_journal_config() @@ -356,19 +356,19 @@ def get_providers() -> Any: "label": ctx_config["label"], "group": ctx_config["group"], } - # Include type for muse contexts + # Include type for talent contexts if "type" in ctx_config: context_defaults[pattern]["type"] = ctx_config["type"] - # Enhance muse contexts with additional metadata from get_muse_configs - from think.muse import key_to_context + # Enhance talent contexts with additional metadata from get_talent_configs + from think.talent import key_to_context - muse_configs = get_muse_configs(include_disabled=True) - for key, info in muse_configs.items(): + talent_configs = get_talent_configs(include_disabled=True) + for key, info in talent_configs.items(): context_key = key_to_context(key) if context_key in context_defaults: - # Add muse-specific fields + # Add talent-specific fields if "schedule" in info: context_defaults[context_key]["schedule"] = info["schedule"] context_defaults[context_key]["disabled"] = info.get("disabled", False) @@ -475,7 +475,7 @@ def update_providers() -> Any: Set or clear context overrides Setting a context to null removes the override. - For muse contexts, disabled and extract can also be set. + For talent contexts, disabled and extract can also be set. """ try: from think.providers import PROVIDER_REGISTRY @@ -698,9 +698,9 @@ def update_providers() -> Any: def _build_generator_info(key: str, info: dict) -> dict: - """Build generator info dict from muse config for Settings UI. + """Build generator info dict from talent config for Settings UI. - Transforms muse config metadata into the format expected by the + Transforms talent config metadata into the format expected by the Settings UI Insights section. """ # Determine if extraction is supported (occurrence/anticipation hooks) @@ -725,7 +725,7 @@ def _build_generator_info(key: str, info: dict) -> dict: def get_generators() -> Any: """Return generators grouped by schedule for Settings UI. - This is a compatibility layer that transforms the unified muse config + This is a compatibility layer that transforms the unified talent config into the format expected by the Settings UI Insights section. Returns: @@ -733,10 +733,10 @@ def get_generators() -> Any: - daily: List of daily-schedule generators """ try: - from think.muse import get_muse_configs + from think.talent import get_talent_configs # Get all generate prompts - all_generators = get_muse_configs(type="generate", include_disabled=True) + all_generators = get_talent_configs(type="generate", include_disabled=True) segment = [] daily = [] @@ -787,7 +787,7 @@ def update_generators() -> Any: old_contexts = old_providers.get("contexts", {}) changed_fields = {} - from think.muse import key_to_context + from think.talent import key_to_context for key, updates in request_data.items(): if not isinstance(updates, dict): diff --git a/apps/speakers/attribution.py b/apps/speakers/attribution.py index 8654caf7e..c538c2ca8 100644 --- a/apps/speakers/attribution.py +++ b/apps/speakers/attribution.py @@ -11,7 +11,7 @@ Layer 2: Structural heuristics (speaker count, setting field, screen.md, meetings.md) — no LLM Layer 3: Acoustic matching (voiceprint cosine similarity, same-stream preference) — no LLM -Layer 4: Contextual identification (LLM) — handled externally via muse hook +Layer 4: Contextual identification (LLM) — handled externally via talent hook High-confidence attributions from Layers 2-3 automatically accumulate into entity voiceprints, creating a learning flywheel. diff --git a/apps/speakers/muse/speakers/SKILL.md b/apps/speakers/talent/speakers/SKILL.md similarity index 100% rename from apps/speakers/muse/speakers/SKILL.md rename to apps/speakers/talent/speakers/SKILL.md diff --git a/apps/stats/routes.py b/apps/stats/routes.py index 9587de7ea..0d6b7e985 100644 --- a/apps/stats/routes.py +++ b/apps/stats/routes.py @@ -10,7 +10,7 @@ from typing import Any from flask import Blueprint, jsonify from convey import state -from think.muse import get_muse_configs +from think.talent import get_talent_configs stats_bp = Blueprint( "app:stats", @@ -37,6 +37,6 @@ def stats_data() -> Any: except Exception: pass - response["generators"] = get_muse_configs(type="generate") + response["generators"] = get_talent_configs(type="generate") return jsonify(response) diff --git a/apps/support/muse/support.md b/apps/support/talent/support.md similarity index 100% rename from apps/support/muse/support.md rename to apps/support/talent/support.md diff --git a/apps/support/muse/support/SKILL.md b/apps/support/talent/support/SKILL.md similarity index 100% rename from apps/support/muse/support/SKILL.md rename to apps/support/talent/support/SKILL.md diff --git a/apps/support/tools.py b/apps/support/tools.py index 38bcea8cc..19a92d43e 100644 --- a/apps/support/tools.py +++ b/apps/support/tools.py @@ -3,7 +3,7 @@ """Support tool functions for agent workflows. -Each function provides a discrete capability that both the muse agent +Each function provides a discrete capability that both the talent agent (via ``sol call support``) and the convey routes can use. All outbound operations are **consent-gated** — they return a draft for review rather than submitting directly. diff --git a/apps/todos/muse/daily.md b/apps/todos/talent/daily.md similarity index 100% rename from apps/todos/muse/daily.md rename to apps/todos/talent/daily.md diff --git a/apps/todos/muse/todo.md b/apps/todos/talent/todo.md similarity index 100% rename from apps/todos/muse/todo.md rename to apps/todos/talent/todo.md diff --git a/apps/todos/muse/todos/SKILL.md b/apps/todos/talent/todos/SKILL.md similarity index 100% rename from apps/todos/muse/todos/SKILL.md rename to apps/todos/talent/todos/SKILL.md diff --git a/apps/todos/muse/weekly.md b/apps/todos/talent/weekly.md similarity index 100% rename from apps/todos/muse/weekly.md rename to apps/todos/talent/weekly.md diff --git a/apps/transcripts/muse/transcripts/SKILL.md b/apps/transcripts/talent/transcripts/SKILL.md similarity index 100% rename from apps/transcripts/muse/transcripts/SKILL.md rename to apps/transcripts/talent/transcripts/SKILL.md diff --git a/convey/root.py b/convey/root.py index 8d8e34bb6..e045866a5 100644 --- a/convey/root.py +++ b/convey/root.py @@ -127,5 +127,5 @@ def app_today() -> Any: @bp.route("/") def index() -> Any: - """Root redirect — always to home, onboarding muse handles new journals.""" + """Root redirect — always to home, onboarding talent handles new journals.""" return redirect(url_for("app:home.index")) diff --git a/convey/triage.py b/convey/triage.py index 59bdf2172..7f3feefc3 100644 --- a/convey/triage.py +++ b/convey/triage.py @@ -31,7 +31,7 @@ def triage() -> Any: WebSocket (cortex/finish event). For reload recovery, use GET /result/. Routes based on onboarding status: new/in-progress journals go to - the onboarding muse, completed/skipped journals go to unified. + the onboarding talent, completed/skipped journals go to unified. """ payload = request.get_json(force=True) message = payload.get("message", "").strip() @@ -65,10 +65,10 @@ def triage() -> Any: # Path A active — use triage with observation context agent_name = "triage" elif onboarding_status not in ("complete", "skipped"): - # Onboarding not yet completed — route to onboarding muse + # Onboarding not yet completed — route to onboarding talent agent_name = "onboarding" elif has_conversation: - # Conversation context present — use unified muse + # Conversation context present — use unified talent agent_name = "unified" else: agent_name = "unified" diff --git a/docs/APPS.md b/docs/APPS.md index 13a1ac029..2ae900165 100644 --- a/docs/APPS.md +++ b/docs/APPS.md @@ -43,7 +43,7 @@ apps/my_app/ ├── app.json # Optional: Metadata (icon, label, facet support) ├── app_bar.html # Optional: Bottom bar controls (forms, buttons) ├── background.html # Optional: Background JavaScript service -├── muse/ # Optional: Custom agents, generators, and skills (auto-discovered) +├── talent/ # Optional: Custom agents, generators, and skills (auto-discovered) │ └── my-skill/ # Optional: Agent Skill directories (SKILL.md + resources) ├── maint/ # Optional: One-time maintenance tasks (auto-discovered) └── tests/ # Optional: App-specific tests (run via make test-apps) @@ -61,7 +61,7 @@ apps/my_app/ | `app.json` | No | Icon, label, facet support overrides | | `app_bar.html` | No | Bottom fixed bar for app controls | | `background.html` | No | Background service (WebSocket listeners) | -| `muse/` | No | Custom agents, generators, and skills (`.md` files + skill subdirectories) | +| `talent/` | No | Custom agents, generators, and skills (`.md` files + skill subdirectories) | | `maint/` | No | One-time maintenance tasks (run on Convey startup) | | `tests/` | No | App-specific tests with self-contained fixtures | @@ -284,17 +284,17 @@ app = typer.Typer(help="Description of your app commands.") --- -### 8. `muse/` - App Generators +### 8. `talent/` - App Generators Define custom generator prompts that integrate with solstone's output generation system. **Key Points:** -- Create `muse/` directory with `.md` files containing JSON frontmatter +- Create `talent/` directory with `.md` files containing JSON frontmatter - App generators are automatically discovered alongside system generators - Keys are namespaced as `{app}:{agent}` (e.g., `my_app:weekly_summary`) - Outputs go to `JOURNAL/YYYYMMDD/agents/__.md` (or `.json` if `output: "json"`) -**Metadata format:** Same schema as system generators in `muse/*.md` - JSON frontmatter includes `title`, `description`, `color`, `schedule` (required), `priority` (required for scheduled prompts), `hook`, `output`, `max_output_tokens`, and `thinking_budget` fields. The `schedule` field must be `"segment"` or `"daily"`. The `priority` field is required for all scheduled prompts - prompts without explicit priority will fail validation. Set `output: "json"` for structured JSON output instead of markdown. Optional `max_output_tokens` sets the maximum response length; `thinking_budget` sets the model's thinking token budget (provider-specific defaults apply if omitted). +**Metadata format:** Same schema as system generators in `talent/*.md` - JSON frontmatter includes `title`, `description`, `color`, `schedule` (required), `priority` (required for scheduled prompts), `hook`, `output`, `max_output_tokens`, and `thinking_budget` fields. The `schedule` field must be `"segment"` or `"daily"`. The `priority` field is required for all scheduled prompts - prompts without explicit priority will fail validation. Set `output: "json"` for structured JSON output instead of markdown. Optional `max_output_tokens` sets the maximum response length; `thinking_budget` sets the model's thinking token budget (provider-specific defaults apply if omitted). **Priority bands:** Prompts run in priority order (lowest first). Recommended bands: - 10-30: Generators (content-producing prompts) @@ -329,7 +329,7 @@ The `occurrences` field (optional string) provides agent-specific extraction gui - Use `"hook": {"post": "my_hook"}` for post-processing hooks - Use both together: `"hook": {"pre": "prep", "post": "process"}` - Use `"hook": {"flush": true}` to opt into segment flush (see below) -- Resolution: `"name"` → `muse/{name}.py`, `"app:name"` → `apps/{app}/muse/{name}.py`, or explicit path +- Resolution: `"name"` → `talent/{name}.py`, `"app:name"` → `apps/{app}/talent/{name}.py`, or explicit path **Pre-hooks** (`pre_process`): Modify inputs before the LLM call - `context` is the full config dict with: `name`, `agent_id`, `provider`, `model`, `prompt`, `system_instruction` (if set), `user_instruction`, `output`, `meta`, and for generators: `day`, `segment`, `span`, `span_mode`, `transcript`, `output_path` @@ -346,7 +346,7 @@ The `occurrences` field (optional string) provides agent-specific extraction gui Hook errors are logged but don't crash the pipeline (graceful degradation). ```python -# muse/my_hook.py +# talent/my_hook.py def pre_process(context: dict) -> dict | None: # Modify inputs before LLM call return {"prompt": context["prompt"] + "\n\nBe concise."} @@ -357,32 +357,32 @@ def post_process(result: str, context: dict) -> str | None: ``` **Reference implementations:** -- System generator templates: `muse/*.md` (files with `schedule` field but no `tools` field) -- Extraction hooks: `muse/occurrence.py`, `muse/anticipation.py` -- Discovery logic: `think/muse.py` - `get_muse_configs(has_tools=False)`, `get_output_name()` -- Hook loading: `think/muse.py` - `load_pre_hook()`, `load_post_hook()` +- System generator templates: `talent/*.md` (files with `schedule` field but no `tools` field) +- Extraction hooks: `talent/occurrence.py`, `talent/anticipation.py` +- Discovery logic: `think/talent.py` - `get_muse_configs(has_tools=False)`, `get_output_name()` +- Hook loading: `think/talent.py` - `load_pre_hook()`, `load_post_hook()` --- -### 9. `muse/` - App Agents and Generators +### 9. `talent/` - App Agents and Generators Define custom agents and generator templates that integrate with solstone's Cortex agent system. **Key Points:** -- Create `muse/` directory with `.md` files containing JSON frontmatter +- Create `talent/` directory with `.md` files containing JSON frontmatter - Both agents and generators live in the same directory - distinguished by frontmatter fields - Agents have a `tools` field, generators have `schedule` but no `tools` - App agents/generators are automatically discovered alongside system ones - Keys are namespaced as `{app}:{name}` (e.g., `my_app:helper`) - Agents inherit all system agent capabilities (tools, scheduling, multi-facet) -**Metadata format:** Same schema as system agents in `muse/*.md` - JSON frontmatter includes `title`, `provider`, `model`, `tools`, `schedule`, `priority`, `multi_facet`, `max_output_tokens`, and `thinking_budget` fields. The `priority` field is **required** for all scheduled prompts - prompts without explicit priority will fail validation. See the priority bands documentation in [THINK.md](THINK.md#unified-priority-execution). Optional `max_output_tokens` sets the maximum response length; `thinking_budget` sets the model's thinking token budget (provider-specific defaults apply if omitted; OpenAI uses fixed reasoning and ignores this field). See [CORTEX.md](CORTEX.md) for agent configuration details. +**Metadata format:** Same schema as system agents in `talent/*.md` - JSON frontmatter includes `title`, `provider`, `model`, `tools`, `schedule`, `priority`, `multi_facet`, `max_output_tokens`, and `thinking_budget` fields. The `priority` field is **required** for all scheduled prompts - prompts without explicit priority will fail validation. See the priority bands documentation in [THINK.md](THINK.md#unified-priority-execution). Optional `max_output_tokens` sets the maximum response length; `thinking_budget` sets the model's thinking token budget (provider-specific defaults apply if omitted; OpenAI uses fixed reasoning and ignores this field). See [CORTEX.md](CORTEX.md) for agent configuration details. **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. **Reference implementations:** -- System agent examples: `muse/*.md` (files with `tools` field) -- Discovery logic: `think/muse.py` - `get_muse_configs(has_tools=True)`, `get_agent()` +- System agent examples: `talent/*.md` (files with `tools` field) +- Discovery logic: `think/talent.py` - `get_muse_configs(has_tools=True)`, `get_agent()` #### Prompt Context Configuration @@ -406,25 +406,25 @@ Context is provided inline in the `.md` body via template variables: - `$facets` - focused facet context or all available facets - `$activity_context` - activity metadata, segment state, and analysis focus sections -**Authoritative source:** `think/muse.py` - `_DEFAULT_LOAD`, `source_is_enabled()`, `source_is_required()`, `get_agent_filter()` +**Authoritative source:** `think/talent.py` - `_DEFAULT_LOAD`, `source_is_enabled()`, `source_is_required()`, `get_agent_filter()` --- -### 10. `muse/` - Agent Skills +### 10. `talent/` - Agent Skills -Define [Agent Skills](https://agentskills.io/specification) as subdirectories within `muse/`. Skills package procedural knowledge, workflows, and resources that AI coding agents (Claude Code, GitHub Copilot, Gemini CLI, etc.) can discover and use on demand. +Define [Agent Skills](https://agentskills.io/specification) as subdirectories within `talent/`. Skills package procedural knowledge, workflows, and resources that AI coding agents (Claude Code, GitHub Copilot, Gemini CLI, etc.) can discover and use on demand. **Key Points:** -- Create a subdirectory in `muse/` with a `SKILL.md` file (YAML frontmatter + markdown body) +- Create a subdirectory in `talent/` with a `SKILL.md` file (YAML frontmatter + markdown body) - The directory name must match the `name` field in the YAML frontmatter -- Skill names must be unique across system `muse/` and all `apps/*/muse/` directories +- Skill names must be unique across system `talent/` and all `apps/*/talent/` directories - `make skills` discovers all skills and symlinks them into `.agents/skills/` and `.claude/skills/` -- Skills are standalone — they don't interact with the muse agent/generator system -- The muse loader ignores subdirectories, so skills won't interfere with agent discovery +- Skills are standalone — they don't interact with the talent agent/generator system +- The talent loader ignores subdirectories, so skills won't interfere with agent discovery **Directory structure:** ``` -muse/my-skill/ +talent/my-skill/ ├── SKILL.md # Required: YAML frontmatter + instructions ├── scripts/ # Optional: Executable code (Python, Bash, etc.) ├── references/ # Optional: Additional documentation loaded on demand @@ -453,14 +453,14 @@ Step-by-step procedures, examples, and domain knowledge for the agent. - `metadata` — Arbitrary key-value string map - `allowed-tools` — Space-delimited list of pre-approved tools (experimental) -**App skills** work the same way — place a skill directory inside `apps/my_app/muse/`: +**App skills** work the same way — place a skill directory inside `apps/my_app/talent/`: ``` -apps/my_app/muse/my-skill/ +apps/my_app/talent/my-skill/ ├── SKILL.md └── references/ ``` -**Running `make skills`:** Discovers all `SKILL.md` files under `muse/*/` and `apps/*/muse/*/`, then creates symlinks so that all supported coding agents see the same skills. Errors if two skills share the same directory name. +**Running `make skills`:** Discovers all `SKILL.md` files under `talent/*/` and `apps/*/talent/*/`, then creates symlinks so that all supported coding agents see the same skills. Errors if two skills share the same directory name. --- diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 36a9c28f9..d58a03075 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -13,7 +13,7 @@ Tactical work items prioritized for implementation. - [ ] Update supervisor/dream interaction to use dynamic daily schedule from daily schedule agent output - [ ] Create segment agent for voiceprint detection and updating via hooks -- [ ] Surface named hook outputs in agents app and sol muse CLI +- [ ] Surface named hook outputs in agents app and sol talent CLI - [ ] Make daily schedule agents idempotent with state tracking (show existing vs new segments) - [ ] Add activities attach/update CLI tools for facet curation (like entity tools) diff --git a/docs/CALLOSUM.md b/docs/CALLOSUM.md index 83635f979..ae225e35b 100644 --- a/docs/CALLOSUM.md +++ b/docs/CALLOSUM.md @@ -111,7 +111,7 @@ Callosum is a JSON-per-line message bus for real-time event distribution across **`status`** - Periodic progress (every ~5s). Fields: `mode`, `day`, `segment`, `stream`, `agents_completed`, `agents_total`, `current_group_priority`, `current_agents` (list of running agent names). In `--segments` batch mode, also includes `segments_completed`, `segments_total`. In activity mode, includes `activity`, `facet`. ### `activity` - Activity lifecycle events -**Sources:** `muse/activity_state.py` (post-hook), `muse/activities.py` (post-hook) +**Sources:** `talent/activity_state.py` (post-hook), `talent/activities.py` (post-hook) **Events:** `live`, `recorded` **Event Log:** Logged to `//events.jsonl` by supervisor diff --git a/docs/CORTEX.md b/docs/CORTEX.md index 879f400eb..b0cd742ba 100644 --- a/docs/CORTEX.md +++ b/docs/CORTEX.md @@ -37,7 +37,7 @@ Requests are created via `cortex_request()` from `think.cortex_client`, which br "event": "request", "ts": 1234567890123, // Required: millisecond timestamp (must match agent_id in filename) "prompt": "Analyze this code for security issues", // Required for agents (not generators) - "name": "default", // Optional: agent name from muse/*.md + "name": "default", // Optional: agent name from talent/*.md "provider": "openai", // Optional: override provider (openai, google, anthropic) "max_output_tokens": 8192, // Optional: maximum response tokens "thinking_budget": 10000, // Optional: thinking token budget (ignored by OpenAI) @@ -53,7 +53,7 @@ Requests are created via `cortex_request()` from `think.cortex_client`, which br } ``` -The model is automatically resolved based on the muse context (`muse.{source}.{name}`) +The model is automatically resolved based on the talent context (`talent.{source}.{name}`) and the configured tier in `journal.json`. Provider can optionally be overridden at request time, which will resolve the appropriate model for that provider at the same tier. @@ -65,7 +65,7 @@ Generators are spawned via Cortex when a request has an `output` field but no `t { "event": "request", "ts": 1234567890123, // Required: millisecond timestamp - "name": "activity", // Required: generator name from muse/*.md + "name": "activity", // Required: generator name from talent/*.md "day": "20250109", // Required: day in YYYYMMDD format "output": "md", // Required: output format ("md" or "json") "segment": "120000_300", // Optional: single segment key (HHMMSS_duration) @@ -240,14 +240,14 @@ When an agent completes successfully, its result can be automatically written to ## Agent Configuration -Agents use configurations stored in the `muse/` directory. Each agent is a `.md` file containing: +Agents use configurations stored in the `talent/` directory. Each agent is a `.md` file containing: - JSON frontmatter with metadata and configuration - The agent-specific prompt and instructions in the content When spawning an agent: 1. Cortex passes the raw request to `sol agents` via stdin (NDJSON format) 2. The agent process (`think/agents.py`) handles all config loading via `prepare_config()`: - - Loads agent configuration using `get_agent()` from `think/muse.py` + - Loads agent configuration using `get_agent()` from `think/talent.py` - Merges request parameters with agent defaults - Resolves provider and model based on context 3. The agent validates the config via `validate_config()` before execution @@ -256,7 +256,7 @@ When spawning an agent: - `extra_context`: Runtime context (facets, generators list, datetime) - `user_instruction`: The agent's `.md` file content -Agents define specialized behaviors and facet expertise. Available agents can be discovered using `get_muse_configs(type="cogitate")` or by listing files in the `muse/` directory. +Agents define specialized behaviors and facet expertise. Available agents can be discovered using `get_muse_configs(type="cogitate")` or by listing files in the `talent/` directory. ### Agent Configuration Options @@ -281,7 +281,7 @@ The JSON frontmatter for an agent can include: ### Model Resolution Models are resolved automatically based on context and tier: -1. Each muse config has a context pattern: `muse.{source}.{name}` (e.g., `muse.system.default`) +1. Each talent config has a context pattern: `talent.{source}.{name}` (e.g., `talent.system.default`) 2. The context determines the tier (pro/flash/lite) from `journal.json` or system defaults 3. The tier + provider determines the actual model to use @@ -290,8 +290,8 @@ This allows controlling model selection via tier configuration rather than hardc { "providers": { "contexts": { - "muse.system.default": {"tier": 1}, - "muse.*": {"tier": 2} + "talent.system.default": {"tier": 1}, + "talent.*": {"tier": 2} } } } diff --git a/docs/JOURNAL.md b/docs/JOURNAL.md index 281928dfc..d2cc04ede 100644 --- a/docs/JOURNAL.md +++ b/docs/JOURNAL.md @@ -322,9 +322,9 @@ The `providers` block enables fine-grained control over which LLM provider and m }, "contexts": { "observe.*": {"provider": "google", "tier": 3}, - "muse.system.*": {"tier": 1}, - "muse.system.meetings": {"provider": "anthropic", "disabled": true}, - "muse.entities.observer": {"tier": 2, "extract": false} + "talent.system.*": {"tier": 1}, + "talent.system.meetings": {"provider": "anthropic", "disabled": true}, + "talent.entities.observer": {"tier": 2, "extract": false} }, "models": { "google": { @@ -354,15 +354,15 @@ If a requested tier is unavailable for a provider, the system falls back to more #### Context matching Contexts are matched in order of specificity: -1. **Exact match** – `"muse.system.meetings"` matches only that exact context +1. **Exact match** – `"talent.system.meetings"` matches only that exact context 2. **Glob pattern** – `"observe.*"` matches any context starting with `observe.` 3. **Default** – Falls back to the `default` configuration #### Context naming convention -Muse configs (agents and generators) use the pattern `muse.{source}.{name}`: -- System configs: `muse.system.{name}` (e.g., `muse.system.meetings`, `muse.system.default`) -- App configs: `muse.{app}.{name}` (e.g., `muse.entities.observer`, `muse.support.support`) +Talent configs (agents and generators) use the pattern `talent.{source}.{name}`: +- System configs: `talent.system.{name}` (e.g., `talent.system.meetings`, `talent.system.default`) +- App configs: `talent.{app}.{name}` (e.g., `talent.entities.observer`, `talent.support.support`) Other contexts follow the pattern `{module}.{feature}[.{operation}]`: - Observe pipeline: `observe.describe.frame`, `observe.enrich`, `observe.transcribe.gemini` @@ -378,7 +378,7 @@ Other contexts follow the pattern `{module}.{feature}[.{operation}]`: - `provider` (string) – Override provider (optional, inherits from default). - `tier` (integer) – Tier number (optional). - `model` (string) – Explicit model name (optional, overrides tier). -- `disabled` (boolean) – Disable this muse config (optional, muse contexts only). +- `disabled` (boolean) – Disable this talent config (optional, talent contexts only). - `extract` (boolean) – Enable/disable event extraction for generators with occurrence/anticipation hooks (optional). **models** – Per-provider tier overrides. Maps provider name to tier-model mappings: @@ -1058,17 +1058,17 @@ Agent outputs are AI-generated markdown files that provide human-readable narrat #### Segment outputs -After captures are processed, segment-level outputs are generated within each segment folder as `HHMMSS_LEN/*.md` files. Available segment output types are defined by templates in `muse/` with `"schedule": "segment"` in their metadata JSON. +After captures are processed, segment-level outputs are generated within each segment folder as `HHMMSS_LEN/*.md` files. Available segment output types are defined by templates in `talent/` with `"schedule": "segment"` in their metadata JSON. #### Daily outputs Post-processing generates day-level outputs in the `agents/` directory that synthesize all segments. **Generator discovery:** Available generator types are discovered at runtime from: -- `muse/*.md` – system generator templates (files with `schedule` field but no `tools` field) -- `apps/{app}/muse/*.md` – app-specific generator templates +- `talent/*.md` – system generator templates (files with `schedule` field but no `tools` field) +- `apps/{app}/talent/*.md` – app-specific generator templates -Each template is a `.md` file with JSON frontmatter containing metadata (title, description, schedule, output format). The `schedule` field is required and must be `"segment"` or `"daily"` - generators with missing or invalid schedule are skipped. Use `get_muse_configs(has_tools=False)` from `think/muse.py` to retrieve all available generators, or `get_muse_configs(has_tools=False, schedule="daily")` to get generators filtered by schedule. +Each template is a `.md` file with JSON frontmatter containing metadata (title, description, schedule, output format). The `schedule` field is required and must be `"segment"` or `"daily"` - generators with missing or invalid schedule are skipped. Use `get_muse_configs(has_tools=False)` from `think/talent.py` to retrieve all available generators, or `get_muse_configs(has_tools=False, schedule="daily")` to get generators filtered by schedule. **Output naming:** - System outputs: `agents/{agent}.md` (e.g., `agents/flow.md`, `agents/meetings.md`) diff --git a/docs/PROMPT_TEMPLATES.md b/docs/PROMPT_TEMPLATES.md index 975447023..13931d225 100644 --- a/docs/PROMPT_TEMPLATES.md +++ b/docs/PROMPT_TEMPLATES.md @@ -130,7 +130,7 @@ The `$segment_preamble` or `$daily_preamble` template provides standardized cont **Optional model configuration:** Add `max_output_tokens` (response length limit) and `thinking_budget` (model thinking token budget) to override provider defaults. -**Reference:** `muse/*.md` for examples (files with `schedule` field but no `tools` field) +**Reference:** `talent/*.md` for examples (files with `schedule` field but no `tools` field) ### For Agents @@ -152,7 +152,7 @@ from `sol/identity.md` via `_load_sol_vars()`. Each agent's `.md` file includes **Optional model configuration:** Add `max_output_tokens` (response length limit) and `thinking_budget` (model thinking token budget) to override provider defaults. Note: OpenAI uses fixed reasoning and ignores `thinking_budget`. -**Reference:** `think/muse.py` → `get_agent()` for agent configuration loading +**Reference:** `think/talent.py` → `get_agent()` for agent configuration loading ### The load_prompt() Function @@ -196,5 +196,5 @@ load_prompt("myprompt", context={"custom_var": "value"}) | Core load function | `think/prompts.py` (`load_prompt`) | | Template files | `think/templates/*.md` | | Test coverage | `tests/test_template_substitution.py` | -| Generator prompts | `muse/*.md` (files with `schedule` field but no `tools`) | -| Agent prompts | `muse/*.md` (files with `tools` field) | +| Generator prompts | `talent/*.md` (files with `schedule` field but no `tools`) | +| Agent prompts | `talent/*.md` (files with `tools` field) | diff --git a/docs/PROVIDERS.md b/docs/PROVIDERS.md index 8ace911af..bea8fcaae 100644 --- a/docs/PROVIDERS.md +++ b/docs/PROVIDERS.md @@ -217,24 +217,24 @@ usage_dict = { Context strings determine provider and model selection. Providers receive already-resolved models, but understanding the system helps: **Context naming convention:** -- Muse configs (agents/generators): `muse.{source}.{name}` where source is `system` or app name - - System: `muse.system.meetings`, `muse.system.default` - - App: `muse.entities.observer`, `muse.chat.helper` +- Talent configs (agents/generators): `talent.{source}.{name}` where source is `system` or app name + - System: `talent.system.meetings`, `talent.system.default` + - App: `talent.entities.observer`, `talent.chat.helper` - Other contexts: `{module}.{feature}[.{operation}]` - Examples: `observe.describe.frame`, `app.chat.title` **Dynamic discovery:** All context metadata (tier/label/group) is defined in prompt .md files via YAML frontmatter: - Prompt files: Listed in `PROMPT_PATHS` in `think/models.py` - add `context`, `tier`, `label`, `group` fields - Categories: `observe/categories/*.md` - add `tier`, `label`, `group` fields -- System muse: `muse/*.md` - add `tier`, `label`, `group` fields in frontmatter -- App muse: `apps/*/muse/*.md` - add `tier`, `label`, `group` fields in frontmatter +- System talent: `talent/*.md` - add `tier`, `label`, `group` fields in frontmatter +- App talent: `apps/*/talent/*.md` - add `tier`, `label`, `group` fields in frontmatter All contexts are discovered at runtime. Use `get_context_registry()` to get the complete context map. **Resolution** (handled by `think/models.py` `resolve_provider(context, agent_type)`): 1. Exact match in journal.json `providers.contexts` 2. Glob pattern match (fnmatch) with specificity ranking -3. Dynamic context registry (discovered prompts, categories, muse configs) +3. Dynamic context registry (discovered prompts, categories, talent configs) 4. Type-specific default (from `providers.generate` or `providers.cogitate`) 5. System defaults from `TYPE_DEFAULTS` diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 53f8cfc40..5d880c4ac 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -42,7 +42,7 @@ Strategic direction and planned capabilities. ## Agent Customization -- Journal-level muse directory (`/muse/`) for custom agents and frontmatter overrides +- Journal-level talent directory (`/talent/`) for custom agents and frontmatter overrides ## Convey UI diff --git a/docs/SOLCLI.md b/docs/SOLCLI.md index 012cebe2e..7a1211c5a 100644 --- a/docs/SOLCLI.md +++ b/docs/SOLCLI.md @@ -13,7 +13,7 @@ The CLI has two tiers with distinct purposes: ### The boundary -**If an AI agent should tool-call it → `sol call`.** These commands appear in SKILL.md files and are invoked by muse agents during conversations. +**If an AI agent should tool-call it → `sol call`.** These commands appear in SKILL.md files and are invoked by talent agents during conversations. **If it's system plumbing → `sol `.** Processing pipelines, supervisor, services, capture — things that cron or systemd runs. @@ -134,7 +134,7 @@ def list_items( 3. **Create the agent skill** (if agents should use these commands): ```markdown -# apps/myapp/muse/myapp/SKILL.md +# apps/myapp/talent/myapp/SKILL.md --- name: myapp description: > @@ -175,14 +175,14 @@ Use this when the command depends heavily on `think/` internals and shouldn't li from think.tools.mytools import app as mytools_app call_app.add_typer(mytools_app, name="mytools") ``` -3. **Optionally create a skill** in `muse//SKILL.md`. +3. **Optionally create a skill** in `talent//SKILL.md`. ### Files to maintain for a new call command | File | What to do | Required? | |------|-----------|-----------| | `apps//call.py` | Typer app with commands | Yes | -| `apps//muse//SKILL.md` | Skill doc for agents | If agents should use it | +| `apps//talent//SKILL.md` | Skill doc for agents | If agents should use it | | `.agents/skills/` | Symlink (via `make skills`) | Auto-generated | | `AGENTS.md` Skills table | Add trigger description | If skill exists | | `tests/test__call.py` | CLI tests | Yes | @@ -264,10 +264,10 @@ solstone/ │ ├── todos/ │ │ ├── call.py # sol call todos (auto-discovered) │ │ ├── todo.py # Data models -│ │ └── muse/todos/SKILL.md # Agent skill doc +│ │ └── talent/todos/SKILL.md # Agent skill doc │ ├── calendar/ │ │ ├── call.py # sol call calendar (auto-discovered) -│ │ └── muse/calendar/SKILL.md +│ │ └── talent/calendar/SKILL.md │ ├── entities/call.py │ ├── speakers/call.py │ ├── support/call.py @@ -275,7 +275,7 @@ solstone/ │ ├── agent/call.py │ ├── awareness/call.py │ └── ... (web-only apps without call.py) -├── muse/ +├── talent/ │ ├── journal/SKILL.md # Skills not tied to an app │ ├── coding/SKILL.md │ └── *.md # Agent prompt files @@ -296,7 +296,7 @@ solstone/ | Think (processing) | `import`, `dream`, `planner`, `indexer`, `supervisor`, `schedule`, `top`, `health`, `callosum`, `notify`, `heartbeat` | | Service | `service` (+ aliases `up`, `down`, `start`) | | Observe (capture) | `transcribe`, `describe`, `sense`, `sync`, `transfer`, `remote` | -| Muse (AI agents) | `agents`, `cortex`, `muse`, `call`, `engage` | +| Talent (AI agents) | `agents`, `cortex`, `talent`, `call`, `engage` | | Convey (web UI) | `convey`, `restart-convey`, `screenshot`, `maint` | | Specialized | `config`, `streams`, `journal-stats`, `formatter`, `detect-created` | | Help | `help`, `chat` | @@ -323,8 +323,8 @@ solstone/ Skills are documented in `SKILL.md` files and symlinked into `.agents/skills/` by `make skills`. **Skill locations:** -- App skills: `apps//muse//SKILL.md` -- Core skills: `muse//SKILL.md` +- App skills: `apps//talent//SKILL.md` +- Core skills: `talent//SKILL.md` **Skill ≠ call command.** Not every skill has a corresponding `call.py`, and not every `call.py` has a skill: - `health`, `coding`, `vit`, `onboarding` have skills but no `call.py` diff --git a/docs/THINK.md b/docs/THINK.md index 72049f4de..b045c022f 100644 --- a/docs/THINK.md +++ b/docs/THINK.md @@ -20,15 +20,15 @@ The package exposes several commands: - `sol agents` is the unified CLI for tool agents and generators (spawned by Cortex, NDJSON protocol). - `sol supervisor` monitors observation heartbeats. Use `--no-observers` to disable local capture (sense still runs for remote uploads and imports). - `sol cortex` starts a Callosum-based service for managing AI agent instances and generators. -- `sol muse` lists available agents and generators with their configuration. Use `sol muse show ` to see details, and `sol muse show --prompt` to see the fully composed prompt that would be sent to the LLM. +- `sol talent` lists available agents and generators with their configuration. Use `sol talent show ` to see details, and `sol talent show --prompt` to see the fully composed prompt that would be sent to the LLM. ```bash sol call transcripts read YYYYMMDD [--start HHMMSS --length MINUTES] sol dream [--day YYYYMMDD] [--segment HHMMSS_LEN] [--stream NAME] [--refresh] [--flush] sol supervisor [--no-observers] sol cortex [--host HOST] [--port PORT] [--path PATH] -sol muse list [--schedule daily|segment] [--json] -sol muse show [--prompt] [--day YYYYMMDD] [--segment HHMMSS_LEN] [--full] +sol talent list [--schedule daily|segment] [--json] +sol talent show [--prompt] [--day YYYYMMDD] [--segment HHMMSS_LEN] [--full] ``` Use `--refresh` to overwrite existing files, and `-v` for verbose logs. @@ -175,7 +175,7 @@ which automatically routes to the configured provider based on context. ## Generator map keys -`think.muse.get_muse_configs(has_tools=False)` reads the `.md` prompt files under `muse/` and +`think.talent.get_muse_configs(has_tools=False)` reads the `.md` prompt files under `talent/` and returns a dictionary keyed by generator name. Each entry contains: - `path` – the prompt file path @@ -209,7 +209,7 @@ request_file = cortex_request( agents_info = cortex_agents(limit=10, agent_type="live") print(f"Found {agents_info['live_count']} running agents") ``` -# Muse Module +# Talent Module AI agent system and tool-calling support for solstone. @@ -251,7 +251,7 @@ Providers implement `run_generate()`, `run_agenerate()`, and `run_cogitate()` fu ## Agent Personas -System prompts in `muse/*.md` (markdown with JSON frontmatter). Apps can add custom agents in `apps/{app}/muse/`. +System prompts in `talent/*.md` (markdown with JSON frontmatter). Apps can add custom agents in `apps/{app}/talent/`. JSON metadata supports `title`, `provider`, `model`, `tools`, `schedule`, `priority`, `multi_facet`, and `load` keys. diff --git a/pyproject.toml b/pyproject.toml index aac633958..3722b3d0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,15 +92,15 @@ Documentation = "https://github.com/solpbc/solstone/blob/main/README.md" "Bug Tracker" = "https://github.com/solpbc/solstone/issues" [tool.setuptools.packages.find] -include = ["apps*", "think*", "convey*", "observe*", "muse*"] +include = ["apps*", "think*", "convey*", "observe*", "talent*"] [tool.setuptools] py-modules = ["media", "sol"] [tool.setuptools.package-data] -apps = ["*/templates/*.html", "*/muse/*.md"] +apps = ["*/templates/*.html", "*/talent/*.md"] think = ["*.md", "*.json", "templates/*.md"] -muse = ["*.md", "*.py"] +talent = ["*.md", "*.py"] observe = ["*.md", "categories/*.md", "transcribe/*.md"] convey = [ "templates/*.html", diff --git a/sol.py b/sol.py index 0fb5a5f21..cd92df9b3 100644 --- a/sol.py +++ b/sol.py @@ -60,10 +60,10 @@ COMMANDS: dict[str, str] = { "sync": "observe.sync", "transfer": "observe.transfer", "remote": "observe.remote_cli", - # AI agents (formerly muse package) + # AI agents (talent package) "agents": "think.agents", "cortex": "think.cortex", - "muse": "think.muse_cli", + "talent": "think.talent_cli", "call": "think.call", "engage": "think.engage", "help": "think.help_cli", @@ -117,10 +117,10 @@ GROUPS: dict[str, list[str]] = { "transfer", "remote", ], - "Muse (AI agents)": [ + "Talent (AI agents)": [ "agents", "cortex", - "muse", + "talent", "call", "engage", ], diff --git a/muse/__init__.py b/talent/__init__.py similarity index 52% rename from muse/__init__.py rename to talent/__init__.py index e02d4947f..fb75dbc78 100644 --- a/muse/__init__.py +++ b/talent/__init__.py @@ -1,4 +1,4 @@ # SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc -"""Muse - unified agentic prompts for agents and insights.""" +"""Talent - unified agentic prompts for agents and insights.""" diff --git a/muse/activities.py b/talent/activities.py similarity index 99% rename from muse/activities.py rename to talent/activities.py index c3656ce52..a974d091f 100644 --- a/muse/activities.py +++ b/talent/activities.py @@ -26,7 +26,7 @@ import json import logging import os -from muse.activity_state import ( +from talent.activity_state import ( check_timeout, find_previous_segment, ) @@ -381,7 +381,7 @@ def _pre_process_normal(context: dict) -> dict | None: if not timed_out: # Look back for facets missing from the adjacent previous segment # but present in earlier segments (covers gaps from agent failures). - from muse.activity_state import _get_preceding_segments + from talent.activity_state import _get_preceding_segments for earlier_seg in _get_preceding_segments(day, prev_segment, stream=stream): if check_timeout(segment, earlier_seg): diff --git a/muse/activity_state.py b/talent/activity_state.py similarity index 100% rename from muse/activity_state.py rename to talent/activity_state.py diff --git a/muse/anticipation.md b/talent/anticipation.md similarity index 100% rename from muse/anticipation.md rename to talent/anticipation.md diff --git a/muse/anticipation.py b/talent/anticipation.py similarity index 97% rename from muse/anticipation.py rename to talent/anticipation.py index 4a3faed1e..664aee2de 100644 --- a/muse/anticipation.py +++ b/talent/anticipation.py @@ -20,7 +20,7 @@ from think.hooks import ( write_events_jsonl, ) from think.models import generate -from think.muse import get_output_name +from think.talent import get_output_name from think.prompts import load_prompt @@ -57,7 +57,7 @@ def post_process(result: str, context: dict) -> str | None: try: response_text = generate( contents=contents, - context=f"muse.system.{name}", + context=f"talent.system.{name}", temperature=0.3, max_output_tokens=24576, thinking_budget=0, diff --git a/muse/chat.md b/talent/chat.md similarity index 99% rename from muse/chat.md rename to talent/chat.md index 576dd9c40..466265567 100644 --- a/muse/chat.md +++ b/talent/chat.md @@ -2,7 +2,7 @@ "type": "cogitate", "title": "Sol", "description": "Sol — the journal itself, as a conversational partner", - "hook": {"pre": "muse/chat_context.py"} + "hook": {"pre": "talent/chat_context.py"} } $sol_identity diff --git a/muse/chat_context.py b/talent/chat_context.py similarity index 99% rename from muse/chat_context.py rename to talent/chat_context.py index b62aeaaa8..24d1ef233 100644 --- a/muse/chat_context.py +++ b/talent/chat_context.py @@ -3,7 +3,7 @@ """Pre-hook: inject chat-bar context into sol's user instruction. -Replaces conversation_memory as the unified muse's pre-hook. +Replaces conversation_memory as the unified talent's pre-hook. Appends conversation memory, location/health context instructions, awareness-conditional guidance, and behavioral defaults to the identity-first prompt. @@ -261,7 +261,7 @@ def _get_eligible_suggestion( def pre_process(context: dict) -> dict | None: - """Append chat-context instructions to the unified muse prompt.""" + """Append chat-context instructions to the unified talent prompt.""" from think.awareness import get_imports, get_onboarding from think.conversation import build_memory_context from think.utils import get_config diff --git a/muse/coder.md b/talent/coder.md similarity index 100% rename from muse/coder.md rename to talent/coder.md diff --git a/muse/coding/SKILL.md b/talent/coding/SKILL.md similarity index 97% rename from muse/coding/SKILL.md rename to talent/coding/SKILL.md index 55ed898c9..347dff156 100644 --- a/muse/coding/SKILL.md +++ b/talent/coding/SKILL.md @@ -46,7 +46,7 @@ description: > ```bash make install # Install package (includes all deps) -make skills # Discover and symlink Agent Skills from muse/ dirs +make skills # Discover and symlink Agent Skills from talent/ dirs make format # Auto-fix formatting, then report remaining issues make test # Run unit tests make ci # Full CI check (format check + lint + test) @@ -72,7 +72,7 @@ pipelines that should only be operated by the supervisor or human operators: - `sol indexer --reset` — destructive index rebuild (read-only queries via `sol indexer` are fine) Agents should use `sol call` commands for journal interaction and `sol health` / -`sol muse logs` for diagnostics. +`sol talent logs` for diagnostics. ## Reference diff --git a/muse/coding/reference/coding-standards.md b/talent/coding/reference/coding-standards.md similarity index 100% rename from muse/coding/reference/coding-standards.md rename to talent/coding/reference/coding-standards.md diff --git a/muse/coding/reference/environment.md b/talent/coding/reference/environment.md similarity index 100% rename from muse/coding/reference/environment.md rename to talent/coding/reference/environment.md diff --git a/muse/coding/reference/project-structure.md b/talent/coding/reference/project-structure.md similarity index 72% rename from muse/coding/reference/project-structure.md rename to talent/coding/reference/project-structure.md index d3f4e9444..3f8607c2e 100644 --- a/muse/coding/reference/project-structure.md +++ b/talent/coding/reference/project-structure.md @@ -9,7 +9,7 @@ solstone/ ├── think/ # Data post-processing, AI agents & orchestration ├── convey/ # Web app frontend & backend ├── apps/ # Convey app extensions (see docs/APPS.md) -├── muse/ # Agent/generator configs + Agent Skills (muse/*/SKILL.md) +├── talent/ # Agent/generator configs + Agent Skills (talent/*/SKILL.md) ├── tests/ # Pytest test suites + test fixtures under tests/fixtures/ ├── docs/ # All documentation (*.md files) ├── AGENTS.md # Development guidelines (this file) @@ -34,14 +34,14 @@ Each package has a README.md symlink pointing to its documentation in `docs/`. ## Agent & Skill Organization -`muse/*.md` stores agent personas and generator templates. Apps can add their own in `apps/*/muse/*.md`. Skills live at `muse/*/SKILL.md` and are symlinked to `.agents/skills/` and `.claude/skills/` via `make skills`. +`talent/*.md` stores agent personas and generator templates. Apps can add their own in `apps/*/talent/*.md`. Skills live at `talent/*/SKILL.md` and are symlinked to `.agents/skills/` and `.claude/skills/` via `make skills`. ## File Locations - **Entry Points**: `sol.py` `COMMANDS` dict - **Test Fixtures**: `tests/fixtures/journal/` - complete mock journal - **Live Logs**: `journal/health/.log` -- **Agent Personas**: `muse/*.md` (apps can add their own in `muse/`, see [docs/APPS.md](docs/APPS.md)) -- **Generator Templates**: `muse/*.md` (apps can add their own in `muse/`, see [docs/APPS.md](docs/APPS.md)) -- **Agent Skills**: `muse/*/SKILL.md` - symlinked to `.agents/skills/` and `.claude/skills/` via `make skills`, read https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices to create the best skills +- **Agent Personas**: `talent/*.md` (apps can add their own in `talent/`, see [docs/APPS.md](docs/APPS.md)) +- **Generator Templates**: `talent/*.md` (apps can add their own in `talent/`, see [docs/APPS.md](docs/APPS.md)) +- **Agent Skills**: `talent/*/SKILL.md` - symlinked to `.agents/skills/` and `.claude/skills/` via `make skills`, read https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices to create the best skills - **Scratch Space**: `scratch/` - git-ignored local workspace diff --git a/muse/coding/reference/testing.md b/talent/coding/reference/testing.md similarity index 100% rename from muse/coding/reference/testing.md rename to talent/coding/reference/testing.md diff --git a/muse/conversation_memory.py b/talent/conversation_memory.py similarity index 84% rename from muse/conversation_memory.py rename to talent/conversation_memory.py index fa1f9d7a0..da339e64b 100644 --- a/muse/conversation_memory.py +++ b/talent/conversation_memory.py @@ -1,11 +1,11 @@ # SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc -"""Pre-hook: inject conversation memory into unified muse context. +"""Pre-hook: inject conversation memory into unified talent context. Loaded via hook config: {"hook": {"pre": "conversation_memory"}} -Replaces CONVERSATION_MEMORY_INJECTION_POINT in the unified muse's +Replaces CONVERSATION_MEMORY_INJECTION_POINT in the unified talent's user instruction with recent conversation exchanges and today's summary. This gives the agent awareness of past conversations without needing to search — recent interactions are always in context. @@ -17,7 +17,7 @@ logger = logging.getLogger(__name__) def pre_process(context: dict) -> dict | None: - """Inject conversation memory into the unified muse's user instruction. + """Inject conversation memory into the unified talent's user instruction. Args: context: Full agent config dict. diff --git a/muse/daily_schedule.md b/talent/daily_schedule.md similarity index 100% rename from muse/daily_schedule.md rename to talent/daily_schedule.md diff --git a/muse/daily_schedule.py b/talent/daily_schedule.py similarity index 100% rename from muse/daily_schedule.py rename to talent/daily_schedule.py diff --git a/muse/decisionalizer.md b/talent/decisionalizer.md similarity index 100% rename from muse/decisionalizer.md rename to talent/decisionalizer.md diff --git a/muse/decisions.md b/talent/decisions.md similarity index 100% rename from muse/decisions.md rename to talent/decisions.md diff --git a/muse/entities.md b/talent/entities.md similarity index 100% rename from muse/entities.md rename to talent/entities.md diff --git a/muse/entities.py b/talent/entities.py similarity index 100% rename from muse/entities.py rename to talent/entities.py diff --git a/muse/facet_newsletter.md b/talent/facet_newsletter.md similarity index 100% rename from muse/facet_newsletter.md rename to talent/facet_newsletter.md diff --git a/muse/firstday_checkin.md b/talent/firstday_checkin.md similarity index 100% rename from muse/firstday_checkin.md rename to talent/firstday_checkin.md diff --git a/muse/firstday_checkin.py b/talent/firstday_checkin.py similarity index 100% rename from muse/firstday_checkin.py rename to talent/firstday_checkin.py diff --git a/muse/flow.md b/talent/flow.md similarity index 100% rename from muse/flow.md rename to talent/flow.md diff --git a/muse/followups.md b/talent/followups.md similarity index 100% rename from muse/followups.md rename to talent/followups.md diff --git a/muse/heartbeat.md b/talent/heartbeat.md similarity index 96% rename from muse/heartbeat.md rename to talent/heartbeat.md index 728bfc486..31e89bf35 100644 --- a/muse/heartbeat.md +++ b/talent/heartbeat.md @@ -30,8 +30,8 @@ If you find issues: update agency.md's `## system` section via ## Step 2: Check journal quality -Run `sol muse logs --daily -c 10` to review recent agent runs and -`sol muse logs --errors -c 10` for recent errors. Look for: +Run `sol talent logs --daily -c 10` to review recent agent runs and +`sol talent logs --errors -c 10` for recent errors. Look for: - Broken segments (transcription failures, missing agent output) - Processing gaps (capture with no dream processing) - Orphaned entities (zero observations after 7+ days) diff --git a/muse/joke_bot.md b/talent/joke_bot.md similarity index 100% rename from muse/joke_bot.md rename to talent/joke_bot.md diff --git a/muse/journal/SKILL.md b/talent/journal/SKILL.md similarity index 98% rename from muse/journal/SKILL.md rename to talent/journal/SKILL.md index ac7034de9..f00e3016d 100644 --- a/muse/journal/SKILL.md +++ b/talent/journal/SKILL.md @@ -105,7 +105,7 @@ Create a new facet directory and initial `facet.json`. - `--emoji`: optional icon emoji (default: `📦`). - `--color`: optional hex color (default: `#667eea`). - `--description`: optional description text. -- `--consent`: asserts that the agent has received a direct owner request or explicit owner approval before calling this command. Pass when acting proactively (cogitate, suggestion flows) rather than in direct response to an owner instruction. Omit for the onboarding muse — onboarding is owner-driven by definition. Adds `"consent": true` to the audit log entry. +- `--consent`: asserts that the agent has received a direct owner request or explicit owner approval before calling this command. Pass when acting proactively (cogitate, suggestion flows) rather than in direct response to an owner instruction. Omit for the onboarding talent — onboarding is owner-driven by definition. Adds `"consent": true` to the audit log entry. Examples: diff --git a/muse/knowledge_graph.md b/talent/knowledge_graph.md similarity index 100% rename from muse/knowledge_graph.md rename to talent/knowledge_graph.md diff --git a/muse/meetings.md b/talent/meetings.md similarity index 100% rename from muse/meetings.md rename to talent/meetings.md diff --git a/muse/messaging.md b/talent/messaging.md similarity index 100% rename from muse/messaging.md rename to talent/messaging.md diff --git a/muse/morning_briefing.md b/talent/morning_briefing.md similarity index 100% rename from muse/morning_briefing.md rename to talent/morning_briefing.md diff --git a/muse/naming.md b/talent/naming.md similarity index 97% rename from muse/naming.md rename to talent/naming.md index 958424cb7..2705169db 100644 --- a/muse/naming.md +++ b/talent/naming.md @@ -8,7 +8,7 @@ You are $agent_name's naming ceremony agent. Your role is to propose a meaningfu ## Pre-hooks -Before this muse runs, two checks must pass silently (no output on failure): +Before this talent runs, two checks must pass silently (no output on failure): 1. **Thickness gate** — Run `sol call agent thickness`. If `ready` is `false`, exit silently. 2. **Name gate** — Run `sol call agent name`. If `name_status` is not `"default"`, exit silently. diff --git a/muse/observation.md b/talent/observation.md similarity index 100% rename from muse/observation.md rename to talent/observation.md diff --git a/muse/observation.py b/talent/observation.py similarity index 100% rename from muse/observation.py rename to talent/observation.py diff --git a/muse/observation_review.md b/talent/observation_review.md similarity index 100% rename from muse/observation_review.md rename to talent/observation_review.md diff --git a/muse/occurrence.md b/talent/occurrence.md similarity index 100% rename from muse/occurrence.md rename to talent/occurrence.md diff --git a/muse/occurrence.py b/talent/occurrence.py similarity index 97% rename from muse/occurrence.py rename to talent/occurrence.py index ac7e5b6e3..5123fe99c 100644 --- a/muse/occurrence.py +++ b/talent/occurrence.py @@ -20,7 +20,7 @@ from think.hooks import ( write_events_jsonl, ) from think.models import generate -from think.muse import get_output_name +from think.talent import get_output_name from think.prompts import load_prompt @@ -62,7 +62,7 @@ def post_process(result: str, context: dict) -> str | None: try: response_text = generate( contents=contents, - context=f"muse.system.{name}", + context=f"talent.system.{name}", temperature=0.3, max_output_tokens=24576, thinking_budget=0, diff --git a/muse/onboarding.md b/talent/onboarding.md similarity index 100% rename from muse/onboarding.md rename to talent/onboarding.md diff --git a/muse/onboarding/SKILL.md b/talent/onboarding/SKILL.md similarity index 100% rename from muse/onboarding/SKILL.md rename to talent/onboarding/SKILL.md diff --git a/muse/partner.md b/talent/partner.md similarity index 100% rename from muse/partner.md rename to talent/partner.md diff --git a/muse/patterns/provenance.md b/talent/patterns/provenance.md similarity index 93% rename from muse/patterns/provenance.md rename to talent/patterns/provenance.md index a9bb05135..512b6c850 100644 --- a/muse/patterns/provenance.md +++ b/talent/patterns/provenance.md @@ -2,7 +2,7 @@ How cogitate agents communicate the basis and reliability of their claims. This pattern ensures briefings and reports distinguish between well-sourced facts and inferences. -Canonical implementation: `muse/morning_briefing.md`. +Canonical implementation: `talent/morning_briefing.md`. ## Four Mechanisms @@ -50,7 +50,7 @@ Claims read differently based on evidence strength. The agent expresses confiden **Bidirectional rule:** Strong evidence must NOT get hedging language. Weak evidence must NOT get assertive language. Both directions must be enforced. -**Upstream confidence scores:** Some agents (e.g., `muse/followups.md`, `muse/decisions.md`) emit a `Confidence: 0.0–1.0` field per item. When consuming this output, use the score to inform language grading. The briefing expresses confidence through language, not by forwarding the numeric score. +**Upstream confidence scores:** Some agents (e.g., `talent/followups.md`, `talent/decisions.md`) emit a `Confidence: 0.0–1.0` field per item. When consuming this output, use the score to inform language grading. The briefing expresses confidence through language, not by forwarding the numeric score. ### 4. Tool Error Guard diff --git a/muse/pulse.md b/talent/pulse.md similarity index 100% rename from muse/pulse.md rename to talent/pulse.md diff --git a/muse/routine.md b/talent/routine.md similarity index 100% rename from muse/routine.md rename to talent/routine.md diff --git a/muse/routines/SKILL.md b/talent/routines/SKILL.md similarity index 100% rename from muse/routines/SKILL.md rename to talent/routines/SKILL.md diff --git a/muse/schedule.md b/talent/schedule.md similarity index 100% rename from muse/schedule.md rename to talent/schedule.md diff --git a/muse/screen.md b/talent/screen.md similarity index 100% rename from muse/screen.md rename to talent/screen.md diff --git a/muse/sense.md b/talent/sense.md similarity index 100% rename from muse/sense.md rename to talent/sense.md diff --git a/muse/speaker_attribution.md b/talent/speaker_attribution.md similarity index 100% rename from muse/speaker_attribution.md rename to talent/speaker_attribution.md diff --git a/muse/speaker_attribution.py b/talent/speaker_attribution.py similarity index 98% rename from muse/speaker_attribution.py rename to talent/speaker_attribution.py index 0edcb196e..a240338e9 100644 --- a/muse/speaker_attribution.py +++ b/talent/speaker_attribution.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc -"""Speaker attribution muse hook — orchestrates the 4-layer pipeline. +"""Speaker attribution talent hook — orchestrates the 4-layer pipeline. pre_process: Runs Layers 1-3 (computational). If all sentences are resolved, writes speaker_labels.json and skips the LLM. diff --git a/muse/timeline.md b/talent/timeline.md similarity index 100% rename from muse/timeline.md rename to talent/timeline.md diff --git a/muse/triage.md b/talent/triage.md similarity index 99% rename from muse/triage.md rename to talent/triage.md index b050bba45..3bd05e5ac 100644 --- a/muse/triage.md +++ b/talent/triage.md @@ -107,7 +107,7 @@ When onboarding is complete, check whether the naming ceremony should trigger: 1. Run `sol call agent name` to check status. 2. If `name_status` is `"default"`, run `sol call agent thickness` to check readiness. -3. If `ready` is `true`, mention that you've been getting to know the owner and offer to suggest a name — or let the naming muse handle it. +3. If `ready` is `true`, mention that you've been getting to know the owner and offer to suggest a name — or let the naming talent handle it. 4. Only do this once per session. If you've already checked or offered, don't repeat. 5. If `name_status` is `"chosen"` or `"self-named"`, do nothing. diff --git a/muse/vit/SKILL.md b/talent/vit/SKILL.md similarity index 100% rename from muse/vit/SKILL.md rename to talent/vit/SKILL.md diff --git a/tests/baselines/api/settings/providers.json b/tests/baselines/api/settings/providers.json index cbfb938c9..41442c9c4 100644 --- a/tests/baselines/api/settings/providers.json +++ b/tests/baselines/api/settings/providers.json @@ -20,7 +20,7 @@ "label": "Date Detection", "tier": 3 }, - "muse.entities.entities": { + "talent.entities.entities": { "disabled": false, "group": "Entities", "label": "Entity Detector", @@ -28,7 +28,7 @@ "tier": 3, "type": "cogitate" }, - "muse.entities.entities_review": { + "talent.entities.entities_review": { "disabled": false, "group": "Entities", "label": "Entity Reviewer", @@ -36,21 +36,21 @@ "tier": 2, "type": "cogitate" }, - "muse.entities.entity_assist": { + "talent.entities.entity_assist": { "disabled": false, "group": "Entities", "label": "Entity Assistant", "tier": 2, "type": "cogitate" }, - "muse.entities.entity_describe": { + "talent.entities.entity_describe": { "disabled": false, "group": "Entities", "label": "Entity Description", "tier": 2, "type": "cogitate" }, - "muse.entities.entity_observer": { + "talent.entities.entity_observer": { "disabled": false, "group": "Entities", "label": "Entity Observer", @@ -58,35 +58,35 @@ "tier": 3, "type": "cogitate" }, - "muse.support.support": { + "talent.support.support": { "disabled": false, "group": "Think", "label": "Support", "tier": 2, "type": "cogitate" }, - "muse.system.anticipation": { + "talent.system.anticipation": { "disabled": false, "group": "Think", "label": "Anticipation Extraction", "tier": 2, "type": null }, - "muse.system.chat": { + "talent.system.chat": { "disabled": false, "group": "Think", "label": "Sol", "tier": 2, "type": "cogitate" }, - "muse.system.coder": { + "talent.system.coder": { "disabled": false, "group": "Think", "label": "Coder", "tier": 2, "type": "cogitate" }, - "muse.system.daily_schedule": { + "talent.system.daily_schedule": { "disabled": false, "group": "Think", "label": "Maintenance Window", @@ -94,7 +94,7 @@ "tier": 2, "type": "generate" }, - "muse.system.decisionalizer": { + "talent.system.decisionalizer": { "disabled": false, "group": "Think", "label": "Decision Dossier Generator", @@ -102,7 +102,7 @@ "tier": 2, "type": "cogitate" }, - "muse.system.decisions": { + "talent.system.decisions": { "disabled": false, "extract": true, "group": "Think", @@ -111,7 +111,7 @@ "tier": 2, "type": "generate" }, - "muse.system.entities": { + "talent.system.entities": { "disabled": false, "group": "Think", "label": "Entity Extraction", @@ -119,7 +119,7 @@ "tier": 2, "type": "generate" }, - "muse.system.facet_newsletter": { + "talent.system.facet_newsletter": { "disabled": false, "group": "Think", "label": "Facet Newsletter Generator", @@ -127,7 +127,7 @@ "tier": 3, "type": "cogitate" }, - "muse.system.firstday_checkin": { + "talent.system.firstday_checkin": { "disabled": false, "group": "Think", "label": "First-Day Check-In", @@ -135,7 +135,7 @@ "tier": 3, "type": "generate" }, - "muse.system.flow": { + "talent.system.flow": { "disabled": false, "extract": true, "group": "Think", @@ -144,7 +144,7 @@ "tier": 2, "type": "generate" }, - "muse.system.followups": { + "talent.system.followups": { "disabled": false, "extract": true, "group": "Think", @@ -153,7 +153,7 @@ "tier": 2, "type": "generate" }, - "muse.system.heartbeat": { + "talent.system.heartbeat": { "disabled": false, "group": "Think", "label": "Heartbeat", @@ -161,7 +161,7 @@ "tier": 2, "type": "cogitate" }, - "muse.system.joke_bot": { + "talent.system.joke_bot": { "disabled": false, "group": "Think", "label": "Joke Bot", @@ -169,7 +169,7 @@ "tier": 2, "type": "cogitate" }, - "muse.system.knowledge_graph": { + "talent.system.knowledge_graph": { "disabled": false, "extract": true, "group": "Think", @@ -178,7 +178,7 @@ "tier": 2, "type": "generate" }, - "muse.system.meetings": { + "talent.system.meetings": { "disabled": false, "extract": true, "group": "Think", @@ -187,7 +187,7 @@ "tier": 2, "type": "generate" }, - "muse.system.messaging": { + "talent.system.messaging": { "disabled": false, "extract": true, "group": "Think", @@ -196,7 +196,7 @@ "tier": 2, "type": "generate" }, - "muse.system.morning_briefing": { + "talent.system.morning_briefing": { "disabled": false, "group": "Think", "label": "Morning Briefing", @@ -204,14 +204,14 @@ "tier": 2, "type": "cogitate" }, - "muse.system.naming": { + "talent.system.naming": { "disabled": false, "group": "Think", "label": "Naming", "tier": 2, "type": "cogitate" }, - "muse.system.observation": { + "talent.system.observation": { "disabled": false, "group": "Think", "label": "Observation", @@ -219,28 +219,28 @@ "tier": 3, "type": "generate" }, - "muse.system.observation_review": { + "talent.system.observation_review": { "disabled": false, "group": "Think", "label": "Observation Review", "tier": 2, "type": "cogitate" }, - "muse.system.occurrence": { + "talent.system.occurrence": { "disabled": false, "group": "Think", "label": "Occurrence Extraction", "tier": 2, "type": null }, - "muse.system.onboarding": { + "talent.system.onboarding": { "disabled": false, "group": "Think", "label": "Onboarding", "tier": 2, "type": "cogitate" }, - "muse.system.partner": { + "talent.system.partner": { "disabled": false, "group": "Think", "label": "Partner Profile", @@ -248,7 +248,7 @@ "tier": 2, "type": "cogitate" }, - "muse.system.pulse": { + "talent.system.pulse": { "disabled": false, "group": "Think", "label": "Pulse", @@ -256,7 +256,7 @@ "tier": 3, "type": "cogitate" }, - "muse.system.routine": { + "talent.system.routine": { "disabled": false, "group": "Think", "label": "Routine", @@ -264,7 +264,7 @@ "tier": 2, "type": "cogitate" }, - "muse.system.schedule": { + "talent.system.schedule": { "disabled": false, "extract": true, "group": "Think", @@ -273,7 +273,7 @@ "tier": 2, "type": "generate" }, - "muse.system.screen": { + "talent.system.screen": { "disabled": false, "group": "Think", "label": "Screen Record", @@ -281,7 +281,7 @@ "tier": 2, "type": "generate" }, - "muse.system.sense": { + "talent.system.sense": { "disabled": false, "group": "Think", "label": "Segment Sense", @@ -289,7 +289,7 @@ "tier": 3, "type": "generate" }, - "muse.system.speaker_attribution": { + "talent.system.speaker_attribution": { "disabled": false, "group": "Think", "label": "Speaker Attribution", @@ -297,7 +297,7 @@ "tier": 2, "type": "generate" }, - "muse.system.timeline": { + "talent.system.timeline": { "disabled": false, "extract": true, "group": "Think", @@ -306,14 +306,14 @@ "tier": 2, "type": "generate" }, - "muse.system.triage": { + "talent.system.triage": { "disabled": false, "group": "Think", "label": "Triage", "tier": 2, "type": "cogitate" }, - "muse.todos.daily": { + "talent.todos.daily": { "disabled": false, "group": "Todos", "label": "Daily TODO Curator", @@ -321,7 +321,7 @@ "tier": 3, "type": "cogitate" }, - "muse.todos.todo": { + "talent.todos.todo": { "disabled": false, "group": "Todos", "label": "TODO Detector", @@ -329,7 +329,7 @@ "tier": 3, "type": "cogitate" }, - "muse.todos.weekly": { + "talent.todos.weekly": { "disabled": false, "group": "Todos", "label": "TODO Weekly Scout", diff --git a/tests/baselines/api/stats/stats.json b/tests/baselines/api/stats/stats.json index 7b55b7a63..43764b2be 100644 --- a/tests/baselines/api/stats/stats.json +++ b/tests/baselines/api/stats/stats.json @@ -17,7 +17,7 @@ "max_output_tokens": 512, "mtime": 0, "output": "json", - "path": "/muse/daily_schedule.md", + "path": "/talent/daily_schedule.md", "priority": 10, "schedule": "daily", "source": "system", @@ -47,7 +47,7 @@ "mtime": 0, "occurrences": "Create an occurrence for every decision-action observed. Include the time span, decision type, actors involved, entities affected, and impact assessment. Each occurrence should capture both the intent and enactment of the decision.", "output": "md", - "path": "/muse/decisions.md", + "path": "/talent/decisions.md", "priority": 10, "schedule": "activity", "source": "system", @@ -68,7 +68,7 @@ "max_output_tokens": 1024, "mtime": 0, "output": "md", - "path": "/muse/entities.md", + "path": "/talent/entities.md", "priority": 10, "schedule": "segment", "source": "system", @@ -89,7 +89,7 @@ "max_output_tokens": 256, "mtime": 0, "output": "text", - "path": "/muse/firstday_checkin.md", + "path": "/talent/firstday_checkin.md", "priority": 98, "schedule": "segment", "source": "system", @@ -114,7 +114,7 @@ "mtime": 0, "occurrences": "Create an occurrence for noteworthy shifts in work rhythms or focus. Include timestamps when deep work starts or ends, or when energy levels noticeably change. Classify each as work or personal based on the surrounding context.", "output": "md", - "path": "/muse/flow.md", + "path": "/talent/flow.md", "priority": 10, "schedule": "daily", "source": "system", @@ -143,7 +143,7 @@ "mtime": 0, "occurrences": "Whenever a future task or commitment is mentioned, create an occurrence with the expected action and deadline if known. Note who requested it and whether it is work or personal.", "output": "md", - "path": "/muse/followups.md", + "path": "/talent/followups.md", "priority": 10, "schedule": "activity", "source": "system", @@ -166,7 +166,7 @@ "mtime": 0, "occurrences": "For each entity interaction or relationship mentioned, create an occurrence describing the connection. Include start and end times when the relationship is visible, and capture the type of link such as works-on or discusses-with.", "output": "md", - "path": "/muse/knowledge_graph.md", + "path": "/talent/knowledge_graph.md", "priority": 10, "schedule": "daily", "source": "system", @@ -192,7 +192,7 @@ "mtime": 0, "occurrences": "Each meeting should generate an occurrence with start and end times, list of participants and a concise summary. If slides are present, mention them in the details field.", "output": "md", - "path": "/muse/meetings.md", + "path": "/talent/meetings.md", "priority": 10, "schedule": "activity", "source": "system", @@ -219,7 +219,7 @@ "mtime": 0, "occurrences": "Create an occurrence for every distinct message interaction. Include the time block, app name, contacts or channels involved, whether $preferred was reading or replying, and a summary of visible content.", "output": "md", - "path": "/muse/messaging.md", + "path": "/talent/messaging.md", "priority": 10, "schedule": "activity", "source": "system", @@ -244,7 +244,7 @@ "max_output_tokens": 2048, "mtime": 0, "output": "json", - "path": "/muse/observation.md", + "path": "/talent/observation.md", "priority": 97, "schedule": "segment", "source": "system", @@ -268,7 +268,7 @@ }, "mtime": 0, "output": "md", - "path": "/muse/schedule.md", + "path": "/talent/schedule.md", "priority": 10, "schedule": "daily", "source": "system", @@ -285,7 +285,7 @@ }, "mtime": 0, "output": "md", - "path": "/muse/screen.md", + "path": "/talent/screen.md", "priority": 10, "schedule": "segment", "source": "system", @@ -303,7 +303,7 @@ "max_output_tokens": 4096, "mtime": 0, "output": "json", - "path": "/muse/sense.md", + "path": "/talent/sense.md", "priority": 5, "schedule": "segment", "source": "system", @@ -328,7 +328,7 @@ }, "mtime": 0, "output": "json", - "path": "/muse/speaker_attribution.md", + "path": "/talent/speaker_attribution.md", "priority": 40, "schedule": "segment", "source": "system", @@ -351,7 +351,7 @@ "mtime": 0, "occurrences": "Create an occurrence for each hour segment, don't break down hours into any smaller segments the goal for timeline occurrences is for them to capture whatever happened within each hour of the day where there was activity.", "output": "md", - "path": "/muse/timeline.md", + "path": "/talent/timeline.md", "priority": 10, "schedule": "daily", "source": "system", diff --git a/tests/test_activities.py b/tests/test_activities.py index 0dbd9f963..1d08b960e 100644 --- a/tests/test_activities.py +++ b/tests/test_activities.py @@ -449,7 +449,7 @@ def test_format_activities_context_includes_instructions(): ], ) - from muse.activity_state import format_activities_context + from talent.activity_state import format_activities_context output = format_activities_context("test_facet") @@ -632,7 +632,7 @@ class TestActivityRecordIO: # --------------------------------------------------------------------------- -# Activities Agent Hooks (muse/activities.py) +# Activities Agent Hooks (talent/activities.py) # --------------------------------------------------------------------------- @@ -661,7 +661,7 @@ class TestMakeActivityId: class TestListFacetsWithActivityState: def test_finds_facets(self, monkeypatch): - from muse.activities import _list_facets_with_activity_state + from talent.activities import _list_facets_with_activity_state with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) @@ -675,7 +675,7 @@ class TestListFacetsWithActivityState: assert facets == ["personal", "work"] def test_returns_empty_for_nonexistent(self, monkeypatch): - from muse.activities import _list_facets_with_activity_state + from talent.activities import _list_facets_with_activity_state with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) @@ -689,7 +689,7 @@ class TestListFacetsWithActivityState: class TestDetectEndedActivities: def test_explicit_ended(self): - from muse.activities import _detect_ended_activities + from talent.activities import _detect_ended_activities prev = [ {"activity": "coding", "state": "active", "since": "100000_300"}, @@ -702,7 +702,7 @@ class TestDetectEndedActivities: assert ended[0]["activity"] == "coding" def test_implicit_ended(self): - from muse.activities import _detect_ended_activities + from talent.activities import _detect_ended_activities prev = [ {"activity": "coding", "state": "active", "since": "100000_300"}, @@ -716,7 +716,7 @@ class TestDetectEndedActivities: assert ended[0]["activity"] == "coding" def test_timeout_ends_all(self): - from muse.activities import _detect_ended_activities + from talent.activities import _detect_ended_activities prev = [ {"activity": "coding", "state": "active", "since": "100000_300"}, @@ -726,7 +726,7 @@ class TestDetectEndedActivities: assert len(ended) == 2 def test_continuing_not_ended(self): - from muse.activities import _detect_ended_activities + from talent.activities import _detect_ended_activities prev = [ {"activity": "coding", "state": "active", "since": "100000_300"}, @@ -738,7 +738,7 @@ class TestDetectEndedActivities: assert len(ended) == 0 def test_ignores_previously_ended(self): - from muse.activities import _detect_ended_activities + from talent.activities import _detect_ended_activities prev = [ {"activity": "coding", "state": "ended", "since": "090000_300"}, @@ -751,7 +751,7 @@ class TestDetectEndedActivities: def test_new_activity_same_type(self): """A new activity of same type with different since is not the same.""" - from muse.activities import _detect_ended_activities + from talent.activities import _detect_ended_activities prev = [ {"activity": "coding", "state": "active", "since": "100000_300"}, @@ -766,7 +766,7 @@ class TestDetectEndedActivities: class TestWalkActivitySegments: def test_walks_segments(self, monkeypatch): - from muse.activities import _walk_activity_segments + from talent.activities import _walk_activity_segments with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) @@ -814,7 +814,7 @@ class TestWalkActivitySegments: assert result["active_entities"] == ["VS Code", "Claude Code"] def test_deduplicates_entities(self, monkeypatch): - from muse.activities import _walk_activity_segments + from talent.activities import _walk_activity_segments with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) @@ -857,7 +857,7 @@ class TestWalkActivitySegments: assert result["active_entities"] == ["VS Code", "Git", "Claude Code"] def test_empty_when_no_match(self, monkeypatch): - from muse.activities import _walk_activity_segments + from talent.activities import _walk_activity_segments with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) @@ -873,7 +873,7 @@ class TestPreProcess: """Tests for the activities pre_process hook.""" def test_skips_when_no_previous_segment(self, monkeypatch): - from muse.activities import pre_process + from talent.activities import pre_process with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) @@ -889,7 +889,7 @@ class TestPreProcess: assert "skip_reason" in result def test_skips_when_no_ended_activities(self, monkeypatch): - from muse.activities import pre_process + from talent.activities import pre_process with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) @@ -930,7 +930,7 @@ class TestPreProcess: assert result.get("skip_reason") == "no_ended_activities" def test_detects_ended_and_writes_record(self, monkeypatch): - from muse.activities import pre_process + from talent.activities import pre_process from think.activities import load_activity_records with tempfile.TemporaryDirectory() as tmpdir: @@ -968,7 +968,7 @@ class TestPreProcess: assert records[0]["segments"] == ["100000_300"] def test_idempotent_on_rerun(self, monkeypatch): - from muse.activities import pre_process + from talent.activities import pre_process from think.activities import load_activity_records with tempfile.TemporaryDirectory() as tmpdir: @@ -999,7 +999,7 @@ class TestPreProcess: assert len(records) == 1 def test_multi_facet_detection(self, monkeypatch): - from muse.activities import pre_process + from talent.activities import pre_process from think.activities import load_activity_records with tempfile.TemporaryDirectory() as tmpdir: @@ -1054,7 +1054,7 @@ class TestPreProcess: def test_multi_segment_span(self, monkeypatch): """Activity spanning multiple segments should collect all segments.""" - from muse.activities import pre_process + from talent.activities import pre_process from think.activities import load_activity_records with tempfile.TemporaryDirectory() as tmpdir: @@ -1127,7 +1127,7 @@ class TestPostProcess: """Tests for the activities post_process hook.""" def test_updates_descriptions(self, monkeypatch): - from muse.activities import post_process + from talent.activities import post_process from think.activities import append_activity_record, load_activity_records with tempfile.TemporaryDirectory() as tmpdir: @@ -1162,19 +1162,19 @@ class TestPostProcess: ) def test_handles_invalid_json(self): - from muse.activities import post_process + from talent.activities import post_process result = post_process("not json", {"day": "20260209"}) assert result is None def test_handles_non_object(self): - from muse.activities import post_process + from talent.activities import post_process result = post_process("[]", {"day": "20260209"}) assert result is None def test_returns_none(self, monkeypatch): - from muse.activities import post_process + from talent.activities import post_process with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) @@ -1204,7 +1204,7 @@ class TestPreProcessMeta: """Tests for pre-hook stashing record data in meta.""" def test_meta_contains_activity_records(self, monkeypatch): - from muse.activities import pre_process + from talent.activities import pre_process with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) @@ -1248,7 +1248,7 @@ class TestPreProcessMeta: assert rec["description"] == "Writing code" def test_meta_multiple_facets(self, monkeypatch): - from muse.activities import pre_process + from talent.activities import pre_process with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) @@ -1299,7 +1299,7 @@ class TestPostProcessEvents: def test_emits_events_with_llm_description(self, monkeypatch): from unittest.mock import patch - from muse.activities import post_process + from talent.activities import post_process from think.activities import append_activity_record with tempfile.TemporaryDirectory() as tmpdir: @@ -1339,7 +1339,7 @@ class TestPostProcessEvents: } } - with patch("muse.activities.callosum_send") as mock_send: + with patch("talent.activities.callosum_send") as mock_send: mock_send.return_value = True post_process( llm_result, @@ -1363,7 +1363,7 @@ class TestPostProcessEvents: def test_falls_back_to_prehook_description_with_warning(self, monkeypatch, caplog): from unittest.mock import patch - from muse.activities import post_process + from talent.activities import post_process with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) @@ -1383,11 +1383,11 @@ class TestPostProcessEvents: } # LLM returns empty — no descriptions to update - with patch("muse.activities.callosum_send") as mock_send: + with patch("talent.activities.callosum_send") as mock_send: mock_send.return_value = True import logging - with caplog.at_level(logging.WARNING, logger="muse.activities"): + with caplog.at_level(logging.WARNING, logger="talent.activities"): post_process( "{}", {"day": "20260209", "segment": "100500_300", "meta": meta}, @@ -1401,19 +1401,19 @@ class TestPostProcessEvents: def test_no_events_without_meta(self, monkeypatch): from unittest.mock import patch - from muse.activities import post_process + from talent.activities import post_process with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) - with patch("muse.activities.callosum_send") as mock_send: + with patch("talent.activities.callosum_send") as mock_send: post_process("{}", {"day": "20260209", "segment": "100500_300"}) mock_send.assert_not_called() def test_event_emission_failure_does_not_raise(self, monkeypatch): from unittest.mock import patch - from muse.activities import post_process + from talent.activities import post_process with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) @@ -1432,7 +1432,7 @@ class TestPostProcessEvents: } } - with patch("muse.activities.callosum_send") as mock_send: + with patch("talent.activities.callosum_send") as mock_send: mock_send.side_effect = OSError("socket error") # Should not raise result = post_process( @@ -1556,7 +1556,7 @@ class TestPreProcessFlush: """Tests for the activities pre_process hook in flush mode.""" def test_flush_ends_all_active_activities(self, monkeypatch): - from muse.activities import pre_process + from talent.activities import pre_process from think.activities import load_activity_records with tempfile.TemporaryDirectory() as tmpdir: @@ -1601,7 +1601,7 @@ class TestPreProcessFlush: assert records[0]["segments"] == ["100000_300"] def test_flush_skips_when_no_active_activities(self, monkeypatch): - from muse.activities import pre_process + from talent.activities import pre_process with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) @@ -1632,7 +1632,7 @@ class TestPreProcessFlush: assert result["skip_reason"] == "no_active_activities" def test_flush_skips_when_no_activity_state(self, monkeypatch): - from muse.activities import pre_process + from talent.activities import pre_process with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) @@ -1652,7 +1652,7 @@ class TestPreProcessFlush: assert result["skip_reason"] == "no_activity_state" def test_flush_handles_multiple_facets(self, monkeypatch): - from muse.activities import pre_process + from talent.activities import pre_process from think.activities import load_activity_records with tempfile.TemporaryDirectory() as tmpdir: @@ -1707,7 +1707,7 @@ class TestPreProcessFlush: assert len(personal_records) == 1 def test_flush_is_idempotent(self, monkeypatch): - from muse.activities import pre_process + from talent.activities import pre_process from think.activities import load_activity_records with tempfile.TemporaryDirectory() as tmpdir: @@ -1742,7 +1742,7 @@ class TestPreProcessFlush: assert len(records) == 1 def test_flush_stashes_meta_for_post_hook(self, monkeypatch): - from muse.activities import pre_process + from talent.activities import pre_process with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", tmpdir) diff --git a/tests/test_activity_state.py b/tests/test_activity_state.py index e75ec9207..038b4cadc 100644 --- a/tests/test_activity_state.py +++ b/tests/test_activity_state.py @@ -13,25 +13,25 @@ class TestExtractFacetFromOutputPath: """Tests for _extract_facet_from_output_path.""" def test_extracts_facet_from_valid_path(self): - from muse.activity_state import _extract_facet_from_output_path + from talent.activity_state import _extract_facet_from_output_path path = "/journal/20260130/143000_300/agents/work/activity_state.json" assert _extract_facet_from_output_path(path) == "work" def test_extracts_facet_with_hyphen(self): - from muse.activity_state import _extract_facet_from_output_path + from talent.activity_state import _extract_facet_from_output_path path = "/journal/20260130/143000_300/agents/my-project/activity_state.json" assert _extract_facet_from_output_path(path) == "my-project" def test_returns_none_for_empty_path(self): - from muse.activity_state import _extract_facet_from_output_path + from talent.activity_state import _extract_facet_from_output_path assert _extract_facet_from_output_path("") is None assert _extract_facet_from_output_path(None) is None def test_returns_none_for_non_matching_path(self): - from muse.activity_state import _extract_facet_from_output_path + from talent.activity_state import _extract_facet_from_output_path # Different generator name assert _extract_facet_from_output_path("/path/to/facets.json") is None @@ -46,7 +46,7 @@ class TestFindPreviousSegment: """Tests for find_previous_segment.""" def test_finds_previous_segment(self): - from muse.activity_state import find_previous_segment + from talent.activity_state import find_previous_segment with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -70,7 +70,7 @@ class TestFindPreviousSegment: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path def test_returns_none_for_nonexistent_day(self): - from muse.activity_state import find_previous_segment + from talent.activity_state import find_previous_segment with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -83,7 +83,7 @@ class TestFindPreviousSegment: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path def test_handles_segments_with_suffix(self): - from muse.activity_state import find_previous_segment + from talent.activity_state import find_previous_segment with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -110,19 +110,19 @@ class TestCheckTimeout: """Tests for check_timeout.""" def test_no_timeout_within_threshold(self): - from muse.activity_state import check_timeout + from talent.activity_state import check_timeout # 5 minute gap (300 seconds) assert check_timeout("100500_300", "100000_300", timeout_seconds=3600) is False def test_timeout_exceeds_threshold(self): - from muse.activity_state import check_timeout + from talent.activity_state import check_timeout # 2 hour gap assert check_timeout("120000_300", "100000_300", timeout_seconds=3600) is True def test_uses_segment_end_time(self): - from muse.activity_state import check_timeout + from talent.activity_state import check_timeout # Previous segment: 10:00:00 - 10:05:00 (300 seconds) # Current segment: 10:10:00 @@ -134,7 +134,7 @@ class TestLoadPreviousState: """Tests for load_previous_state.""" def test_loads_valid_state(self): - from muse.activity_state import load_previous_state + from talent.activity_state import load_previous_state with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -171,7 +171,7 @@ class TestLoadPreviousState: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path def test_returns_none_for_missing_file(self): - from muse.activity_state import load_previous_state + from talent.activity_state import load_previous_state with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -193,7 +193,7 @@ class TestLoadPreviousState: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path def test_rejects_non_array(self): - from muse.activity_state import load_previous_state + from talent.activity_state import load_previous_state with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -224,7 +224,7 @@ class TestFormatActivitiesContext: """Tests for format_activities_context.""" def test_formats_activities_list(self): - from muse.activity_state import format_activities_context + from talent.activity_state import format_activities_context with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -255,7 +255,7 @@ class TestFormatActivitiesContext: def test_handles_empty_activities(self): """Facet with no activities.jsonl still gets always-on defaults.""" - from muse.activity_state import format_activities_context + from talent.activity_state import format_activities_context with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -281,7 +281,7 @@ class TestFormatPreviousState: """Tests for format_previous_state.""" def test_formats_active_activities(self): - from muse.activity_state import format_previous_state + from talent.activity_state import format_previous_state state = [ { @@ -303,7 +303,7 @@ class TestFormatPreviousState: assert "since" not in result def test_formats_ended_activities(self): - from muse.activity_state import format_previous_state + from talent.activity_state import format_previous_state state = [ { @@ -321,7 +321,7 @@ class TestFormatPreviousState: assert "email" in result def test_handles_timeout(self): - from muse.activity_state import format_previous_state + from talent.activity_state import format_previous_state state = [{"activity": "meeting", "state": "active"}] result = format_previous_state( @@ -331,13 +331,13 @@ class TestFormatPreviousState: assert "meeting" not in result def test_handles_no_previous_state(self): - from muse.activity_state import format_previous_state + from talent.activity_state import format_previous_state result = format_previous_state(None, None, "100000_300", timed_out=False) assert "No previous segment state" in result def test_handles_empty_list(self): - from muse.activity_state import format_previous_state + from talent.activity_state import format_previous_state result = format_previous_state([], "100000_300", "100500_300", timed_out=False) assert "No activities were detected" in result @@ -347,7 +347,7 @@ class TestPreProcess: """Tests for the pre_process hook function.""" def test_builds_enriched_context(self): - from muse.activity_state import pre_process + from talent.activity_state import pre_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -414,7 +414,7 @@ class TestPreProcess: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path def test_returns_none_without_day(self): - from muse.activity_state import pre_process + from talent.activity_state import pre_process context = { "segment": "100000_300", @@ -423,7 +423,7 @@ class TestPreProcess: assert pre_process(context) is None def test_returns_none_without_segment(self): - from muse.activity_state import pre_process + from talent.activity_state import pre_process context = { "day": "20260130", @@ -432,7 +432,7 @@ class TestPreProcess: assert pre_process(context) is None def test_returns_none_without_facet_in_path(self): - from muse.activity_state import pre_process + from talent.activity_state import pre_process context = { "day": "20260130", @@ -446,7 +446,7 @@ class TestPostProcess: """Tests for the post_process hook function.""" def test_new_activity_gets_current_segment(self): - from muse.activity_state import post_process + from talent.activity_state import post_process llm_output = json.dumps( [ @@ -468,7 +468,7 @@ class TestPostProcess: assert items[0]["level"] == "high" def test_continuing_activity_copies_since(self): - from muse.activity_state import post_process + from talent.activity_state import post_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -526,7 +526,7 @@ class TestPostProcess: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path def test_ended_activity_copies_since(self): - from muse.activity_state import post_process + from talent.activity_state import post_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -583,7 +583,7 @@ class TestPostProcess: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path def test_no_previous_state_continuing_becomes_new(self): - from muse.activity_state import post_process + from talent.activity_state import post_process llm_output = json.dumps( [ @@ -607,7 +607,7 @@ class TestPostProcess: def test_unmatched_ended_with_novel_description_becomes_active(self): """Ended activity with no previous active match but novel description is treated as a new active activity (LLM mis-tagged).""" - from muse.activity_state import post_process + from talent.activity_state import post_process llm_output = json.dumps( [ @@ -632,7 +632,7 @@ class TestPostProcess: def test_unmatched_ended_with_empty_description_dropped(self): """Ended activity with no previous active match and no description is dropped as redundant.""" - from muse.activity_state import post_process + from talent.activity_state import post_process llm_output = json.dumps( [ @@ -650,7 +650,7 @@ class TestPostProcess: def test_unmatched_ended_matching_prev_ended_dropped(self): """Ended activity that matches a previously ended activity is dropped.""" - from muse.activity_state import post_process + from talent.activity_state import post_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -706,7 +706,7 @@ class TestPostProcess: def test_unmatched_ended_novel_desc_with_prev_ended_becomes_active(self): """Ended activity with novel description (different from prev ended) is promoted to active.""" - from muse.activity_state import post_process + from talent.activity_state import post_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -762,33 +762,33 @@ class TestPostProcess: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path def test_empty_array_passthrough(self): - from muse.activity_state import post_process + from talent.activity_state import post_process result = post_process("[]", {"segment": "143000_300"}) assert result is not None assert json.loads(result) == [] def test_malformed_json_returns_none(self): - from muse.activity_state import post_process + from talent.activity_state import post_process result = post_process("not json", {"segment": "143000_300"}) assert result is None def test_non_array_returns_none(self): - from muse.activity_state import post_process + from talent.activity_state import post_process result = post_process('{"active": []}', {"segment": "143000_300"}) assert result is None def test_missing_segment_returns_none(self): - from muse.activity_state import post_process + from talent.activity_state import post_process result = post_process("[]", {}) assert result is None def test_same_type_transition_end_and_new(self): """One meeting ends, another starts — both get correct since.""" - from muse.activity_state import post_process + from talent.activity_state import post_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -857,7 +857,7 @@ class TestPostProcess: def test_default_level_for_new(self): """New activity without level gets default 'medium'.""" - from muse.activity_state import post_process + from talent.activity_state import post_process llm_output = json.dumps( [{"activity": "coding", "state": "new", "description": "Writing code"}] @@ -869,7 +869,7 @@ class TestPostProcess: def test_active_entities_passthrough_on_new(self): """active_entities array is passed through on new activities.""" - from muse.activity_state import post_process + from talent.activity_state import post_process llm_output = json.dumps( [ @@ -889,7 +889,7 @@ class TestPostProcess: def test_active_entities_omitted_when_empty(self): """active_entities is omitted from output when not provided or empty.""" - from muse.activity_state import post_process + from talent.activity_state import post_process llm_output = json.dumps( [ @@ -909,7 +909,7 @@ class TestPostProcess: def test_active_entities_omitted_on_ended(self): """active_entities is not included on ended activities.""" - from muse.activity_state import post_process + from talent.activity_state import post_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -966,7 +966,7 @@ class TestPostProcess: def test_fuzzy_match_disambiguates_same_type(self): """Multiple same-type previous activities matched by description.""" - from muse.activity_state import post_process + from talent.activity_state import post_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -1033,7 +1033,7 @@ class TestActivityId: """Tests for the id field added to resolved activity entries.""" def test_new_activity_gets_id(self): - from muse.activity_state import post_process + from talent.activity_state import post_process llm_output = json.dumps( [ @@ -1051,7 +1051,7 @@ class TestActivityId: assert items[0]["id"] == "coding_143000_300" def test_continuing_activity_preserves_since_in_id(self): - from muse.activity_state import post_process + from talent.activity_state import post_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -1106,7 +1106,7 @@ class TestActivityId: os.environ["_SOLSTONE_JOURNAL_OVERRIDE"] = original_path def test_ended_activity_gets_id(self): - from muse.activity_state import post_process + from talent.activity_state import post_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -1161,7 +1161,7 @@ class TestActivityId: def test_promoted_ended_gets_new_id(self): """Ended activity promoted to active gets id with current segment.""" - from muse.activity_state import post_process + from talent.activity_state import post_process llm_output = json.dumps( [ @@ -1185,7 +1185,7 @@ class TestActivityLiveEvents: def test_emits_live_for_new_activity(self): from unittest.mock import patch - from muse.activity_state import post_process + from talent.activity_state import post_process llm_output = json.dumps( [ @@ -1205,7 +1205,7 @@ class TestActivityLiveEvents: "output_path": "/j/20260130/143000_300/agents/work/activity_state.json", } - with patch("muse.activity_state.callosum_send") as mock_send: + with patch("talent.activity_state.callosum_send") as mock_send: mock_send.return_value = True post_process(llm_output, context) @@ -1226,7 +1226,7 @@ class TestActivityLiveEvents: def test_emits_live_for_continuing_activity(self): from unittest.mock import patch - from muse.activity_state import post_process + from talent.activity_state import post_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -1272,7 +1272,7 @@ class TestActivityLiveEvents: "output_path": f"{tmpdir}/20260130/100500_300/agents/work/activity_state.json", } - with patch("muse.activity_state.callosum_send") as mock_send: + with patch("talent.activity_state.callosum_send") as mock_send: mock_send.return_value = True post_process(llm_output, context) @@ -1288,7 +1288,7 @@ class TestActivityLiveEvents: def test_no_live_event_for_ended_activity(self): from unittest.mock import patch - from muse.activity_state import post_process + from talent.activity_state import post_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -1333,7 +1333,7 @@ class TestActivityLiveEvents: "output_path": f"{tmpdir}/20260130/100500_300/agents/work/activity_state.json", } - with patch("muse.activity_state.callosum_send") as mock_send: + with patch("talent.activity_state.callosum_send") as mock_send: post_process(llm_output, context) mock_send.assert_not_called() @@ -1344,7 +1344,7 @@ class TestActivityLiveEvents: def test_no_live_events_without_day_or_facet(self): from unittest.mock import patch - from muse.activity_state import post_process + from talent.activity_state import post_process llm_output = json.dumps( [ @@ -1358,14 +1358,14 @@ class TestActivityLiveEvents: ) # No day — events should not fire - with patch("muse.activity_state.callosum_send") as mock_send: + with patch("talent.activity_state.callosum_send") as mock_send: post_process(llm_output, {"segment": "143000_300"}) mock_send.assert_not_called() def test_live_event_failure_does_not_break_posthook(self): from unittest.mock import patch - from muse.activity_state import post_process + from talent.activity_state import post_process llm_output = json.dumps( [ @@ -1384,7 +1384,7 @@ class TestActivityLiveEvents: "output_path": "/j/20260130/143000_300/agents/work/activity_state.json", } - with patch("muse.activity_state.callosum_send") as mock_send: + with patch("talent.activity_state.callosum_send") as mock_send: mock_send.side_effect = OSError("socket error") result = post_process(llm_output, context) # Should still return valid resolved output @@ -1401,7 +1401,7 @@ class TestActivityIdValidation: """Post-hook drops LLM output entries with activity IDs not in config.""" from unittest.mock import patch - from muse.activity_state import post_process + from talent.activity_state import post_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -1437,7 +1437,7 @@ class TestActivityIdValidation: "output_path": f"{tmpdir}/20260130/143000_300/agents/work/activity_state.json", } - with patch("muse.activity_state.callosum_send"): + with patch("talent.activity_state.callosum_send"): result = post_process(llm_output, context) items = json.loads(result) @@ -1453,7 +1453,7 @@ class TestActivityIdValidation: import logging from unittest.mock import patch - from muse.activity_state import post_process + from talent.activity_state import post_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -1480,8 +1480,8 @@ class TestActivityIdValidation: "output_path": f"{tmpdir}/20260130/143000_300/agents/work/activity_state.json", } - with caplog.at_level(logging.WARNING, logger="muse.activity_state"): - with patch("muse.activity_state.callosum_send"): + with caplog.at_level(logging.WARNING, logger="talent.activity_state"): + with patch("talent.activity_state.callosum_send"): post_process(llm_output, context) assert "Dropped 1 activity entries" in caplog.text @@ -1495,7 +1495,7 @@ class TestActivityIdValidation: """Post-hook preserves entries with valid activity IDs.""" from unittest.mock import patch - from muse.activity_state import post_process + from talent.activity_state import post_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -1536,7 +1536,7 @@ class TestActivityIdValidation: "output_path": f"{tmpdir}/20260130/143000_300/agents/work/activity_state.json", } - with patch("muse.activity_state.callosum_send"): + with patch("talent.activity_state.callosum_send"): result = post_process(llm_output, context) items = json.loads(result) @@ -1553,7 +1553,7 @@ class TestActivityIdValidation: """Post-hook allows all default activity IDs for unconfigured facets.""" from unittest.mock import patch - from muse.activity_state import post_process + from talent.activity_state import post_process with tempfile.TemporaryDirectory() as tmpdir: original_path = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") @@ -1586,7 +1586,7 @@ class TestActivityIdValidation: "output_path": f"{tmpdir}/20260130/143000_300/agents/new_facet/activity_state.json", } - with patch("muse.activity_state.callosum_send"): + with patch("talent.activity_state.callosum_send"): result = post_process(llm_output, context) items = json.loads(result) diff --git a/tests/test_agent_fallback.py b/tests/test_agent_fallback.py index ee75803f1..a63a779dc 100644 --- a/tests/test_agent_fallback.py +++ b/tests/test_agent_fallback.py @@ -100,10 +100,10 @@ def _mock_base_agent_config() -> dict: def _patch_prepare_config_dependencies(monkeypatch): monkeypatch.setattr( - "think.muse.get_agent", lambda *args, **kwargs: _mock_base_agent_config() + "think.talent.get_agent", lambda *args, **kwargs: _mock_base_agent_config() ) monkeypatch.setattr( - "think.muse.key_to_context", lambda _name: "muse.system.default" + "think.talent.key_to_context", lambda _name: "talent.system.default" ) monkeypatch.setattr( "think.models.resolve_provider", @@ -205,7 +205,7 @@ def test_on_failure_retry_cogitate(monkeypatch): "provider": "google", "model": "gemini-3-flash-preview", "health_stale": False, - "context": "muse.system.default", + "context": "talent.system.default", } asyncio.run(_execute_with_tools(config, events.append)) @@ -247,8 +247,8 @@ def test_on_failure_retry_cogitate_uses_context_from_name(monkeypatch): ), ) monkeypatch.setattr( - "think.muse.key_to_context", - lambda _name: "muse.system.default", + "think.talent.key_to_context", + lambda _name: "talent.system.default", ) monkeypatch.setattr("think.models.get_backup_provider", lambda _type: "anthropic") monkeypatch.setattr("think.models.resolve_model_for_provider", resolve_model) @@ -263,7 +263,7 @@ def test_on_failure_retry_cogitate_uses_context_from_name(monkeypatch): asyncio.run(_execute_with_tools(config, events.append)) - assert seen["context"] == "muse.system.default" + assert seen["context"] == "talent.system.default" def test_on_failure_retry_generate(monkeypatch): @@ -281,7 +281,7 @@ def test_on_failure_retry_generate(monkeypatch): return {"text": "backup text", "usage": {"input_tokens": 1, "output_tokens": 1}} monkeypatch.setattr( - "think.muse.key_to_context", lambda _name: "muse.system.default" + "think.talent.key_to_context", lambda _name: "talent.system.default" ) monkeypatch.setattr("think.models.generate_with_result", mock_generate_with_result) monkeypatch.setattr("think.models.get_backup_provider", lambda _type: "anthropic") @@ -319,7 +319,7 @@ def test_on_failure_no_retry_value_error(monkeypatch): raise ValueError("bad input") monkeypatch.setattr( - "think.muse.key_to_context", lambda _name: "muse.system.default" + "think.talent.key_to_context", lambda _name: "talent.system.default" ) monkeypatch.setattr("think.models.generate_with_result", bad_generate) @@ -350,7 +350,7 @@ def test_on_failure_both_fail_raises_original(monkeypatch): raise RuntimeError("primary failed") monkeypatch.setattr( - "think.muse.key_to_context", lambda _name: "muse.system.default" + "think.talent.key_to_context", lambda _name: "talent.system.default" ) monkeypatch.setattr("think.models.generate_with_result", always_fail) monkeypatch.setattr("think.models.get_backup_provider", lambda _type: "anthropic") diff --git a/tests/test_app_agents.py b/tests/test_app_agents.py index c8bda1beb..36bed5346 100644 --- a/tests/test_app_agents.py +++ b/tests/test_app_agents.py @@ -10,7 +10,7 @@ from pathlib import Path import pytest from apps.agents.routes import _resolve_output_path -from think.muse import _resolve_agent_path, get_agent, get_muse_configs +from think.talent import _resolve_agent_path, get_agent, get_talent_configs @pytest.fixture @@ -24,13 +24,13 @@ def fixture_journal(): def app_with_agent(tmp_path, monkeypatch): """Create a temporary app with an agent for testing. - Creates apps/testapp/muse/myhelper.md with frontmatter in a temp directory, + Creates apps/testapp/talent/myhelper.md with frontmatter in a temp directory, then monkeypatches the apps directory path. """ # Create app structure app_dir = tmp_path / "apps" / "testapp" - muse_dir = app_dir / "muse" - muse_dir.mkdir(parents=True) + talent_dir = app_dir / "talent" + talent_dir.mkdir(parents=True) # Create workspace.html (required for app discovery, though not used here) (app_dir / "workspace.html").write_text("

Test App

") @@ -45,12 +45,12 @@ def app_with_agent(tmp_path, monkeypatch): "priority": 42, } json_str = json.dumps(metadata, indent=2) - (muse_dir / "myhelper.md").write_text( + (talent_dir / "myhelper.md").write_text( f"{{\n{json_str[1:-1]}\n}}\n\nYou are a test helper agent.\n\n## Purpose\nHelp with testing." ) # Create another agent without metadata (defaults only) - (muse_dir / "simple.md").write_text("A simple test agent with no metadata.") + (talent_dir / "simple.md").write_text("A simple test agent with no metadata.") # Monkeypatch the parent directory so apps discovery finds our temp apps monkeypatch.setattr( @@ -64,7 +64,7 @@ def app_with_agent(tmp_path, monkeypatch): yield { "tmp_path": tmp_path, "app_dir": app_dir, - "muse_dir": muse_dir, + "talent_dir": talent_dir, } @@ -73,7 +73,7 @@ def test_resolve_agent_path_system_agent(): agent_dir, agent_name = _resolve_agent_path("unified") assert agent_name == "chat" - assert agent_dir.name == "muse" + assert agent_dir.name == "talent" def test_resolve_agent_path_app_agent(): @@ -81,7 +81,7 @@ def test_resolve_agent_path_app_agent(): agent_dir, agent_name = _resolve_agent_path("support:support") assert agent_name == "support" - assert agent_dir.name == "muse" + assert agent_dir.name == "talent" assert agent_dir.parent.name == "support" assert "apps" in str(agent_dir) @@ -119,9 +119,9 @@ def test_get_agent_nonexistent_app_agent_raises(): assert "fakeapp:fakeagent" in str(exc_info.value) -def test_get_muse_configs_includes_system_agents(fixture_journal): - """Test get_muse_configs returns system agents with metadata.""" - agents = get_muse_configs(type="cogitate") +def test_get_talent_configs_includes_system_agents(fixture_journal): + """Test get_talent_configs returns system agents with metadata.""" + agents = get_talent_configs(type="cogitate") # Should include known system agents with frontmatter metadata assert "chat" in agents @@ -130,9 +130,9 @@ def test_get_muse_configs_includes_system_agents(fixture_journal): assert "path" in agents["chat"] -def test_get_muse_configs_system_agents_have_metadata(fixture_journal): +def test_get_talent_configs_system_agents_have_metadata(fixture_journal): """Test system agents have proper metadata fields.""" - agents = get_muse_configs(type="cogitate") + agents = get_talent_configs(type="cogitate") # Check a known system agent chat = agents.get("chat") @@ -142,8 +142,8 @@ def test_get_muse_configs_system_agents_have_metadata(fixture_journal): assert "color" in chat -def test_get_muse_configs_excludes_private_apps(fixture_journal, tmp_path, monkeypatch): - """Test get_muse_configs skips apps starting with underscore.""" +def test_get_talent_configs_excludes_private_apps(fixture_journal, tmp_path, monkeypatch): + """Test get_talent_configs skips apps starting with underscore.""" # Create a private app with an agent private_app = tmp_path / "_private_app" / "agents" private_app.mkdir(parents=True) @@ -151,9 +151,9 @@ def test_get_muse_configs_excludes_private_apps(fixture_journal, tmp_path, monke # This is tricky to test without modifying the actual apps directory # The current implementation filters by app_path.name.startswith("_") - # We verify this by checking the code behavior with get_muse_configs() + # We verify this by checking the code behavior with get_talent_configs() - agents = get_muse_configs(type="cogitate") + agents = get_talent_configs(type="cogitate") # No agents should have keys starting with "_" for key in agents: @@ -162,7 +162,7 @@ def test_get_muse_configs_excludes_private_apps(fixture_journal, tmp_path, monke def test_app_agent_namespace_format(fixture_journal): """Test app agent keys follow {app}:{agent} format.""" - agents = get_muse_configs(type="cogitate") + agents = get_talent_configs(type="cogitate") for key, config in agents.items(): if config.get("source") == "app": diff --git a/tests/test_awareness.py b/tests/test_awareness.py index 1f45aaefd..1919c38e0 100644 --- a/tests/test_awareness.py +++ b/tests/test_awareness.py @@ -367,7 +367,7 @@ class TestComputeThickness: ] exchanges = [ { - "muse": "triage", + "talent": "triage", "agent_response": f"talked about entity_{i}", "user_message": "hi", } @@ -402,7 +402,7 @@ class TestComputeThickness: ] exchanges = [ { - "muse": "triage", + "talent": "triage", "agent_response": f"entity_{i} is great", "user_message": "yo", } @@ -443,7 +443,7 @@ class TestComputeThickness: {"entity_name": f"entity_{i}", "observation_depth": 3} for i in range(15) ] exchanges = [ - {"muse": "triage", "agent_response": "hello there", "user_message": "hi"} + {"talent": "triage", "agent_response": "hello there", "user_message": "hi"} for _ in range(10) ] facets = {"work": {}, "personal": {}, "hobby": {}} @@ -466,18 +466,18 @@ class TestComputeThickness: assert result["ready"] is False def test_onboarding_exchanges_excluded(self): - """Exchanges with muse='onboarding' are excluded from conversation_count.""" + """Exchanges with talent='onboarding' are excluded from conversation_count.""" from think.awareness import compute_thickness entities = [{"entity_name": "foo", "observation_depth": 3}] * 10 exchanges = [ - {"muse": "onboarding", "agent_response": "foo stuff", "user_message": "hi"}, + {"talent": "onboarding", "agent_response": "foo stuff", "user_message": "hi"}, { - "muse": "onboarding", + "talent": "onboarding", "agent_response": "foo bar", "user_message": "hello", }, - {"muse": "triage", "agent_response": "foo is great", "user_message": "hey"}, + {"talent": "triage", "agent_response": "foo is great", "user_message": "hey"}, ] with unittest.mock.patch( diff --git a/tests/test_chat_context.py b/tests/test_chat_context.py index 33b9e1d65..5da0b74d5 100644 --- a/tests/test_chat_context.py +++ b/tests/test_chat_context.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc -from muse.chat_context import pre_process +from talent.chat_context import pre_process def test_chat_context_appends_conversation_memory(monkeypatch, tmp_path): @@ -16,7 +16,7 @@ def test_chat_context_appends_conversation_memory(monkeypatch, tmp_path): facet="work", user_message="hello", agent_response="hi there!", - muse="unified", + talent="unified", ) result = pre_process({"user_instruction": "Base instruction.", "facet": "work"}) diff --git a/tests/test_cogitate_coder.py b/tests/test_cogitate_coder.py index 06f0e50a2..906247cbd 100644 --- a/tests/test_cogitate_coder.py +++ b/tests/test_cogitate_coder.py @@ -179,19 +179,19 @@ class TestGoogleWriteFlag: # --------------------------------------------------------------------------- -# muse/coder.md existence and frontmatter +# talent/coder.md existence and frontmatter # --------------------------------------------------------------------------- class TestCoderAgent: - """Verify muse/coder.md exists with correct frontmatter.""" + """Verify talent/coder.md exists with correct frontmatter.""" def test_coder_md_exists(self): - """muse/coder.md must exist in the repo.""" + """talent/coder.md must exist in the repo.""" from pathlib import Path - coder_path = Path(__file__).parent.parent / "muse" / "coder.md" - assert coder_path.exists(), "muse/coder.md not found" + coder_path = Path(__file__).parent.parent / "talent" / "coder.md" + assert coder_path.exists(), "talent/coder.md not found" def test_coder_frontmatter(self): """coder.md must have write: true and type: cogitate.""" @@ -199,7 +199,7 @@ class TestCoderAgent: import frontmatter - coder_path = Path(__file__).parent.parent / "muse" / "coder.md" + coder_path = Path(__file__).parent.parent / "talent" / "coder.md" post = frontmatter.load(coder_path) assert post.metadata.get("type") == "cogitate" @@ -211,7 +211,7 @@ class TestCoderAgent: """coder.md must reference the coding skill instead of inlining guidelines.""" from pathlib import Path - coder_path = Path(__file__).parent.parent / "muse" / "coder.md" + coder_path = Path(__file__).parent.parent / "talent" / "coder.md" content = coder_path.read_text(encoding="utf-8") # Should reference the coding skill, not inline dev guidelines @@ -219,10 +219,10 @@ class TestCoderAgent: assert "single source of truth" in content # The coding skill must exist with reference files - coding_skill = Path(__file__).parent.parent / "muse" / "coding" / "SKILL.md" - assert coding_skill.exists(), "muse/coding/SKILL.md not found" + coding_skill = Path(__file__).parent.parent / "talent" / "coding" / "SKILL.md" + assert coding_skill.exists(), "talent/coding/SKILL.md not found" - coding_refs = Path(__file__).parent.parent / "muse" / "coding" / "reference" + coding_refs = Path(__file__).parent.parent / "talent" / "coding" / "reference" assert (coding_refs / "coding-standards.md").exists() assert (coding_refs / "project-structure.md").exists() assert (coding_refs / "testing.md").exists() diff --git a/tests/test_conversation.py b/tests/test_conversation.py index e5afbd3c2..3195f5ed1 100644 --- a/tests/test_conversation.py +++ b/tests/test_conversation.py @@ -35,7 +35,7 @@ def test_record_exchange_writes_jsonl(journal_dir): path="/app/entities/adrian", user_message="what's our history with adrian?", agent_response="You met Adrian at betaworks.", - muse="unified", + talent="unified", agent_id="12345", ) @@ -52,7 +52,7 @@ def test_record_exchange_writes_jsonl(journal_dir): assert ex["app"] == "entities" assert ex["user_message"] == "what's our history with adrian?" assert ex["agent_response"] == "You met Adrian at betaworks." - assert ex["muse"] == "unified" + assert ex["talent"] == "unified" assert ex["agent_id"] == "12345" @@ -70,7 +70,7 @@ def test_record_exchange_writes_journal_segment(journal_dir): path="/app/calendar", user_message="move my 3pm to 4pm", agent_response="Done — moved 'DVD sync' to 4pm.", - muse="unified", + talent="unified", agent_id="67890", ) @@ -102,13 +102,13 @@ def test_record_exchange_appends_multiple(journal_dir): ts=1710000001000, user_message="hello", agent_response="hi there", - muse="triage", + talent="triage", ) record_exchange( ts=1710000002000, user_message="what time is it?", agent_response="It's 2pm.", - muse="triage", + talent="triage", ) jsonl_path = journal_dir / "conversation" / "exchanges.jsonl" @@ -124,8 +124,8 @@ def test_record_exchange_skips_empty(journal_dir): """Empty user_message or agent_response is silently skipped.""" from think.conversation import record_exchange - record_exchange(user_message="", agent_response="response", muse="triage") - record_exchange(user_message="hello", agent_response="", muse="triage") + record_exchange(user_message="", agent_response="response", talent="triage") + record_exchange(user_message="hello", agent_response="", talent="triage") jsonl_path = journal_dir / "conversation" / "exchanges.jsonl" assert not jsonl_path.exists() @@ -152,7 +152,7 @@ def test_get_recent_exchanges_returns_last_n(journal_dir): ts=1710000000000 + i * 1000, user_message=f"msg {i}", agent_response=f"resp {i}", - muse="triage", + talent="triage", ) recent = get_recent_exchanges(limit=5) @@ -170,14 +170,14 @@ def test_get_recent_exchanges_filters_by_facet(journal_dir): facet="work", user_message="work question", agent_response="work answer", - muse="triage", + talent="triage", ) record_exchange( ts=1710000002000, facet="personal", user_message="personal question", agent_response="personal answer", - muse="triage", + talent="triage", ) work = get_recent_exchanges(facet="work") @@ -204,7 +204,7 @@ def test_get_today_exchanges_filters_by_day(journal_dir): ts=now_ms(), user_message="today question", agent_response="today answer", - muse="triage", + talent="triage", ) # Record an exchange with old timestamp (not today) @@ -212,7 +212,7 @@ def test_get_today_exchanges_filters_by_day(journal_dir): ts=1000000000000, # 2001-09-08 user_message="old question", agent_response="old answer", - muse="triage", + talent="triage", ) today = get_today_exchanges() @@ -244,7 +244,7 @@ def test_build_memory_context_includes_recent(journal_dir): app="entities", user_message="who is adrian?", agent_response="Adrian is the CTO of Own Company.", - muse="unified", + talent="unified", ) context = build_memory_context() @@ -266,7 +266,7 @@ def test_build_memory_context_truncates_long_responses(journal_dir): ts=now_ms(), user_message="tell me a story", agent_response=long_response, - muse="unified", + talent="unified", ) context = build_memory_context() @@ -287,7 +287,7 @@ def test_build_memory_context_earlier_today(journal_dir): ts=ts + i * 1000, user_message=f"question {i}", agent_response=f"answer {i}", - muse="unified", + talent="unified", ) context = build_memory_context(recent_limit=10) @@ -389,7 +389,7 @@ def test_format_exchange_compact(): def test_conversation_memory_pre_hook(journal_dir): """Pre-hook injects memory into user instruction.""" - from muse.conversation_memory import pre_process + from talent.conversation_memory import pre_process from think.conversation import record_exchange from think.utils import now_ms @@ -399,7 +399,7 @@ def test_conversation_memory_pre_hook(journal_dir): facet="work", user_message="hello", agent_response="hi there!", - muse="unified", + talent="unified", ) context = { @@ -425,7 +425,7 @@ Populated by conversation memory service. def test_conversation_memory_pre_hook_no_marker(): """Pre-hook returns None when no injection marker present.""" - from muse.conversation_memory import pre_process + from talent.conversation_memory import pre_process context = {"user_instruction": "No marker here."} result = pre_process(context) diff --git a/tests/test_dream_activity.py b/tests/test_dream_activity.py index f8632d20f..f8f24db62 100644 --- a/tests/test_dream_activity.py +++ b/tests/test_dream_activity.py @@ -58,7 +58,7 @@ class TestRunActivityPrompts: ) # No activity-scheduled agents - monkeypatch.setattr("think.dream.get_muse_configs", lambda schedule: {}) + monkeypatch.setattr("think.dream.get_talent_configs", lambda schedule: {}) result = run_activity_prompts( day="20260209", @@ -103,7 +103,7 @@ class TestRunActivityPrompts: } monkeypatch.setattr( - "think.dream.get_muse_configs", lambda schedule: configs + "think.dream.get_talent_configs", lambda schedule: configs ) spawned_requests = [] @@ -159,7 +159,7 @@ class TestRunActivityPrompts: } monkeypatch.setattr( - "think.dream.get_muse_configs", lambda schedule: configs + "think.dream.get_talent_configs", lambda schedule: configs ) spawned = [] @@ -209,7 +209,7 @@ class TestRunActivityPrompts: } monkeypatch.setattr( - "think.dream.get_muse_configs", lambda schedule: configs + "think.dream.get_talent_configs", lambda schedule: configs ) captured_config = {} @@ -275,7 +275,7 @@ class TestRunActivityPrompts: } monkeypatch.setattr( - "think.dream.get_muse_configs", lambda schedule: configs + "think.dream.get_talent_configs", lambda schedule: configs ) monkeypatch.setattr( "think.dream.cortex_request", @@ -351,7 +351,7 @@ class TestRunActivityPrompts: } monkeypatch.setattr( - "think.dream.get_muse_configs", lambda schedule: configs + "think.dream.get_talent_configs", lambda schedule: configs ) monkeypatch.setattr( "think.dream.cortex_request", @@ -466,27 +466,27 @@ class TestActivityTemplateVars: # --------------------------------------------------------------------------- -# Muse config validation for activity schedule +# Talent config validation for activity schedule # --------------------------------------------------------------------------- -class TestMuseActivityValidation: - """Tests for activity schedule validation in get_muse_configs.""" +class TestTalentActivityValidation: + """Tests for activity schedule validation in get_talent_configs.""" - def _isolate_muse(self, monkeypatch, tmp_path): - """Point muse discovery at tmp_path only (no real muse/ or apps/).""" - muse_dir = tmp_path / "muse" - muse_dir.mkdir(exist_ok=True) - monkeypatch.setattr("think.muse.MUSE_DIR", muse_dir) - monkeypatch.setattr("think.muse.APPS_DIR", tmp_path / "no_apps") - return muse_dir + def _isolate_talent(self, monkeypatch, tmp_path): + """Point talent discovery at tmp_path only (no real talent/ or apps/).""" + talent_dir = tmp_path / "talent" + talent_dir.mkdir(exist_ok=True) + monkeypatch.setattr("think.talent.TALENT_DIR", talent_dir) + monkeypatch.setattr("think.talent.APPS_DIR", tmp_path / "no_apps") + return talent_dir def test_missing_activities_field_raises(self, monkeypatch, tmp_path): import frontmatter - from think.muse import get_muse_configs + from think.talent import get_talent_configs - muse_dir = self._isolate_muse(monkeypatch, tmp_path) + talent_dir = self._isolate_talent(monkeypatch, tmp_path) post = frontmatter.Post( "Test prompt", @@ -496,17 +496,17 @@ class TestMuseActivityValidation: output="md", # Missing 'activities' field ) - (muse_dir / "test_agent.md").write_text(frontmatter.dumps(post)) + (talent_dir / "test_agent.md").write_text(frontmatter.dumps(post)) with pytest.raises(ValueError, match="non-empty 'activities' list"): - get_muse_configs(schedule="activity") + get_talent_configs(schedule="activity") def test_valid_activities_field_passes(self, monkeypatch, tmp_path): import frontmatter - from think.muse import get_muse_configs + from think.talent import get_talent_configs - muse_dir = self._isolate_muse(monkeypatch, tmp_path) + talent_dir = self._isolate_talent(monkeypatch, tmp_path) post = frontmatter.Post( "Test prompt", @@ -516,18 +516,18 @@ class TestMuseActivityValidation: output="md", activities=["coding", "meeting"], ) - (muse_dir / "test_agent.md").write_text(frontmatter.dumps(post)) + (talent_dir / "test_agent.md").write_text(frontmatter.dumps(post)) - configs = get_muse_configs(schedule="activity") + configs = get_talent_configs(schedule="activity") assert "test_agent" in configs assert configs["test_agent"]["activities"] == ["coding", "meeting"] def test_wildcard_activities_passes(self, monkeypatch, tmp_path): import frontmatter - from think.muse import get_muse_configs + from think.talent import get_talent_configs - muse_dir = self._isolate_muse(monkeypatch, tmp_path) + talent_dir = self._isolate_talent(monkeypatch, tmp_path) post = frontmatter.Post( "Test prompt", @@ -537,9 +537,9 @@ class TestMuseActivityValidation: output="md", activities=["*"], ) - (muse_dir / "test_agent.md").write_text(frontmatter.dumps(post)) + (talent_dir / "test_agent.md").write_text(frontmatter.dumps(post)) - configs = get_muse_configs(schedule="activity") + configs = get_talent_configs(schedule="activity") assert "test_agent" in configs assert configs["test_agent"]["activities"] == ["*"] diff --git a/tests/test_dream_full.py b/tests/test_dream_full.py index 6bf24cc23..f1273eae3 100644 --- a/tests/test_dream_full.py +++ b/tests/test_dream_full.py @@ -101,11 +101,11 @@ def test_segment_mode_skips_pre_post_phases(tmp_path, monkeypatch): def test_priority_validation_required(tmp_path, monkeypatch): - """Test that get_muse_configs raises error for scheduled prompts without priority.""" - from think.muse import get_muse_configs + """Test that get_talent_configs raises error for scheduled prompts without priority.""" + from think.talent import get_talent_configs # This test verifies the validation exists - actual validation tested in test_utils.py # Here we just confirm all existing scheduled prompts have priority - configs = get_muse_configs(schedule="daily") + configs = get_talent_configs(schedule="daily") for name, config in configs.items(): assert "priority" in config, f"Scheduled prompt '{name}' missing priority" diff --git a/tests/test_dream_segment.py b/tests/test_dream_segment.py index fbe98443b..a035f915a 100644 --- a/tests/test_dream_segment.py +++ b/tests/test_dream_segment.py @@ -143,7 +143,7 @@ class TestRunSegmentSense: monkeypatch.setattr( dream, - "get_muse_configs", + "get_talent_configs", lambda schedule=None, **kwargs: _segment_configs("sense", "entities"), ) monkeypatch.setattr( @@ -189,7 +189,7 @@ class TestRunSegmentSense: monkeypatch.setattr( dream, - "get_muse_configs", + "get_talent_configs", lambda schedule=None, **kwargs: _segment_configs( "sense", "entities", "screen" ), @@ -239,7 +239,7 @@ class TestRunSegmentSense: monkeypatch.setattr( dream, - "get_muse_configs", + "get_talent_configs", lambda schedule=None, **kwargs: _segment_configs( "sense", "entities", "screen" ), @@ -297,7 +297,7 @@ class TestRunSegmentSense: monkeypatch.setattr( dream, - "get_muse_configs", + "get_talent_configs", lambda schedule=None, **kwargs: _segment_configs( "sense", "entities", @@ -337,7 +337,7 @@ class TestRunSegmentSense: monkeypatch.setattr( dream, - "get_muse_configs", + "get_talent_configs", lambda schedule=None, **kwargs: _segment_configs("sense", "entities"), ) monkeypatch.setattr( @@ -376,7 +376,7 @@ class TestRunSegmentSense: monkeypatch.setattr( dream, - "get_muse_configs", + "get_talent_configs", lambda schedule=None, **kwargs: _segment_configs( "sense", "entities", "screen" ), @@ -415,7 +415,7 @@ class TestRunSegmentSense: monkeypatch.setattr( dream, - "get_muse_configs", + "get_talent_configs", lambda schedule=None, **kwargs: _segment_configs( "sense", "entities", "pulse" ), @@ -453,7 +453,7 @@ class TestRunSegmentSense: monkeypatch.setattr( dream, - "get_muse_configs", + "get_talent_configs", lambda schedule=None, **kwargs: _segment_configs("sense", "entities"), ) monkeypatch.setattr( @@ -499,7 +499,7 @@ class TestRunSegmentSense: monkeypatch.setattr( dream, - "get_muse_configs", + "get_talent_configs", lambda schedule=None, **kwargs: _segment_configs("sense", "entities"), ) monkeypatch.setattr( @@ -560,7 +560,7 @@ class TestRunSegmentSense: monkeypatch.setattr( dream, - "get_muse_configs", + "get_talent_configs", lambda schedule=None, **kwargs: { **_segment_configs("sense"), "entities": { @@ -617,7 +617,7 @@ class TestRunSegmentSense: monkeypatch.setattr( dream, - "get_muse_configs", + "get_talent_configs", lambda schedule=None, **kwargs: _segment_configs("sense", "entities"), ) monkeypatch.setattr(dream, "cortex_request", mock_cortex_request) diff --git a/tests/test_entities_hook.py b/tests/test_entities_hook.py index 8cfd03f16..0ea972b0c 100644 --- a/tests/test_entities_hook.py +++ b/tests/test_entities_hook.py @@ -9,7 +9,7 @@ import json def test_entities_post_process_writes_without_segment(tmp_path): - from muse.entities import post_process + from talent.entities import post_process output_path = ( tmp_path @@ -40,16 +40,16 @@ def test_entities_post_process_writes_without_segment(tmp_path): def test_entities_post_process_requires_output_path(caplog): - from muse.entities import post_process + from talent.entities import post_process post_process("* Person: Alice Smith - Mentioned in the meeting\n", {}) assert "missing output_path" in caplog.text -def test_entities_muse_is_segment_scheduled(): - from think.muse import get_muse_configs +def test_entities_talent_is_segment_scheduled(): + from think.talent import get_talent_configs - segment_prompts = get_muse_configs(schedule="segment") + segment_prompts = get_talent_configs(schedule="segment") assert "entities" in segment_prompts diff --git a/tests/test_entity_agents.py b/tests/test_entity_agents.py index fc9b219d6..dc6191ce1 100644 --- a/tests/test_entity_agents.py +++ b/tests/test_entity_agents.py @@ -7,7 +7,7 @@ import os import pytest -from think.muse import get_agent +from think.talent import get_agent @pytest.fixture @@ -20,7 +20,7 @@ def fixture_journal(): def test_entities_agent_config(fixture_journal): """Test detection agent configuration loads correctly.""" - # Entity agents are in apps/entities/muse/ so use app-qualified name + # Entity agents are in apps/entities/talent/ so use app-qualified name config = get_agent("entities:entities") # Verify required fields @@ -37,7 +37,7 @@ def test_entities_agent_config(fixture_journal): def test_entities_review_agent_config(fixture_journal): """Test review agent configuration loads correctly.""" - # Entity agents are in apps/entities/muse/ so use app-qualified name + # Entity agents are in apps/entities/talent/ so use app-qualified name config = get_agent("entities:entities_review") # Verify required fields diff --git a/tests/test_generate_full.py b/tests/test_generate_full.py index 944d2f0f6..6079ad709 100644 --- a/tests/test_generate_full.py +++ b/tests/test_generate_full.py @@ -73,9 +73,9 @@ def test_generate_output_ndjson(tmp_path, monkeypatch): mod = importlib.import_module("think.agents") copy_day(tmp_path) - import think.muse + import think.talent - monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) + monkeypatch.setattr(think.talent, "TALENT_DIR", tmp_path) test_generator = tmp_path / "test_gen.md" test_generator.write_text( @@ -119,9 +119,9 @@ def test_generate_hook_invoked_with_context(tmp_path, monkeypatch): mod = importlib.import_module("think.agents") copy_day(tmp_path) - import think.muse + import think.talent - monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) + monkeypatch.setattr(think.talent, "TALENT_DIR", tmp_path) hook_file = tmp_path / "test_hook.py" hook_file.write_text(""" @@ -192,9 +192,9 @@ def test_generate_without_hook_succeeds(tmp_path, monkeypatch): mod = importlib.import_module("think.agents") copy_day(tmp_path) - import think.muse + import think.talent - monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) + monkeypatch.setattr(think.talent, "TALENT_DIR", tmp_path) test_generator = tmp_path / "nohook_gen.md" test_generator.write_text( @@ -259,9 +259,9 @@ def test_generate_skipped_on_no_input(tmp_path, monkeypatch): day_dir = day_path("20240101") day_dir.mkdir(parents=True, exist_ok=True) - import think.muse + import think.talent - monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) + monkeypatch.setattr(think.talent, "TALENT_DIR", tmp_path) test_generator = tmp_path / "empty_gen.md" test_generator.write_text( @@ -296,9 +296,9 @@ def test_cogitate_not_skipped_without_sources(tmp_path, monkeypatch): day_dir = day_path("20240101") day_dir.mkdir(parents=True, exist_ok=True) - import think.muse + import think.talent - monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) + monkeypatch.setattr(think.talent, "TALENT_DIR", tmp_path) test_agent = tmp_path / "test_cogitate.md" test_agent.write_text( @@ -320,11 +320,11 @@ def test_cogitate_not_skipped_without_sources(tmp_path, monkeypatch): def test_named_hook_resolution(tmp_path, monkeypatch): """Test that named hooks are resolved via load_post_hook.""" - from think.muse import load_post_hook + from think.talent import load_post_hook # Config with named hook (new format) config = {"hook": {"post": "occurrence"}} hook_fn = load_post_hook(config) - # Should resolve to muse/occurrence.py and be callable + # Should resolve to talent/occurrence.py and be callable assert callable(hook_fn) diff --git a/tests/test_generators.py b/tests/test_generators.py index d929a27d9..c09b59266 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -9,10 +9,10 @@ from pathlib import Path import pytest -def test_get_muse_configs_generators(): +def test_get_talent_configs_generators(): """Test that system generators are discovered with source field.""" - muse = importlib.import_module("think.muse") - generators = muse.get_muse_configs(type="generate") + talent = importlib.import_module("think.talent") + generators = talent.get_talent_configs(type="generate") assert "flow" in generators info = generators["flow"] assert os.path.basename(info["path"]) == "flow.md" @@ -26,23 +26,23 @@ def test_get_muse_configs_generators(): def test_get_output_name(): """Test generator key to filename conversion.""" - muse = importlib.import_module("think.muse") + talent = importlib.import_module("think.talent") # System generators: key unchanged - assert muse.get_output_name("activity") == "activity" - assert muse.get_output_name("flow") == "flow" + assert talent.get_output_name("activity") == "activity" + assert talent.get_output_name("flow") == "flow" # App generators: _app_name format - assert muse.get_output_name("chat:sentiment") == "_chat_sentiment" - assert muse.get_output_name("my_app:weekly_summary") == "_my_app_weekly_summary" + assert talent.get_output_name("chat:sentiment") == "_chat_sentiment" + assert talent.get_output_name("my_app:weekly_summary") == "_my_app_weekly_summary" -def test_get_muse_configs_app_discovery(tmp_path, monkeypatch): - """Test that app generators are discovered from apps/*/muse/.""" - muse = importlib.import_module("think.muse") +def test_get_talent_configs_app_discovery(tmp_path, monkeypatch): + """Test that app generators are discovered from apps/*/talent/.""" + talent = importlib.import_module("think.talent") # Create a fake app with a generator - app_dir = tmp_path / "apps" / "test_app" / "muse" + app_dir = tmp_path / "apps" / "test_app" / "talent" app_dir.mkdir(parents=True) # Create generator files with frontmatter @@ -54,24 +54,24 @@ def test_get_muse_configs_app_discovery(tmp_path, monkeypatch): (tmp_path / "apps" / "test_app" / "workspace.html").write_text("

Test

") # For now, just verify system generators have correct source - generators = muse.get_muse_configs(type="generate") + generators = talent.get_talent_configs(type="generate") for key, info in generators.items(): if ":" not in key: assert info.get("source") == "system", f"{key} should have source=system" -def test_get_muse_configs_by_schedule(): +def test_get_talent_configs_by_schedule(): """Test filtering generators by schedule.""" - muse = importlib.import_module("think.muse") + talent = importlib.import_module("think.talent") # Get daily generators - daily = muse.get_muse_configs(type="generate", schedule="daily") + daily = talent.get_talent_configs(type="generate", schedule="daily") assert len(daily) > 0 for key, meta in daily.items(): assert meta.get("schedule") == "daily", f"{key} should have schedule=daily" # Get segment generators - segment = muse.get_muse_configs(type="generate", schedule="segment") + segment = talent.get_talent_configs(type="generate", schedule="segment") assert len(segment) > 0 for key, meta in segment.items(): assert meta.get("schedule") == "segment", f"{key} should have schedule=segment" @@ -82,19 +82,19 @@ def test_get_muse_configs_by_schedule(): ) # Unknown schedule returns empty dict - assert muse.get_muse_configs(type="generate", schedule="hourly") == {} - assert muse.get_muse_configs(type="generate", schedule="") == {} + assert talent.get_talent_configs(type="generate", schedule="hourly") == {} + assert talent.get_talent_configs(type="generate", schedule="") == {} -def test_get_muse_configs_include_disabled(monkeypatch): +def test_get_talent_configs_include_disabled(monkeypatch): """Test include_disabled parameter.""" - muse = importlib.import_module("think.muse") + talent = importlib.import_module("think.talent") # Get generators without disabled (default) - without_disabled = muse.get_muse_configs(type="generate", schedule="daily") + without_disabled = talent.get_talent_configs(type="generate", schedule="daily") # Get generators with disabled included - with_disabled = muse.get_muse_configs( + with_disabled = talent.get_talent_configs( type="generate", schedule="daily", include_disabled=True ) @@ -109,9 +109,9 @@ def test_scheduled_generators_have_valid_schedule(): ('segment', 'daily', or 'activity'). Some generators (like importer) have output but no schedule - they're used for ad-hoc processing, not scheduled runs. """ - muse = importlib.import_module("think.muse") + talent = importlib.import_module("think.talent") - generators = muse.get_muse_configs(type="generate") + generators = talent.get_talent_configs(type="generate") valid_schedules = ("segment", "daily", "activity", "weekly") for key, meta in generators.items(): @@ -124,9 +124,9 @@ def test_scheduled_generators_have_valid_schedule(): def test_sense_in_segment_schedule(): """Test that sense generator exists in segment schedule at priority 5.""" - muse = importlib.import_module("think.muse") + talent = importlib.import_module("think.talent") - generators = muse.get_muse_configs(type="generate", schedule="segment") + generators = talent.get_talent_configs(type="generate", schedule="segment") assert "sense" in generators sense = generators["sense"] @@ -138,19 +138,19 @@ def test_sense_in_segment_schedule(): assert sources.get("percepts") is True, "sense should include percepts" -def _write_temp_muse_prompt(stem: str, frontmatter: str) -> Path: - muse_dir = Path(__file__).resolve().parent.parent / "muse" - prompt_path = muse_dir / f"{stem}.md" +def _write_temp_talent_prompt(stem: str, frontmatter: str) -> Path: + talent_dir = Path(__file__).resolve().parent.parent / "talent" + prompt_path = talent_dir / f"{stem}.md" prompt_path.write_text( f"{frontmatter}\n\nTemporary test prompt\n", encoding="utf-8" ) return prompt_path -def test_get_muse_configs_raises_on_missing_type_with_output(): - muse = importlib.import_module("think.muse") +def test_get_talent_configs_raises_on_missing_type_with_output(): + talent = importlib.import_module("think.talent") stem = f"test_missing_type_output_{uuid.uuid4().hex}" - prompt_path = _write_temp_muse_prompt( + prompt_path = _write_temp_talent_prompt( stem, '{\n "schedule": "daily",\n "priority": 10,\n "output": "md"\n}', ) @@ -158,30 +158,30 @@ def test_get_muse_configs_raises_on_missing_type_with_output(): with pytest.raises( ValueError, match=rf"Prompt '{stem}'.*missing required 'type'" ): - muse.get_muse_configs(include_disabled=True) + talent.get_talent_configs(include_disabled=True) finally: prompt_path.unlink(missing_ok=True) -def test_get_muse_configs_allows_missing_type_with_tools(): - muse = importlib.import_module("think.muse") +def test_get_talent_configs_allows_missing_type_with_tools(): + talent = importlib.import_module("think.talent") stem = f"test_missing_type_tools_{uuid.uuid4().hex}" - prompt_path = _write_temp_muse_prompt( + prompt_path = _write_temp_talent_prompt( stem, '{\n "schedule": "daily",\n "priority": 10,\n "tools": "journal"\n}', ) try: - configs = muse.get_muse_configs(include_disabled=True) + configs = talent.get_talent_configs(include_disabled=True) assert stem in configs assert configs[stem].get("type") is None finally: prompt_path.unlink(missing_ok=True) -def test_get_muse_configs_raises_when_generate_missing_output(): - muse = importlib.import_module("think.muse") +def test_get_talent_configs_raises_when_generate_missing_output(): + talent = importlib.import_module("think.talent") stem = f"test_generate_missing_output_{uuid.uuid4().hex}" - prompt_path = _write_temp_muse_prompt( + prompt_path = _write_temp_talent_prompt( stem, '{\n "type": "generate",\n "schedule": "daily",\n "priority": 10\n}', ) @@ -190,35 +190,35 @@ def test_get_muse_configs_raises_when_generate_missing_output(): ValueError, match=rf"Prompt '{stem}'.*type='generate'.*missing required 'output'", ): - muse.get_muse_configs(include_disabled=True) + talent.get_talent_configs(include_disabled=True) finally: prompt_path.unlink(missing_ok=True) -def test_get_muse_configs_allows_cogitate_without_tools(): - muse = importlib.import_module("think.muse") +def test_get_talent_configs_allows_cogitate_without_tools(): + talent = importlib.import_module("think.talent") stem = f"test_cogitate_missing_tools_{uuid.uuid4().hex}" - prompt_path = _write_temp_muse_prompt( + prompt_path = _write_temp_talent_prompt( stem, '{\n "type": "cogitate",\n "schedule": "daily",\n "priority": 10\n}', ) try: - configs = muse.get_muse_configs(include_disabled=True) + configs = talent.get_talent_configs(include_disabled=True) assert stem in configs assert configs[stem]["type"] == "cogitate" finally: prompt_path.unlink(missing_ok=True) -def test_get_muse_configs_type_generate_returns_only_generate(): - muse = importlib.import_module("think.muse") - generators = muse.get_muse_configs(type="generate") +def test_get_talent_configs_type_generate_returns_only_generate(): + talent = importlib.import_module("think.talent") + generators = talent.get_talent_configs(type="generate") assert generators, "Expected at least one generate prompt" assert all(meta.get("type") == "generate" for meta in generators.values()) -def test_get_muse_configs_type_cogitate_returns_only_cogitate(): - muse = importlib.import_module("think.muse") - cogitate_prompts = muse.get_muse_configs(type="cogitate") +def test_get_talent_configs_type_cogitate_returns_only_cogitate(): + talent = importlib.import_module("think.talent") + cogitate_prompts = talent.get_talent_configs(type="cogitate") assert cogitate_prompts, "Expected at least one cogitate prompt" assert all(meta.get("type") == "cogitate" for meta in cogitate_prompts.values()) diff --git a/tests/test_home_events.py b/tests/test_home_events.py index ae9f6cf76..8b63cedbf 100644 --- a/tests/test_home_events.py +++ b/tests/test_home_events.py @@ -92,7 +92,7 @@ class TestRecordTriageExchange: path="/home", user_message="hello world", agent_response="hi there", - muse=agent_name, + talent=agent_name, agent_id="abc123", ) @@ -119,7 +119,7 @@ class TestRecordTriageExchange: path="", user_message="", agent_response="done", - muse="unified", + talent="unified", agent_id="abc123", ) diff --git a/tests/test_journal_stats.py b/tests/test_journal_stats.py index 2af0d51f7..58720b8ba 100644 --- a/tests/test_journal_stats.py +++ b/tests/test_journal_stats.py @@ -283,7 +283,7 @@ def test_process_token_entry_counts_all_int_usage_fields(tmp_path, monkeypatch): token_entry_with_duration = { "timestamp": 1704067200.0, "model": "gemini-2.5-flash", - "context": "muse.system.meetings", + "context": "talent.system.meetings", "type": "cogitate", "usage": { "input_tokens": 100, diff --git a/tests/test_models.py b/tests/test_models.py index ebe564144..88015b6d0 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -440,30 +440,30 @@ def test_context_registry_includes_categories(): assert registry[context]["tier"] in (TIER_PRO, TIER_FLASH, TIER_LITE) -def test_context_registry_includes_muse_configs(): - """Test that registry includes discovered muse contexts (agents + generators).""" +def test_context_registry_includes_talent_configs(): + """Test that registry includes discovered talent contexts (agents + generators).""" registry = get_context_registry() - # Should have muse entries (from muse/*.md and apps/*/muse/*.md) - muse_contexts = [k for k in registry if k.startswith("muse.")] + # Should have talent entries (from talent/*.md and apps/*/talent/*.md) + talent_contexts = [k for k in registry if k.startswith("talent.")] - # Should have multiple muse contexts (agents + generators) - assert len(muse_contexts) > 1, "Should discover muse contexts" + # Should have multiple talent contexts (agents + generators) + assert len(talent_contexts) > 1, "Should discover talent contexts" - # Should have system muse configs - system_muse = [k for k in muse_contexts if k.startswith("muse.system.")] - assert len(system_muse) > 0, "Should discover system muse configs" + # Should have system talent configs + system_talent = [k for k in talent_contexts if k.startswith("talent.system.")] + assert len(system_talent) > 0, "Should discover system talent configs" - # Should have app muse configs - app_muse = [ + # Should have app talent configs + app_talent = [ k - for k in muse_contexts - if k.startswith("muse.") and not k.startswith("muse.system.") + for k in talent_contexts + if k.startswith("talent.") and not k.startswith("talent.system.") ] - assert len(app_muse) > 0, "Should discover app muse configs" + assert len(app_talent) > 0, "Should discover app talent configs" - # Should include type field for muse contexts - for context in muse_contexts: + # Should include type field for talent contexts + for context in talent_contexts: assert "type" in registry[context], f"{context} missing type field" diff --git a/tests/test_observation.py b/tests/test_observation.py index e1c7b2fa5..55bff0efb 100644 --- a/tests/test_observation.py +++ b/tests/test_observation.py @@ -16,13 +16,13 @@ def _temp_journal(monkeypatch, tmp_path): class TestPreHook: def test_skips_when_not_observing(self): - from muse.observation import pre_process + from talent.observation import pre_process result = pre_process({"day": "20260306", "segment": "120000_300"}) assert result == {"skip_reason": "not_observing"} def test_skips_when_status_is_ready(self): - from muse.observation import pre_process + from talent.observation import pre_process from think.awareness import update_state update_state("onboarding", {"status": "ready"}) @@ -30,7 +30,7 @@ class TestPreHook: assert result == {"skip_reason": "not_observing"} def test_skips_when_status_is_complete(self): - from muse.observation import pre_process + from talent.observation import pre_process from think.awareness import update_state update_state("onboarding", {"status": "complete"}) @@ -38,7 +38,7 @@ class TestPreHook: assert result == {"skip_reason": "not_observing"} def test_skips_when_status_is_skipped(self): - from muse.observation import pre_process + from talent.observation import pre_process from think.awareness import update_state update_state("onboarding", {"status": "skipped"}) @@ -46,7 +46,7 @@ class TestPreHook: assert result == {"skip_reason": "not_observing"} def test_proceeds_when_observing(self): - from muse.observation import pre_process + from talent.observation import pre_process from think.awareness import start_onboarding start_onboarding("a") @@ -62,7 +62,7 @@ class TestPostHook: start_onboarding("a") def test_writes_observation_to_log(self): - from muse.observation import post_process + from talent.observation import post_process from think.awareness import read_log findings = json.dumps( @@ -89,7 +89,7 @@ class TestPostHook: assert obs[0]["message"] == "Solo coding session" def test_increments_observation_count(self): - from muse.observation import post_process + from talent.observation import post_process from think.awareness import get_onboarding findings = json.dumps({"summary": "test", "has_meeting": False}) @@ -101,19 +101,19 @@ class TestPostHook: assert get_onboarding()["observation_count"] == 2 def test_handles_invalid_json(self): - from muse.observation import post_process + from talent.observation import post_process result = post_process("not json", {"day": "20260306", "segment": "120000_300"}) assert result == "not json" # Returns result unchanged def test_handles_non_dict_json(self): - from muse.observation import post_process + from talent.observation import post_process result = post_process("[1,2,3]", {"day": "20260306", "segment": "120000_300"}) assert result == "[1,2,3]" # Returns result unchanged def test_returns_result_unchanged(self): - from muse.observation import post_process + from talent.observation import post_process findings = json.dumps({"summary": "test", "has_meeting": False}) result = post_process(findings, {"day": "20260306", "segment": "120000_300"}) @@ -122,7 +122,7 @@ class TestPostHook: class TestNudgeLogic: def test_first_meeting_triggers_nudge(self): - from muse.observation import _check_nudge + from talent.observation import _check_nudge findings = { "has_meeting": True, @@ -135,14 +135,14 @@ class TestNudgeLogic: assert "3 people" in nudge["message"] def test_no_meeting_no_first_nudge(self): - from muse.observation import _check_nudge + from talent.observation import _check_nudge findings = {"has_meeting": False} nudge = _check_nudge(findings, 1, 0, {}) assert nudge is None def test_entity_cluster_triggers_nudge(self): - from muse.observation import _check_nudge + from talent.observation import _check_nudge findings = { "has_meeting": False, @@ -153,7 +153,7 @@ class TestNudgeLogic: assert "network" in nudge["title"].lower() def test_progress_update_at_5_segments(self): - from muse.observation import _check_nudge + from talent.observation import _check_nudge findings = {"has_meeting": False, "people": []} nudge = _check_nudge(findings, 5, 2, {}) @@ -161,7 +161,7 @@ class TestNudgeLogic: assert "Still learning" in nudge["title"] def test_no_nudge_when_max_reached(self): - from muse.observation import MAX_NUDGES, _check_nudge + from talent.observation import MAX_NUDGES, _check_nudge findings = {"has_meeting": True, "speaker_count": 5} # nudges_sent == MAX_NUDGES means all nudges used @@ -173,7 +173,7 @@ class TestNudgeLogic: class TestThreshold: def test_not_met_with_few_segments(self): - from muse.observation import _threshold_met + from talent.observation import _threshold_met onboarding = {"started": "20260306T08:00:00"} assert _threshold_met(onboarding, 5) is False @@ -182,21 +182,21 @@ class TestThreshold: # Just started — not enough time elapsed from datetime import datetime - from muse.observation import MIN_SEGMENTS, _threshold_met + from talent.observation import MIN_SEGMENTS, _threshold_met now = datetime.now().strftime("%Y%m%dT%H:%M:%S") onboarding = {"started": now} assert _threshold_met(onboarding, MIN_SEGMENTS) is False def test_met_with_enough_segments_and_time(self): - from muse.observation import MIN_SEGMENTS, _threshold_met + from talent.observation import MIN_SEGMENTS, _threshold_met # Started 5 hours ago onboarding = {"started": "20260101T03:00:00"} assert _threshold_met(onboarding, MIN_SEGMENTS) is True def test_not_met_with_no_started(self): - from muse.observation import MIN_SEGMENTS, _threshold_met + from talent.observation import MIN_SEGMENTS, _threshold_met onboarding = {} assert _threshold_met(onboarding, MIN_SEGMENTS) is False @@ -204,19 +204,19 @@ class TestThreshold: class TestElapsedHours: def test_valid_iso(self): - from muse.observation import _elapsed_hours + from talent.observation import _elapsed_hours # A date far in the past should give many hours hours = _elapsed_hours("20200101T00:00:00") assert hours > 24 def test_empty_string(self): - from muse.observation import _elapsed_hours + from talent.observation import _elapsed_hours assert _elapsed_hours("") == 0.0 def test_invalid_format(self): - from muse.observation import _elapsed_hours + from talent.observation import _elapsed_hours assert _elapsed_hours("not-a-date") == 0.0 diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index 3064d5661..7ef207b02 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -104,13 +104,13 @@ def test_triage_path_a_ready_gets_triage(): def test_triage_skipped_gets_unified(): - """Onboarding skipped, no facets → unified (single muse, no two-mode split).""" + """Onboarding skipped, no facets → unified (single talent, no two-mode split).""" mock = _run_triage(onboarding={"status": "skipped"}) assert mock.call_args.kwargs["name"] == "unified" def test_triage_complete_gets_unified(): - """Onboarding complete, no facets → unified (single muse, no two-mode split).""" + """Onboarding complete, no facets → unified (single talent, no two-mode split).""" mock = _run_triage(onboarding={"status": "complete"}) assert mock.call_args.kwargs["name"] == "unified" @@ -121,7 +121,7 @@ def test_triage_complete_gets_unified(): def test_chat_cli_routes_to_onboarding_when_unified_and_no_facets(): args = argparse.Namespace( message=["Hi there"], - muse="unified", + talent="unified", facet=None, provider=None, verbose=False, @@ -130,10 +130,10 @@ def test_chat_cli_routes_to_onboarding_when_unified_and_no_facets(): assert mock_request.call_args.kwargs["name"] == "onboarding" -def test_chat_cli_keeps_explicit_muse_when_no_facets(): +def test_chat_cli_keeps_explicit_talent_when_no_facets(): args = argparse.Namespace( message=["Hi there"], - muse="entities", + talent="entities", facet=None, provider=None, verbose=False, @@ -143,10 +143,10 @@ def test_chat_cli_keeps_explicit_muse_when_no_facets(): def test_chat_cli_path_a_observing_stays_unified(): - """During Path A observation, chat CLI uses unified muse, not onboarding.""" + """During Path A observation, chat CLI uses unified talent, not onboarding.""" args = argparse.Namespace( message=["What have you noticed?"], - muse="unified", + talent="unified", facet=None, provider=None, verbose=False, @@ -158,10 +158,10 @@ def test_chat_cli_path_a_observing_stays_unified(): def test_chat_cli_skipped_stays_unified(): - """After skipping onboarding, chat CLI uses unified muse.""" + """After skipping onboarding, chat CLI uses unified talent.""" args = argparse.Namespace( message=["Hello"], - muse="unified", + talent="unified", facet=None, provider=None, verbose=False, diff --git a/tests/test_output_hooks.py b/tests/test_output_hooks.py index af031a8f3..cca8a8a21 100644 --- a/tests/test_output_hooks.py +++ b/tests/test_output_hooks.py @@ -16,7 +16,7 @@ import os import shutil from pathlib import Path -from think.muse import load_post_hook, load_pre_hook +from think.talent import load_post_hook, load_pre_hook from think.utils import day_path FIXTURES = Path("tests/fixtures") @@ -120,8 +120,8 @@ def test_load_post_hook_no_hook_config(): def test_load_post_hook_named_resolution(): - """Test that named hooks resolve to muse/{name}.py.""" - # occurrence.py exists in muse/ + """Test that named hooks resolve to talent/{name}.py.""" + # occurrence.py exists in talent/ config = {"hook": {"post": "occurrence"}} hook_fn = load_post_hook(config) assert callable(hook_fn) @@ -139,7 +139,7 @@ def test_load_post_hook_file_not_found(tmp_path): def test_prompt_metadata_no_hook_path(tmp_path): """Test that _load_prompt_metadata no longer sets hook_path.""" - muse = importlib.import_module("think.muse") + talent = importlib.import_module("think.talent") md_file = tmp_path / "test_generator.md" md_file.write_text( @@ -150,7 +150,7 @@ def test_prompt_metadata_no_hook_path(tmp_path): hook_file = tmp_path / "test_generator.py" hook_file.write_text("def post_process(r, c): return r") - meta = muse._load_prompt_metadata(md_file) + meta = talent._load_prompt_metadata(md_file) # hook_path should no longer be set (hooks are loaded via load_post_hook) assert "hook_path" not in meta @@ -163,10 +163,10 @@ def test_output_hook_invocation(tmp_path, monkeypatch): mod = importlib.import_module("think.agents") copy_day(tmp_path) - # Use tmp_path as muse directory to avoid polluting real muse/ - import think.muse + # Use tmp_path as talent directory to avoid polluting real talent/ + import think.talent - monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) + monkeypatch.setattr(think.talent, "TALENT_DIR", tmp_path) prompt_file = tmp_path / "hooked_test.md" prompt_file.write_text( @@ -218,9 +218,9 @@ def test_output_hook_returns_none(tmp_path, monkeypatch): mod = importlib.import_module("think.agents") copy_day(tmp_path) - import think.muse + import think.talent - monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) + monkeypatch.setattr(think.talent, "TALENT_DIR", tmp_path) prompt_file = tmp_path / "noop_test.md" prompt_file.write_text( @@ -264,9 +264,9 @@ def test_output_hook_error_fallback(tmp_path, monkeypatch): mod = importlib.import_module("think.agents") copy_day(tmp_path) - import think.muse + import think.talent - monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) + monkeypatch.setattr(think.talent, "TALENT_DIR", tmp_path) prompt_file = tmp_path / "broken_test.md" prompt_file.write_text( @@ -381,9 +381,9 @@ def test_pre_hook_invocation(tmp_path, monkeypatch): mod = importlib.import_module("think.agents") copy_day(tmp_path) - import think.muse + import think.talent - monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) + monkeypatch.setattr(think.talent, "TALENT_DIR", tmp_path) prompt_file = tmp_path / "prehooked_test.md" prompt_file.write_text( @@ -441,9 +441,9 @@ def test_both_pre_and_post_hooks(tmp_path, monkeypatch): mod = importlib.import_module("think.agents") copy_day(tmp_path) - import think.muse + import think.talent - monkeypatch.setattr(think.muse, "MUSE_DIR", tmp_path) + monkeypatch.setattr(think.talent, "TALENT_DIR", tmp_path) prompt_file = tmp_path / "both_hooks_test.md" prompt_file.write_text( diff --git a/tests/test_output_path.py b/tests/test_output_path.py index f1c775f91..e8c882766 100644 --- a/tests/test_output_path.py +++ b/tests/test_output_path.py @@ -6,7 +6,7 @@ import os from pathlib import Path -from think.muse import get_output_name, get_output_path +from think.talent import get_output_name, get_output_path os.environ.setdefault("_SOLSTONE_JOURNAL_OVERRIDE", "tests/fixtures/journal") diff --git a/tests/test_routines.py b/tests/test_routines.py index d1a31ee43..25bfe4928 100644 --- a/tests/test_routines.py +++ b/tests/test_routines.py @@ -21,8 +21,8 @@ runner = CliRunner() def _load_chat_context_module(): - """Load muse.chat_context from this worktree explicitly for tests.""" - path = Path(__file__).resolve().parents[1] / "muse" / "chat_context.py" + """Load talent.chat_context from this worktree explicitly for tests.""" + path = Path(__file__).resolve().parents[1] / "talent" / "chat_context.py" spec = importlib.util.spec_from_file_location("test_chat_context", path) assert spec is not None assert spec.loader is not None diff --git a/tests/test_speaker_attribution_hook.py b/tests/test_speaker_attribution_hook.py index fe1a610b0..bd726f246 100644 --- a/tests/test_speaker_attribution_hook.py +++ b/tests/test_speaker_attribution_hook.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc -"""Unit tests for muse.speaker_attribution pre_process stub-writing behavior.""" +"""Unit tests for talent.speaker_attribution pre_process stub-writing behavior.""" import json from unittest.mock import patch @@ -19,7 +19,7 @@ def _run_pre_process(context, seg_dir, attribute_result): return_value=seg_dir, ), ): - from muse.speaker_attribution import pre_process + from talent.speaker_attribution import pre_process return pre_process(context) @@ -82,7 +82,7 @@ class TestPreProcessStub: """Missing day/segment -> returns early before any stub logic.""" (tmp_path / "audio.npz").write_bytes(b"x") with patch("think.utils.segment_path", return_value=tmp_path): - from muse.speaker_attribution import pre_process + from talent.speaker_attribution import pre_process result = pre_process({"stream": "default"}) stub_path = tmp_path / "agents" / "speaker_labels.json" diff --git a/tests/test_muse.py b/tests/test_talent.py similarity index 94% rename from tests/test_muse.py rename to tests/test_talent.py index 6f9e3acfe..a8dce1c07 100644 --- a/tests/test_muse.py +++ b/tests/test_talent.py @@ -1,9 +1,9 @@ # SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc -"""Tests for think.muse module.""" +"""Tests for think.talent module.""" -from think.muse import get_agent_filter, source_is_enabled, source_is_required +from think.talent import get_agent_filter, source_is_enabled, source_is_required def test_source_is_enabled_bool(): diff --git a/tests/test_muse_cli.py b/tests/test_talent_cli.py similarity index 97% rename from tests/test_muse_cli.py rename to tests/test_talent_cli.py index 1e76ed740..303a85655 100644 --- a/tests/test_muse_cli.py +++ b/tests/test_talent_cli.py @@ -1,13 +1,13 @@ # SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc -"""Tests for the sol muse CLI.""" +"""Tests for the sol talent CLI.""" import json import pytest -from think.muse_cli import ( +from think.talent_cli import ( _collect_configs, _format_bytes, _format_cost, @@ -159,7 +159,7 @@ def test_show_prompt_known(capsys): show_prompt("flow") output = capsys.readouterr().out - assert "muse/flow.md" in output + assert "talent/flow.md" in output assert "title:" in output assert "schedule:" in output assert "daily" in output @@ -238,7 +238,7 @@ def test_show_prompt_as_json(capsys): def test_truncate_content(): """Content truncation works correctly.""" - from think.muse_cli import _truncate_content + from think.talent_cli import _truncate_content # Short content not truncated short = "line1\nline2\nline3" @@ -257,7 +257,7 @@ def test_truncate_content(): def test_yesterday(): """Yesterday helper returns correct format.""" - from think.muse_cli import _yesterday + from think.talent_cli import _yesterday result = _yesterday() assert len(result) == 8 @@ -266,7 +266,7 @@ def test_yesterday(): def test_show_prompt_context_segment_validation(capsys): """Segment-scheduled prompts require --segment.""" - from think.muse_cli import show_prompt_context + from think.talent_cli import show_prompt_context with pytest.raises(SystemExit): show_prompt_context("screen", day="20260101") @@ -277,7 +277,7 @@ def test_show_prompt_context_segment_validation(capsys): def test_show_prompt_context_multi_facet_validation(capsys): """Multi-facet prompts require --facet.""" - from think.muse_cli import show_prompt_context + from think.talent_cli import show_prompt_context with pytest.raises(SystemExit): show_prompt_context("entities:entities") @@ -288,7 +288,7 @@ def test_show_prompt_context_multi_facet_validation(capsys): def test_show_prompt_context_day_format_validation(capsys): """Day argument must be YYYYMMDD format.""" - from think.muse_cli import show_prompt_context + from think.talent_cli import show_prompt_context # Too short with pytest.raises(SystemExit): @@ -601,7 +601,7 @@ def test_log_run_error_run(capsys): def test_show_prompt_context_activity_requires_facet(capsys): """Activity-scheduled prompts require --facet.""" - from think.muse_cli import show_prompt_context + from think.talent_cli import show_prompt_context with pytest.raises(SystemExit): show_prompt_context("decisions", day="20260214") @@ -613,7 +613,7 @@ def test_show_prompt_context_activity_requires_facet(capsys): def test_show_prompt_context_activity_requires_activity_id(capsys): """Activity-scheduled prompts require --activity and list available IDs.""" - from think.muse_cli import show_prompt_context + from think.talent_cli import show_prompt_context with pytest.raises(SystemExit): show_prompt_context("decisions", day="20260214", facet="full-featured") @@ -626,7 +626,7 @@ def test_show_prompt_context_activity_requires_activity_id(capsys): def test_show_prompt_context_activity_not_found(capsys): """Activity-scheduled prompt with unknown activity ID errors.""" - from think.muse_cli import show_prompt_context + from think.talent_cli import show_prompt_context with pytest.raises(SystemExit): show_prompt_context( diff --git a/think/activities.py b/think/activities.py index a9b6c451d..fb96b9e80 100644 --- a/think/activities.py +++ b/think/activities.py @@ -705,7 +705,7 @@ def get_activity_output_path( Returns: Absolute path for the output file """ - from think.muse import get_output_name + from think.talent import get_output_name output_name = get_output_name(key) ext = "json" if output_format == "json" else "md" diff --git a/think/agents.py b/think/agents.py index 53f211c54..c9b7b9617 100644 --- a/think/agents.py +++ b/think/agents.py @@ -26,9 +26,9 @@ from pathlib import Path from typing import Any, Callable, Optional from think.cluster import cluster, cluster_period, cluster_span -from think.muse import ( +from think.talent import ( get_agent_filter, - get_muse_configs, + get_talent_configs, get_output_path, load_post_hook, load_pre_hook, @@ -434,7 +434,7 @@ def prepare_config(request: dict) -> dict: Fully prepared config dict """ from think.models import resolve_model_for_provider, resolve_provider - from think.muse import get_agent, key_to_context + from think.talent import get_agent, key_to_context name = request.get("name", "unified") facet = request.get("facet") @@ -806,7 +806,7 @@ async def _execute_with_tools( context = config.get("context") if not context: - from think.muse import key_to_context + from think.talent import key_to_context context = key_to_context(config.get("name", "unified")) backup_model = resolve_model_for_provider(context, backup, "cogitate") @@ -862,7 +862,7 @@ async def _execute_generate( emit_event: Event emission callback """ from think.models import generate_with_result - from think.muse import key_to_context + from think.talent import key_to_context name = config.get("name", "unified") transcript = config.get("transcript", "") @@ -1127,7 +1127,7 @@ def scan_day(day: str) -> dict[str, list[str]]: stored within segment directories and are not included here. """ day_dir = day_path(day) - daily_generators = get_muse_configs( + daily_generators = get_talent_configs( type="generate", schedule="daily", include_disabled=True ) processed: list[str] = [] diff --git a/think/awareness.py b/think/awareness.py index b7e67c4f6..2f53763ea 100644 --- a/think/awareness.py +++ b/think/awareness.py @@ -25,6 +25,7 @@ from pathlib import Path from typing import Any logger = logging.getLogger(__name__) +_LEGACY_AGENT_FIELD = "mu" "se" def _awareness_dir() -> Path: @@ -565,7 +566,11 @@ def compute_thickness() -> dict[str, Any]: exchanges = get_recent_exchanges(limit=10000) except Exception: exchanges = [] - non_onboarding = [ex for ex in exchanges if ex.get("muse") != "onboarding"] + non_onboarding = [ + ex + for ex in exchanges + if (ex.get("talent") or ex.get(_LEGACY_AGENT_FIELD, "")) != "onboarding" + ] conversation_count = len(non_onboarding) entity_names = [e["entity_name"].lower() for e in entities if e.get("entity_name")] diff --git a/think/chat_cli.py b/think/chat_cli.py index c45b2f8c2..b45bd48d1 100644 --- a/think/chat_cli.py +++ b/think/chat_cli.py @@ -24,11 +24,11 @@ def main() -> None: parser.add_argument("--facet", help="Facet context") parser.add_argument("--provider", help="AI provider override") parser.add_argument( - "--muse", default="unified", help="Muse agent name (default: unified)" + "--talent", default="unified", help="Talent agent name (default: unified)" ) args = setup_cli(parser) - if args.muse == "unified": + if args.talent == "unified": from think.awareness import get_onboarding from think.facets import get_enabled_facets @@ -36,9 +36,9 @@ def main() -> None: onboarding_status = onboarding.get("status", "") if onboarding_status in ("observing", "ready", "complete", "skipped"): - pass # Stay with unified muse — onboarding path already chosen + pass # Stay with unified talent — onboarding path already chosen elif not get_enabled_facets(): - args.muse = "onboarding" + args.talent = "onboarding" if not args.message: parser.print_help() @@ -52,7 +52,7 @@ def main() -> None: agent_id = cortex_request( prompt=message, - name=args.muse, + name=args.talent, provider=args.provider, config=config if config else None, ) diff --git a/think/conversation.py b/think/conversation.py index ff1f4f905..f8081cfd7 100644 --- a/think/conversation.py +++ b/think/conversation.py @@ -4,7 +4,7 @@ """Conversation memory service for solstone. Manages conversation exchange storage, retrieval, and context injection -for the unified muse agent. Three layers of recall: +for the unified talent agent. Three layers of recall: - Layer 1: Recent exchanges (last ~10 turns), loaded directly into context - Layer 2: Today's earlier exchanges, summarized compactly @@ -30,7 +30,7 @@ EXCHANGES_FILE = "conversation/exchanges.jsonl" # Journal stream name for conversation segments CONVERSATION_STREAM = "conversation" -# Marker in unified muse for memory injection +# Marker in unified talent for memory injection INJECTION_MARKER = "CONVERSATION_MEMORY_INJECTION_POINT" # Context budget: max characters for agent response in recent exchanges @@ -56,7 +56,7 @@ def record_exchange( path: str = "", user_message: str = "", agent_response: str = "", - muse: str = "", + talent: str = "", agent_id: str = "", ) -> None: """Record a conversation exchange to journal storage. @@ -83,7 +83,7 @@ def record_exchange( "path": path, "user_message": user_message, "agent_response": agent_response, - "muse": muse, + "talent": talent, "agent_id": agent_id, } @@ -321,7 +321,7 @@ def build_memory_context( """Build the full conversation memory context block. Assembles layer 1 (recent exchanges) and layer 2 (today's summary) - into a formatted block for injection into the unified muse prompt. + into a formatted block for injection into the unified talent prompt. Args: facet: Active facet for filtering. @@ -361,7 +361,7 @@ def inject_memory(user_instruction: str, memory_context: str) -> str: """Replace the CONVERSATION_MEMORY_INJECTION_POINT with memory context. Args: - user_instruction: The unified muse's user instruction text. + user_instruction: The unified talent's user instruction text. memory_context: Formatted conversation memory to inject. Returns: diff --git a/think/cortex.py b/think/cortex.py index 35901e968..78a97f14d 100644 --- a/think/cortex.py +++ b/think/cortex.py @@ -428,7 +428,7 @@ class CortexService: if usage_data and original_request: try: from think.models import log_token_usage - from think.muse import key_to_context + from think.talent import key_to_context model = original_request.get("model", "unknown") name = original_request.get("name", "unknown") diff --git a/think/dream.py b/think/dream.py index 11bdce667..9a99619f7 100644 --- a/think/dream.py +++ b/think/dream.py @@ -30,7 +30,7 @@ from think.facets import ( get_enabled_facets, load_segment_facets, ) -from think.muse import get_muse_configs, get_output_path +from think.talent import get_talent_configs, get_output_path from think.runner import run_task from think.sense_splitter import write_idle_stubs, write_sense_outputs from think.utils import ( @@ -404,7 +404,7 @@ def run_segment_sense( agents based on Sense output. """ target_schedule = "segment" - all_prompts = get_muse_configs(schedule="segment") + all_prompts = get_talent_configs(schedule="segment") if not all_prompts: logging.info("No prompts found for schedule: segment") return (0, 0, []) @@ -764,7 +764,7 @@ def run_daily_prompts( target_schedule = "daily" # Load ALL scheduled prompts (both generators and agents) - all_prompts = get_muse_configs(schedule=target_schedule) + all_prompts = get_talent_configs(schedule=target_schedule) if not all_prompts: logging.info(f"No prompts found for schedule: {target_schedule}") @@ -773,7 +773,7 @@ def run_daily_prompts( # Group prompts by priority priority_groups: dict[int, list[tuple[str, dict]]] = {} for name, config in all_prompts.items(): - priority = config["priority"] # Required field, validated by get_muse_configs + priority = config["priority"] # Required field, validated by get_talent_configs priority_groups.setdefault(priority, []).append((name, config)) # Pre-compute shared data for multi-facet prompts @@ -1060,7 +1060,7 @@ def run_weekly_prompts( target_schedule = "weekly" # Load ALL scheduled prompts (both generators and agents) - all_prompts = get_muse_configs(schedule=target_schedule) + all_prompts = get_talent_configs(schedule=target_schedule) if not all_prompts: logging.info(f"No prompts found for schedule: {target_schedule}") @@ -1069,7 +1069,7 @@ def run_weekly_prompts( # Group prompts by priority priority_groups: dict[int, list[tuple[str, dict]]] = {} for name, config in all_prompts.items(): - priority = config["priority"] # Required field, validated by get_muse_configs + priority = config["priority"] # Required field, validated by get_talent_configs priority_groups.setdefault(priority, []).append((name, config)) # Pre-compute shared data for multi-facet prompts @@ -1380,7 +1380,7 @@ def run_activity_prompts( return False # Load activity-scheduled agents - all_prompts = get_muse_configs(schedule="activity") + all_prompts = get_talent_configs(schedule="activity") if not all_prompts: logging.info("No activity-scheduled agents found") @@ -1680,7 +1680,7 @@ def run_flush_prompts( Returns: True if all flush agents succeeded, False if any failed """ - all_prompts = get_muse_configs(schedule="segment") + all_prompts = get_talent_configs(schedule="segment") # Filter to only agents with flush hooks flush_prompts = { @@ -1895,7 +1895,7 @@ def dry_run( return if weekly: - all_prompts = get_muse_configs(schedule="weekly") + all_prompts = get_talent_configs(schedule="weekly") print(f"Day {day_formatted} — weekly agents\n") if not all_prompts: print("No prompts for schedule: weekly") @@ -1917,14 +1917,14 @@ def dry_run( label += f" stream={seg_stream}" print(label) print() - all_prompts = get_muse_configs(schedule="segment") + all_prompts = get_talent_configs(schedule="segment") if all_prompts: _print_segment_orchestrator(all_prompts, "") return # Default: full daily or segment run target_schedule = "segment" if segment else "daily" - all_prompts = get_muse_configs(schedule=target_schedule) + all_prompts = get_talent_configs(schedule=target_schedule) header = f"Day {day_formatted}" if segment: @@ -2063,7 +2063,7 @@ def _dry_run_activity( print(f" type: {activity_type}") print(f" segments: {len(segments)}") - all_prompts = get_muse_configs(schedule="activity") + all_prompts = get_talent_configs(schedule="activity") matching = { n: c for n, c in all_prompts.items() @@ -2102,7 +2102,7 @@ def _dry_run_activity( def _dry_run_flush(day: str, segment: str) -> None: """Dry-run for --flush mode.""" - all_prompts = get_muse_configs(schedule="segment") + all_prompts = get_talent_configs(schedule="segment") flush_prompts = { n: c for n, c in all_prompts.items() diff --git a/think/hooks.py b/think/hooks.py index bba71a8a5..a90e29c13 100644 --- a/think/hooks.py +++ b/think/hooks.py @@ -4,7 +4,7 @@ """Shared utilities for output extraction hooks. This module provides common functions used by extraction hooks like -occurrence.py and anticipation.py in the muse/ directory. +occurrence.py and anticipation.py in the talent/ directory. """ import json @@ -182,7 +182,7 @@ def compute_output_source(context: dict) -> str: Returns: Relative path like "20240101/agents/meetings.md". """ - from think.muse import get_output_name + from think.talent import get_output_name from think.utils import get_journal day = context.get("day", "") diff --git a/think/models.py b/think/models.py index 3a3be5c3b..a6f826b26 100644 --- a/think/models.py +++ b/think/models.py @@ -117,14 +117,14 @@ class IncompleteJSONError(ValueError): # Examples: # - observe.describe.frame -> observe module, describe feature, frame operation # - observe.enrich -> observe module, enrich feature (no sub-operation) -# - muse.system.meetings -> muse module, system source, meetings config -# - muse.entities.observer -> muse module, entities app, observer config +# - talent.system.meetings -> talent module, system source, meetings config +# - talent.entities.observer -> talent module, entities app, observer config # - app.chat.title -> apps module, chat app, title operation # # DISCOVERY SOURCES: # 1. Prompt files listed in PROMPT_PATHS (with context in frontmatter) # 2. Categories from observe/categories/*.md (tier/label/group in frontmatter) -# 3. Muse configs from muse/*.md and apps/*/muse/*.md +# 3. Talent configs from talent/*.md and apps/*/talent/*.md # # When adding new contexts: # 1. Create a .md prompt file with YAML frontmatter containing: @@ -153,6 +153,8 @@ PROMPT_PATHS: List[str] = [ # Cached context registry (built lazily on first use) _context_registry: Optional[Dict[str, Dict[str, Any]]] = None +_LEGACY_CONTEXT_PREFIX = "mu" "se." +_TALENT_CONTEXT_PREFIX = "talent." def _discover_prompt_contexts() -> Dict[str, Dict[str, Any]]: @@ -198,24 +200,24 @@ def _discover_prompt_contexts() -> Dict[str, Dict[str, Any]]: return contexts -def _discover_muse_contexts() -> Dict[str, Dict[str, Any]]: - """Discover muse context defaults from muse/*.md config files. +def _discover_talent_contexts() -> Dict[str, Dict[str, Any]]: + """Discover talent context defaults from talent/*.md config files. - Uses get_muse_configs() from think.muse to load all muse configurations + Uses get_talent_configs() from think.talent to load all talent configurations and converts them to context patterns with tier/label/group metadata. Returns ------- Dict[str, Dict[str, Any]] Mapping of context patterns to {tier, label, group, type} dicts. - Context patterns are: muse.system.{name} or muse.{app}.{name} + Context patterns are: talent.system.{name} or talent.{app}.{name} """ - from think.muse import get_muse_configs, key_to_context + from think.talent import get_talent_configs, key_to_context contexts = {} - # Load all muse configs (including disabled for completeness) - all_configs = get_muse_configs(include_disabled=True) + # Load all talent configs (including disabled for completeness) + all_configs = get_talent_configs(include_disabled=True) for key, config in all_configs.items(): context = key_to_context(key) @@ -235,7 +237,7 @@ def _build_context_registry() -> Dict[str, Dict[str, Any]]: Merges: 1. Prompt contexts from _discover_prompt_contexts() 2. Category contexts from observe/describe.py CATEGORIES - 3. Muse contexts from _discover_muse_contexts() + 3. Talent contexts from _discover_talent_contexts() Returns ------- @@ -259,9 +261,9 @@ def _build_context_registry() -> Dict[str, Dict[str, Any]]: except ImportError: pass # observe module not available - # Merge muse contexts (agents + generators) - muse_contexts = _discover_muse_contexts() - registry.update(muse_contexts) + # Merge talent contexts (agents + generators) + talent_contexts = _discover_talent_contexts() + registry.update(talent_contexts) return registry @@ -288,7 +290,7 @@ def _resolve_tier(context: str, agent_type: str) -> int: Parameters ---------- context - Context string (e.g., "muse.system.default", "observe.describe.frame"). + Context string (e.g., "talent.system.default", "observe.describe.frame"). agent_type Agent type ("generate" or "cogitate"). @@ -305,7 +307,7 @@ def _resolve_tier(context: str, agent_type: str) -> int: providers_config = journal_config.get("providers", {}) contexts = providers_config.get("contexts", {}) - # Get dynamic context registry (discovered prompts, categories, muse configs) + # Get dynamic context registry (discovered prompts, categories, talent configs) registry = get_context_registry() # Check journal config contexts first (exact match) @@ -383,7 +385,7 @@ def resolve_model_for_provider( Parameters ---------- context - Context string (e.g., "muse.system.default"). + Context string (e.g., "talent.system.default"). provider Provider name ("google", "openai", "anthropic"). agent_type @@ -421,7 +423,7 @@ def resolve_provider(context: str, agent_type: str) -> tuple[str, str]: Parameters ---------- context - Context string (e.g., "observe.describe.frame", "muse.system.meetings"). + Context string (e.g., "observe.describe.frame", "talent.system.meetings"). agent_type Agent type ("generate" or "cogitate"). @@ -470,7 +472,7 @@ def resolve_provider(context: str, agent_type: str) -> tuple[str, str]: # No context match - check dynamic context registry for this context if match_config is None: - # Get dynamic context registry (discovered prompts, categories, muse configs) + # Get dynamic context registry (discovered prompts, categories, talent configs) registry = get_context_registry() # Check for matching context default (exact match first, then glob) @@ -538,7 +540,7 @@ def log_token_usage( usage : dict Normalized usage dict with keys from USAGE_KEYS. context : str, optional - Context string (e.g., "module.function:123" or "muse.system.default"). + Context string (e.g., "module.function:123" or "talent.system.default"). If None, auto-detects from call stack. segment : str, optional Segment key (e.g., "143022_300") for attribution. @@ -761,6 +763,13 @@ def calc_agent_cost( return None +def _normalize_legacy_context(ctx: str) -> str: + """Normalize legacy token-log context strings to the talent namespace.""" + if ctx.startswith(_LEGACY_CONTEXT_PREFIX): + return _TALENT_CONTEXT_PREFIX + ctx[len(_LEGACY_CONTEXT_PREFIX) :] + return ctx + + def iter_token_log(day: str) -> Any: """Iterate over token log entries for a given day. @@ -790,7 +799,11 @@ def iter_token_log(day: str) -> Any: if not line: continue try: - yield json.loads(line) + entry = json.loads(line) + ctx = entry.get("context") + if isinstance(ctx, str): + entry["context"] = _normalize_legacy_context(ctx) + yield entry except json.JSONDecodeError: continue @@ -813,7 +826,7 @@ def get_usage_cost( Filter to entries with this exact segment key. context : str, optional Filter to entries where context starts with this prefix. - For example, "muse.system" matches "muse.system.default". + For example, "talent.system" matches "talent.system.default". Returns ------- @@ -896,7 +909,7 @@ def generate( contents : str or List The content to send to the model. context : str - Context string for routing and token logging (e.g., "muse.system.meetings"). + Context string for routing and token logging (e.g., "talent.system.meetings"). This is required and determines which provider/model to use. temperature : float Temperature for generation (default: 0.3). @@ -1145,7 +1158,7 @@ async def agenerate( contents : str or List The content to send to the model. context : str - Context string for routing and token logging (e.g., "muse.system.meetings"). + Context string for routing and token logging (e.g., "talent.system.meetings"). This is required and determines which provider/model to use. temperature : float Temperature for generation (default: 0.3). diff --git a/think/prompts.py b/think/prompts.py index ff7f732ba..09bb7e30e 100644 --- a/think/prompts.py +++ b/think/prompts.py @@ -4,14 +4,14 @@ """Core prompt loading utilities. This module provides the foundational prompt loading functionality used by both -standalone prompts (observe/, think/*.md) and the full muse agent orchestration. +standalone prompts (observe/, think/*.md) and the full talent agent orchestration. Key functions: - load_prompt(): Load and parse .md prompt files with template substitution - PromptContent: Named tuple for prompt text, path, and metadata For full agent/generator orchestration (scheduling, hooks, instruction composition), -use think.muse instead. +use think.talent instead. """ from __future__ import annotations diff --git a/think/muse.py b/think/talent.py similarity index 91% rename from think/muse.py rename to think/talent.py index c1120d817..37220eed1 100644 --- a/think/muse.py +++ b/think/talent.py @@ -1,13 +1,13 @@ # SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc -"""Muse agent and generator orchestration utilities. +"""Talent agent and generator orchestration utilities. -This module provides functionality for configuring and orchestrating muse agents -and generators from muse/*.md and apps/*/muse/*.md. +This module provides functionality for configuring and orchestrating talent agents +and generators from talent/*.md and apps/*/talent/*.md. Key functions: -- get_muse_configs(): Discover all muse configs with filtering +- get_talent_configs(): Discover all talent configs with filtering - get_agent(): Load complete agent configuration by name - Hook loading: load_pre_hook(), load_post_hook() @@ -31,39 +31,39 @@ from think.prompts import _load_prompt_metadata, load_prompt # Constants # --------------------------------------------------------------------------- -MUSE_DIR = Path(__file__).parent.parent / "muse" +TALENT_DIR = Path(__file__).parent.parent / "talent" APPS_DIR = Path(__file__).parent.parent / "apps" # --------------------------------------------------------------------------- -# Muse Config Discovery +# Talent Config Discovery # --------------------------------------------------------------------------- def key_to_context(key: str) -> str: - """Convert muse config key to context pattern. + """Convert talent config key to context pattern. Parameters ---------- key: - Muse config key in format "name" (system) or "app:name" (app). + Talent config key in format "name" (system) or "app:name" (app). Returns ------- str - Context pattern: "muse.system.{name}" or "muse.{app}.{name}". + Context pattern: "talent.system.{name}" or "talent.{app}.{name}". Examples -------- >>> key_to_context("meetings") - 'muse.system.meetings' + 'talent.system.meetings' >>> key_to_context("entities:observer") - 'muse.entities.observer' + 'talent.entities.observer' """ if ":" in key: app, name = key.split(":", 1) - return f"muse.{app}.{name}" - return f"muse.system.{key}" + return f"talent.{app}.{name}" + return f"talent.system.{key}" def get_output_name(key: str) -> str: @@ -151,16 +151,16 @@ def get_output_path( return day / "agents" / filename -def get_muse_configs( +def get_talent_configs( *, type: str | None = None, schedule: str | None = None, include_disabled: bool = False, ) -> dict[str, dict[str, Any]]: - """Load muse configs from system and app directories. + """Load talent configs from system and app directories. Unified function for loading both cogitate agents and generate prompts from - muse/*.md and apps/*/muse/*.md files. Filters based on explicit type field. + talent/*.md and apps/*/talent/*.md files. Filters based on explicit type field. Args: type: If provided, only configs with matching type value @@ -197,26 +197,26 @@ def get_muse_configs( return True - # System configs from muse/ - if MUSE_DIR.is_dir(): - for md_path in sorted(MUSE_DIR.glob("*.md")): + # System configs from talent/ + if TALENT_DIR.is_dir(): + for md_path in sorted(TALENT_DIR.glob("*.md")): name = md_path.stem info = _load_prompt_metadata(md_path) info["source"] = "system" configs[name] = info - # App configs from apps/*/muse/ + # App configs from apps/*/talent/ apps_dir = APPS_DIR if apps_dir.is_dir(): for app_path in sorted(apps_dir.iterdir()): if not app_path.is_dir() or app_path.name.startswith("_"): continue - app_muse_dir = app_path / "muse" - if not app_muse_dir.is_dir(): + app_talent_dir = app_path / "talent" + if not app_talent_dir.is_dir(): continue app_name = app_path.name - for md_path in sorted(app_muse_dir.glob("*.md")): + for md_path in sorted(app_talent_dir.glob("*.md")): item_name = md_path.stem info = _load_prompt_metadata(md_path) @@ -311,16 +311,16 @@ def _resolve_agent_path(name: str) -> tuple[Path, str]: (agent_directory, agent_name) tuple. """ if ":" in name: - # App agent: "support:support" -> apps/support/muse/support + # App agent: "support:support" -> apps/support/talent/support app, agent_name = name.split(":", 1) - agent_dir = Path(__file__).parent.parent / "apps" / app / "muse" + agent_dir = Path(__file__).parent.parent / "apps" / app / "talent" elif name == "unified": - # Chat agent: "unified" -> muse/chat - agent_dir = MUSE_DIR + # Chat agent: "unified" -> talent/chat + agent_dir = TALENT_DIR agent_name = "chat" else: - # System agent: bare name -> muse/{name} - agent_dir = MUSE_DIR + # System agent: bare name -> talent/{name} + agent_dir = TALENT_DIR agent_name = name return agent_dir, agent_name @@ -426,7 +426,7 @@ def get_agent( ---------- name: Agent name to load. Can be a system agent (e.g., "unified") - or an app-namespaced agent (e.g., "support:support" for apps/support/muse/support). + or an app-namespaced agent (e.g., "support:support" for apps/support/talent/support). facet: Optional facet name to focus on. Controls $facets template variable. analysis_day: @@ -485,8 +485,8 @@ def _resolve_hook_path(hook_name: str) -> Path: """Resolve hook name to file path. Resolution: - - Named: "name" -> muse/{name}.py - - App-qualified: "app:name" -> apps/{app}/muse/{name}.py + - Named: "name" -> talent/{name}.py + - App-qualified: "app:name" -> apps/{app}/talent/{name}.py - Explicit path: "path/to/hook.py" -> direct path """ if "/" in hook_name or hook_name.endswith(".py"): @@ -495,9 +495,9 @@ def _resolve_hook_path(hook_name: str) -> Path: return project_root / hook_name elif ":" in hook_name: app, name = hook_name.split(":", 1) - return Path(__file__).parent.parent / "apps" / app / "muse" / f"{name}.py" + return Path(__file__).parent.parent / "apps" / app / "talent" / f"{name}.py" else: - return MUSE_DIR / f"{hook_name}.py" + return TALENT_DIR / f"{hook_name}.py" def _load_hook_function(config: dict, key: str, func_name: str) -> Callable | None: diff --git a/think/muse_cli.py b/think/talent_cli.py similarity index 96% rename from think/muse_cli.py rename to think/talent_cli.py index eaf1abb8b..1e9eb49f8 100644 --- a/think/muse_cli.py +++ b/think/talent_cli.py @@ -1,23 +1,23 @@ # SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc -"""CLI for inspecting muse prompt configurations. +"""CLI for inspecting talent prompt configurations. Lists all system and app prompts with their frontmatter metadata, supports filtering by schedule and source, and provides detail views. Usage: - sol muse List all prompts grouped by schedule - sol muse list --schedule daily Filter by schedule type - sol muse list --json Output all configs as JSONL - sol muse show Show details for a specific prompt - sol muse show --json Output a single prompt as JSONL - sol muse show --prompt Show full prompt context (dry-run) - sol muse logs Show recent agent runs - sol muse logs -c 5 Show last 5 runs for an agent - sol muse log Show events for an agent run - sol muse log --json Output raw JSONL events - sol muse log --full Show expanded event details + sol talent List all prompts grouped by schedule + sol talent list --schedule daily Filter by schedule type + sol talent list --json Output all configs as JSONL + sol talent show Show details for a specific prompt + sol talent show --json Output a single prompt as JSONL + sol talent show --prompt Show full prompt context (dry-run) + sol talent logs Show recent agent runs + sol talent logs -c 5 Show last 5 runs for an agent + sol talent log Show events for an agent run + sol talent log --json Output raw JSONL events + sol talent log --full Show expanded event details """ from __future__ import annotations @@ -35,10 +35,10 @@ from typing import Any import frontmatter -from think.muse import ( - MUSE_DIR, +from think.talent import ( + TALENT_DIR, _load_prompt_metadata, - get_muse_configs, + get_talent_configs, ) from think.utils import setup_cli @@ -61,8 +61,8 @@ def _resolve_md_path(name: str) -> Path: """Resolve a prompt name to its .md file path.""" if ":" in name: app, agent_name = name.split(":", 1) - return _PROJECT_ROOT / "apps" / app / "muse" / f"{agent_name}.md" - return MUSE_DIR / f"{name}.md" + return _PROJECT_ROOT / "apps" / app / "talent" / f"{agent_name}.md" + return TALENT_DIR / f"{name}.md" def _scan_variables(body: str) -> list[str]: @@ -162,8 +162,8 @@ def _collect_configs( source: str | None = None, include_disabled: bool = False, ) -> dict[str, dict[str, Any]]: - """Collect all muse configs with optional filters applied.""" - configs = get_muse_configs(schedule=schedule, include_disabled=True) + """Collect all talent configs with optional filters applied.""" + configs = get_talent_configs(schedule=schedule, include_disabled=True) filtered: dict[str, dict[str, Any]] = {} for key, info in configs.items(): @@ -424,7 +424,7 @@ def show_prompt_context( what would be sent to the LLM provider. """ # Load prompt metadata - configs = get_muse_configs(include_disabled=True) + configs = get_talent_configs(include_disabled=True) if name not in configs: print(f"Prompt not found: {name}", file=sys.stderr) sys.exit(1) @@ -571,7 +571,7 @@ def show_prompt_context( config["facet"] = facet else: # Cogitate prompt - use get_agent() to build full config with instructions - from think.muse import get_agent + from think.talent import get_agent try: agent_config = get_agent(name, facet=facet) @@ -789,7 +789,7 @@ def _format_cost(cost_usd: float | None) -> str: def _get_output_size(request_event: dict[str, Any], journal_root: str) -> int | None: """Get output file size in bytes from a request event, or None.""" - from think.muse import get_output_path + from think.talent import get_output_path req_output = request_event.get("output") if not req_output: @@ -920,7 +920,7 @@ def logs_runs( rec_schedule = record.get("schedule") if rec_schedule is None: if _schedule_lookup is None: - all_configs = get_muse_configs(include_disabled=True) + all_configs = get_talent_configs(include_disabled=True) _schedule_lookup = { key: info.get("schedule") for key, info in all_configs.items() @@ -1118,8 +1118,8 @@ def log_run(agent_id: str, *, json_mode: bool = False, full: bool = False) -> No def main() -> None: - """Entry point for sol muse.""" - parser = argparse.ArgumentParser(description="Inspect muse prompt configurations") + """Entry point for sol talent.""" + parser = argparse.ArgumentParser(description="Inspect talent prompt configurations") subparsers = parser.add_subparsers(dest="subcommand") # --- list subcommand --- diff --git a/think/utils.py b/think/utils.py index f4317b75a..807997846 100644 --- a/think/utils.py +++ b/think/utils.py @@ -4,8 +4,8 @@ """General utilities for solstone. This module provides core utilities for journal access, date/segment handling, -configuration loading, and CLI setup. Muse-related utilities (prompt loading, -agent configs, etc.) have been moved to think/muse.py. +configuration loading, and CLI setup. Talent-related utilities (prompt loading, +agent configs, etc.) have been moved to think/talent.py. """ from __future__ import annotations @@ -104,7 +104,7 @@ def get_journal() -> str: Trust this function — never bypass it, cache its result, or set _SOLSTONE_JOURNAL_OVERRIDE from application code. The env var exists for external use only (tests, Makefile sandboxes). See - ``muse/coding/reference/environment.md``. + ``talent/coding/reference/environment.md``. """ override = os.environ.get("_SOLSTONE_JOURNAL_OVERRIDE") if override: