personal memory agent
0
fork

Configure Feed

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

Rename agent "persona" to "name" throughout codebase

Simplify agent terminology by replacing "persona" with "name" as the
identifier for agent configurations. This makes the API clearer:
- "name" identifies the agent configuration (e.g., "default", "entities:entity_assist")
- "agent_id" identifies the unique instance (timestamp)

API changes:
- cortex_request(persona=) → cortex_request(name=)
- spawn_agent(persona=) → spawn_agent(name=)
- get_agent(persona=) → get_agent(name=)
- Event field: "persona" → "name"
- HTTP header: X-Agent-Persona → X-Agent-Name
- Config key: config["persona"] → config["name"]

Updated 41 files across think/, convey/, apps/, tests/, and docs/.

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

+261 -261
+2 -2
AGENTS.md
··· 24 24 25 25 * **Entities**: Extracted information (people, projects, concepts) tracked over time across transcripts and interactions. Entities are associated with facets and enable semantic navigation. 26 26 27 - * **Agents**: AI processors with configurable personas that analyze content, extract insights, and respond to queries. See [docs/THINK.md](docs/THINK.md) for the agent system and [docs/CORTEX.md](docs/CORTEX.md) for eventing. 27 + * **Agents**: AI processors with configurable prompts that analyze content, extract insights, and respond to queries. See [docs/THINK.md](docs/THINK.md) for the agent system and [docs/CORTEX.md](docs/CORTEX.md) for eventing. 28 28 29 29 * **Callosum**: Message bus that enables asynchronous communication between components. See [docs/CALLOSUM.md](docs/CALLOSUM.md). 30 30 ··· 74 74 75 75 **Component Communication**: 76 76 * Callosum message bus enables async communication between services 77 - * Cortex orchestrates AI agent execution via `sol cortex`, spawning agent subprocesses with persona configurations 77 + * Cortex orchestrates AI agent execution via `sol cortex`, spawning agent subprocesses with agent configurations 78 78 * See [docs/THINK.md](docs/THINK.md) for agent system details and [docs/CORTEX.md](docs/CORTEX.md) for the eventing protocol 79 79 80 80 **Command Reference**:
+1 -1
apps/__init__.py
··· 14 14 app.json # Optional: Metadata overrides 15 15 tools.py # Optional: MCP tool extensions 16 16 insights/ # Optional: Custom insight prompts 17 - agents/ # Optional: Custom agent personas 17 + agents/ # Optional: Custom agents 18 18 tests/ # Optional: App-specific tests 19 19 20 20 Naming Rules:
+37 -37
apps/agents/routes.py
··· 113 113 def _parse_agent_file(agent_file: Path) -> dict[str, Any] | None: 114 114 """Parse agent JSONL file and extract metadata. 115 115 116 - Returns dict with: id, persona, start, status, prompt, facet, failed, 116 + Returns dict with: id, name, start, status, prompt, facet, failed, 117 117 runtime_seconds, thinking_count, tool_count, cost. 118 118 Returns None if file cannot be parsed. 119 119 """ ··· 143 143 144 144 agent_info = { 145 145 "id": agent_id, 146 - "persona": request_event.get("persona", "default"), 146 + "name": request_event.get("name", "default"), 147 147 "start": request_event.get("ts", 0), 148 148 "status": "running" if is_active else "completed", 149 149 "prompt": request_event.get("prompt", ""), ··· 217 217 return agents 218 218 219 219 220 - def _group_agents_by_persona( 220 + def _group_agents_by_name( 221 221 agents: list[dict], agents_meta: dict 222 222 ) -> dict[str, dict[str, Any]]: 223 - """Group agents by persona and add metadata. 223 + """Group agents by name and add metadata. 224 224 225 - Returns dict mapping persona to: 225 + Returns dict mapping name to: 226 226 - title: Display title 227 227 - source: "system" or "app" 228 228 - app: App name (for app agents) ··· 236 236 groups: dict[str, dict[str, Any]] = {} 237 237 238 238 for agent in agents: 239 - persona = agent["persona"] 240 - if persona not in groups: 241 - meta = agents_meta.get(persona, {}) 242 - groups[persona] = { 243 - "persona": persona, 244 - "title": meta.get("title", persona), 239 + name = agent["name"] 240 + if name not in groups: 241 + meta = agents_meta.get(name, {}) 242 + groups[name] = { 243 + "name": name, 244 + "title": meta.get("title", name), 245 245 "source": meta.get("source", "system"), 246 246 "app": meta.get("app"), 247 247 "run_count": 0, ··· 252 252 "facets": set(), 253 253 } 254 254 255 - groups[persona]["run_count"] += 1 255 + groups[name]["run_count"] += 1 256 256 if agent.get("failed"): 257 - groups[persona]["failed_count"] += 1 258 - groups[persona]["thinking_count"] += agent.get("thinking_count", 0) 259 - groups[persona]["tool_count"] += agent.get("tool_count", 0) 257 + groups[name]["failed_count"] += 1 258 + groups[name]["thinking_count"] += agent.get("thinking_count", 0) 259 + groups[name]["tool_count"] += agent.get("tool_count", 0) 260 260 if agent.get("cost") is not None: 261 - groups[persona]["total_cost"] += agent["cost"] 261 + groups[name]["total_cost"] += agent["cost"] 262 262 if agent.get("facet"): 263 - groups[persona]["facets"].add(agent["facet"]) 263 + groups[name]["facets"].add(agent["facet"]) 264 264 265 265 # Convert facet sets to lists for JSON serialization 266 266 for group in groups.values(): ··· 299 299 300 300 @agents_bp.route("/api/agents/<day>") 301 301 def api_agents_day(day: str) -> Any: 302 - """Get agents that ran on a specific day, grouped by persona. 302 + """Get agents that ran on a specific day, grouped by name. 303 303 304 304 Query params: 305 305 facet: Optional facet filter (from cookie if not specified) ··· 326 326 # Get agents for this day 327 327 agents = _get_agents_for_day(day, facet_filter) 328 328 329 - # Group by persona 330 - persona_groups = _group_agents_by_persona(agents, agents_meta) 329 + # Group by name 330 + name_groups = _group_agents_by_name(agents, agents_meta) 331 331 332 332 # Organize into system vs app groups 333 333 system_groups = [] 334 334 app_groups: dict[str, list] = {} 335 335 336 - for persona, group in persona_groups.items(): 336 + for name, group in name_groups.items(): 337 337 # Add facet colors for display 338 338 group["facet_colors"] = {} 339 339 for facet_name in group["facets"]: ··· 354 354 app_groups[app_name].sort(key=lambda x: x["title"].lower()) 355 355 356 356 # Calculate totals 357 - total_runs = sum(g["run_count"] for g in persona_groups.values()) 358 - failed_runs = sum(g["failed_count"] for g in persona_groups.values()) 357 + total_runs = sum(g["run_count"] for g in name_groups.values()) 358 + failed_runs = sum(g["failed_count"] for g in name_groups.values()) 359 359 360 360 return jsonify( 361 361 { ··· 369 369 ) 370 370 371 371 372 - @agents_bp.route("/api/agents/<day>/<path:persona>") 373 - def api_agent_runs(day: str, persona: str) -> Any: 372 + @agents_bp.route("/api/agents/<day>/<path:name>") 373 + def api_agent_runs(day: str, name: str) -> Any: 374 374 """Get runs for a specific agent on a specific day. 375 375 376 376 Returns list of runs with full details for display. ··· 384 384 agents_meta = get_agents() 385 385 facets = get_facets() 386 386 387 - # Get all agents for day and filter to this persona 387 + # Get all agents for day and filter to this name 388 388 all_agents = _get_agents_for_day(day, facet_filter) 389 - runs = [a for a in all_agents if a["persona"] == persona] 389 + runs = [a for a in all_agents if a["name"] == name] 390 390 391 391 # Add facet color to each run 392 392 for run in runs: ··· 396 396 run["facet_title"] = facets[run_facet].get("title", run_facet) 397 397 398 398 # Get agent metadata 399 - meta = agents_meta.get(persona, {}) 399 + meta = agents_meta.get(name, {}) 400 400 401 401 return jsonify( 402 402 { 403 - "persona": persona, 404 - "title": meta.get("title", persona), 403 + "name": name, 404 + "title": meta.get("title", name), 405 405 "source": meta.get("source", "system"), 406 406 "app": meta.get("app"), 407 407 "runs": runs, ··· 463 463 return jsonify({"error": str(e)}), 500 464 464 465 465 466 - @agents_bp.route("/api/preview/<path:persona>") 467 - def api_preview_prompt(persona: str) -> Any: 466 + @agents_bp.route("/api/preview/<path:name>") 467 + def api_preview_prompt(name: str) -> Any: 468 468 """Return the complete rendered prompt for an agent. 469 469 470 470 Returns: 471 471 { 472 - "persona": str, 472 + "name": str, 473 473 "title": str, 474 474 "full_prompt": str, 475 475 "multi_facet": bool ··· 478 478 try: 479 479 from think.utils import get_agent 480 480 481 - config = get_agent(persona) 481 + config = get_agent(name) 482 482 483 483 system_instruction = config.get("system_instruction", "") 484 484 extra_context = config.get("extra_context", "") ··· 489 489 490 490 return jsonify( 491 491 { 492 - "persona": persona, 493 - "title": config.get("title", persona), 492 + "name": name, 493 + "title": config.get("title", name), 494 494 "full_prompt": full_prompt, 495 495 "multi_facet": config.get("multi_facet", False), 496 496 } 497 497 ) 498 498 except FileNotFoundError: 499 - return jsonify({"error": f"Agent '{persona}' not found"}), 404 499 + return jsonify({"error": f"Agent '{name}' not found"}), 404 500 500 except Exception as e: 501 501 return jsonify({"error": str(e)}), 500 502 502
+8 -8
apps/agents/workspace.html
··· 510 510 (function() { 511 511 // State 512 512 let currentDay = null; 513 - let currentPersona = null; 513 + let currentName = null; 514 514 let agentData = null; 515 515 let expandedRunId = null; 516 516 let runDetailCache = {}; ··· 634 634 function renderCard(agent) { 635 635 const card = document.createElement('div'); 636 636 card.className = 'agent-card'; 637 - card.onclick = () => showRunList(agent.persona); 637 + card.onclick = () => showRunList(agent.name); 638 638 639 639 // Header with title and badges 640 640 const header = document.createElement('div'); ··· 730 730 } 731 731 732 732 // Show run list for a specific agent 733 - async function showRunList(persona) { 734 - currentPersona = persona; 733 + async function showRunList(name) { 734 + currentName = name; 735 735 expandedRunId = null; 736 736 runDetailCache = {}; 737 737 ··· 742 742 tbody.innerHTML = '<tr><td colspan="8" class="run-loading"><div class="spinner"></div></td></tr>'; 743 743 744 744 try { 745 - const response = await fetch(`api/agents/${currentDay}/${encodeURIComponent(persona)}`); 745 + const response = await fetch(`api/agents/${currentDay}/${encodeURIComponent(name)}`); 746 746 const data = await response.json(); 747 747 748 748 document.getElementById('list-view-title').textContent = data.title; ··· 952 952 953 953 // Show grid view 954 954 window.showGridView = function() { 955 - currentPersona = null; 955 + currentName = null; 956 956 expandedRunId = null; 957 957 document.getElementById('grid-view').style.display = 'block'; 958 958 document.getElementById('list-view').style.display = 'none'; ··· 960 960 961 961 // Show preview modal 962 962 window.showPreview = async function() { 963 - if (!currentPersona) return; 963 + if (!currentName) return; 964 964 965 965 const modal = document.getElementById('preview-modal'); 966 966 const content = document.getElementById('preview-modal-content'); ··· 970 970 modal.classList.add('show'); 971 971 972 972 try { 973 - const response = await fetch(`api/preview/${encodeURIComponent(currentPersona)}`); 973 + const response = await fetch(`api/preview/${encodeURIComponent(currentName)}`); 974 974 const data = await response.json(); 975 975 976 976 if (data.error) {
+2 -2
apps/chat/routes.py
··· 186 186 # Create agent request - events will be broadcast by shared watcher 187 187 agent_id = spawn_agent( 188 188 prompt=full_prompt, 189 - persona="default", 189 + name="default", 190 190 provider=provider, 191 191 config=config, 192 192 ) ··· 507 507 # Spawn retry agent 508 508 agent_id = spawn_agent( 509 509 prompt=prompt, 510 - persona="default", 510 + name="default", 511 511 provider=provider, 512 512 config=config, 513 513 )
+3 -3
apps/entities/routes.py
··· 523 523 524 524 agent_id = spawn_agent( 525 525 prompt=prompt, 526 - persona="entities:entity_describe", 526 + name="entities:entity_describe", 527 527 provider="google", 528 528 ) 529 529 ··· 553 553 # Format prompt as specified by entity_assist agent 554 554 prompt = f"For the '{facet_name}' facet, this is the user's request to attach a new entity: {name}" 555 555 556 - # Create agent request - entity_assist persona already has provider configured 556 + # Create agent request - entity_assist agent already has provider configured 557 557 agent_id = spawn_agent( 558 558 prompt=prompt, 559 - persona="entities:entity_assist", 559 + name="entities:entity_assist", 560 560 ) 561 561 562 562 return jsonify({"success": True, "agent_id": agent_id})
+3 -3
apps/live/workspace.html
··· 950 950 return ` 951 951 <div class="activity-card agent-active" data-agent-id="${agent.agent_id}"> 952 952 <div class="activity-card-id">...${getAgentId(agent.agent_id)}</div> 953 - <div class="activity-card-name">${agent.persona || 'default'}</div> 953 + <div class="activity-card-name">${agent.name || 'default'}</div> 954 954 <div class="activity-card-state">${stateIcon} ${agent.event || 'unknown'}</div> 955 955 <div class="activity-card-elapsed">${elapsed}</div> 956 956 <div class="activity-card-provider">${agent.provider || 'unknown'}</div> ··· 1108 1108 state.agents.set(agent.agent_id, { 1109 1109 ...existing, 1110 1110 agent_id: agent.agent_id, 1111 - persona: agent.persona, 1111 + name: agent.name, 1112 1112 provider: agent.provider, 1113 1113 elapsed_seconds: agent.elapsed_seconds, 1114 1114 event: existing.event || 'thinking' ··· 1132 1132 state.agents.set(agentId, { 1133 1133 ...existing, 1134 1134 agent_id: agentId, 1135 - persona: msg.persona || existing.persona, 1135 + name: msg.name || existing.name, 1136 1136 provider: msg.provider || existing.provider, 1137 1137 event: msg.event, 1138 1138 ts: msg.ts,
+2 -2
apps/todos/routes.py
··· 583 583 584 584 agent_id = spawn_agent( 585 585 prompt=prompt, 586 - persona="todos:todo", 586 + name="todos:todo", 587 587 provider="openai", 588 588 config={}, 589 589 ) ··· 667 667 668 668 agent_id = spawn_agent( 669 669 prompt=prompt, 670 - persona="todos:weekly", 670 + name="todos:weekly", 671 671 provider="openai", 672 672 config={}, 673 673 )
+3 -3
convey/utils.py
··· 84 84 85 85 def spawn_agent( 86 86 prompt: str, 87 - persona: str, 87 + name: str, 88 88 provider: Optional[str] = None, 89 89 config: Optional[dict[str, Any]] = None, 90 90 ) -> str: ··· 95 95 96 96 Args: 97 97 prompt: The task or question for the agent 98 - persona: Agent persona - system (e.g., "default") or app-qualified (e.g., "entities:entity_assist") 98 + name: Agent name - system (e.g., "default") or app-qualified (e.g., "entities:entity_assist") 99 99 provider: Optional provider override (openai, google, anthropic) 100 100 config: Additional configuration (max_tokens, facet, continue_from, etc.) 101 101 ··· 110 110 111 111 return cortex_request( 112 112 prompt=prompt, 113 - persona=persona, 113 + name=name, 114 114 provider=provider, 115 115 config=config, 116 116 )
+4 -4
docs/APPS.md
··· 43 43 ├── app_bar.html # Optional: Bottom bar controls (forms, buttons) 44 44 ├── background.html # Optional: Background JavaScript service 45 45 ├── insights/ # Optional: Custom insight prompts (auto-discovered) 46 - ├── agents/ # Optional: Custom agent personas (auto-discovered) 46 + ├── agents/ # Optional: Custom agents (auto-discovered) 47 47 ├── maint/ # Optional: One-time maintenance tasks (auto-discovered) 48 48 └── tests/ # Optional: App-specific tests (run via make test-apps) 49 49 ``` ··· 60 60 | `app_bar.html` | No | Bottom fixed bar for app controls | 61 61 | `background.html` | No | Background service (WebSocket listeners) | 62 62 | `insights/` | No | Custom insight prompts as `.md` files with JSON frontmatter | 63 - | `agents/` | No | Custom agent personas as `.md` files with JSON frontmatter | 63 + | `agents/` | No | Custom agents as `.md` files with JSON frontmatter | 64 64 | `maint/` | No | One-time maintenance tasks (run on Convey startup) | 65 65 | `tests/` | No | App-specific tests with self-contained fixtures | 66 66 ··· 322 322 323 323 ### 8. `muse/` - App Agents and Insights 324 324 325 - Define custom agent personas and insight templates that integrate with solstone's Cortex agent system. 325 + Define custom agents and insight templates that integrate with solstone's Cortex agent system. 326 326 327 327 **Key Points:** 328 328 - Create `muse/` directory with `.md` files containing JSON frontmatter ··· 445 445 - `format_date(date_str)` - Format YYYYMMDD as "Wednesday January 14th" 446 446 447 447 ### Agent Spawning 448 - - `spawn_agent(prompt, persona, provider, config)` - Spawn Cortex agent, returns agent_id 448 + - `spawn_agent(prompt, name, provider, config)` - Spawn Cortex agent, returns agent_id 449 449 450 450 ### JSON Utilities 451 451 - `load_json(path)` - Load JSON file with error handling (returns None on error)
+2 -2
docs/CALLOSUM.md
··· 37 37 ### `cortex` - Agent execution events 38 38 **Source:** `think/cortex.py` 39 39 **Events:** `request`, `start`, `thinking`, `tool_start`, `tool_end`, `finish`, `error`, `agent_updated`, `info`, `status` 40 - **Details:** See [CORTEX.md](CORTEX.md) for agent lifecycle, personas, and event schemas 40 + **Details:** See [CORTEX.md](CORTEX.md) for agent lifecycle, configuration, and event schemas 41 41 42 42 ### `supervisor` - Process lifecycle management 43 43 **Source:** `think/supervisor.py` ··· 195 195 For agent requests, use the cortex client: 196 196 ```python 197 197 from think.cortex_client import cortex_request 198 - agent_id = cortex_request(prompt="...", persona="default") 198 + agent_id = cortex_request(prompt="...", name="default") 199 199 ``` 200 200 201 201 See `think/cortex_client.py` for the full API.
+19 -19
docs/CORTEX.md
··· 16 16 17 17 ### Key Components 18 18 - **Message Bus Integration**: Cortex connects to Callosum to receive requests and broadcast events 19 - - **Configuration Loading**: Cortex loads and merges persona configuration with request parameters 19 + - **Configuration Loading**: Cortex loads and merges agent configuration with request parameters 20 20 - **Process Management**: Spawns agent subprocesses via the `sol agents` command with merged configuration 21 21 - **Event Capture**: Monitors agent stdout/stderr and appends to JSONL files 22 22 - **Dual Event Distribution**: Events go to both persistent files and real-time message bus ··· 37 37 "event": "request", 38 38 "ts": 1234567890123, // Required: millisecond timestamp (must match filename) 39 39 "prompt": "Analyze this code for security issues", // Required: the task or question 40 - "persona": "default", // Optional: agent persona from muse/*.md 40 + "name": "default", // Optional: agent name from muse/*.md 41 41 "provider": "openai", // Optional: override provider (openai, google, anthropic) 42 42 "max_output_tokens": 8192, // Optional: maximum response tokens 43 43 "thinking_budget": 10000, // Optional: thinking token budget (ignored by OpenAI) ··· 51 51 "DEBUG": "true" 52 52 }, 53 53 "handoff": { // Optional: chain to another agent on completion 54 - "persona": "reviewer", 54 + "name": "reviewer", 55 55 "prompt": "Review the analysis", 56 56 "provider": "openai" 57 57 }, ··· 59 59 } 60 60 ``` 61 61 62 - The model is automatically resolved based on the agent context (`agent.{app}.{persona}`) 62 + The model is automatically resolved based on the agent context (`agent.{app}.{name}`) 63 63 and the configured tier in `journal.json`. Provider can optionally be overridden at 64 64 request time, which will resolve the appropriate model for that provider at the same tier. 65 65 ··· 85 85 "agent_id": "1234567890123", 86 86 "prompt": "User's task or question", 87 87 "provider": "openai", 88 - "persona": "default", 88 + "name": "default", 89 89 "output": "md", 90 90 "day": "20250109", 91 91 "handoff": {}, ··· 100 100 "event": "start", 101 101 "ts": 1234567890123, 102 102 "agent_id": "1234567890123", 103 - "persona": "default", 103 + "name": "default", 104 104 "model": "gpt-4o" 105 105 } 106 106 ``` ··· 165 165 "result": "Final response text to the user", 166 166 "handoff": { // Optional: triggers next agent 167 167 "prompt": "Continue with next task", 168 - "persona": "specialist", 168 + "name": "specialist", 169 169 "provider": "openai" 170 170 } 171 171 } ··· 209 209 When an agent completes successfully, its result can be automatically written to a file. This uses the same output path logic as insights. 210 210 211 211 - Include an `output` field in the agent's frontmatter with the format ("md" or "json") 212 - - Output path is derived from persona name + format + schedule: 213 - - Daily agents: `YYYYMMDD/insights/{persona}.{ext}` 214 - - Segment agents: `YYYYMMDD/{segment}/{persona}.{ext}` 212 + - Output path is derived from agent name + format + schedule: 213 + - Daily agents: `YYYYMMDD/insights/{name}.{ext}` 214 + - Segment agents: `YYYYMMDD/{segment}/{name}.{ext}` 215 215 - Writing occurs before any handoff processing 216 216 - Write failures are logged but don't interrupt the agent flow 217 217 - Commonly used for scheduled agents that generate daily reports ··· 225 225 - Handoff agents automatically inherit the parent agent's configuration (provider, model, etc.) unless explicitly overridden 226 226 - This enables multi-step workflows and agent specialization with consistent configuration 227 227 228 - ## Agent Personas 228 + ## Agent Configuration 229 229 230 - Agents use persona configurations stored in the `muse/` directory. Each persona is a `.md` file containing: 230 + Agents use configurations stored in the `muse/` directory. Each agent is a `.md` file containing: 231 231 - JSON frontmatter with metadata and configuration 232 232 - The agent-specific prompt and instructions in the content 233 233 234 234 When spawning an agent: 235 - 1. Cortex loads the persona configuration using `get_agent()` from `think/utils.py` 235 + 1. Cortex loads the agent configuration using `get_agent()` from `think/utils.py` 236 236 2. The configuration is built with three instruction components: 237 237 - `system_instruction`: `journal.md` (shared base prompt, cacheable) 238 238 - `extra_context`: Runtime context (facets, insights list, datetime) 239 239 - `user_instruction`: The agent's `.md` file content 240 - 3. Request parameters override persona defaults in the merged configuration 240 + 3. Request parameters override agent defaults in the merged configuration 241 241 4. The full configuration is passed to the agent process 242 242 243 - Personas define specialized behaviors, tool usage patterns, and facet expertise. Available personas can be discovered using the `get_agents()` function or by listing files in the `muse/` directory (agents are `.md` files with a `tools` field). 243 + Agents define specialized behaviors, tool usage patterns, and facet expertise. Available agents can be discovered using the `get_agents()` function or by listing files in the `muse/` directory (agents are `.md` files with a `tools` field). 244 244 245 - ### Persona Configuration Options 245 + ### Agent Configuration Options 246 246 247 - The JSON frontmatter for a persona can include: 247 + The JSON frontmatter for an agent can include: 248 248 - `max_tokens`: Maximum response token limit 249 249 - `tools`: MCP tools configuration (string or array) 250 250 - String: Comma-separated pack names (e.g., `"journal"`, `"journal, todo"`) - expanded via `get_tools()` ··· 264 264 - When true, agent runs for all non-muted facets regardless of activity 265 265 - `env`: Environment variables to set for the agent subprocess (object) 266 266 - Keys are variable names, values are coerced to strings 267 - - Request-level `env` overrides persona defaults 267 + - Request-level `env` overrides agent defaults 268 268 - Inherited by handoff agents unless explicitly overridden 269 269 - Note: `JOURNAL_PATH` cannot be overridden (always set by Cortex) 270 270 271 271 ### Model Resolution 272 272 273 273 Models are resolved automatically based on context and tier: 274 - 1. Each agent has a context pattern: `agent.{app}.{persona}` (e.g., `agent.system.default`) 274 + 1. Each agent has a context pattern: `agent.{app}.{name}` (e.g., `agent.system.default`) 275 275 2. The context determines the tier (pro/flash/lite) from `journal.json` or system defaults 276 276 3. The tier + provider determines the actual model to use 277 277
+2 -2
docs/DOCTOR.md
··· 107 107 108 108 **Event sequence** (JSONL, one event per line): 109 109 110 - 1. `request` - Initial spawn request (prompt, provider, persona) 110 + 1. `request` - Initial spawn request (prompt, provider, name) 111 111 2. `start` - Agent began execution (model info) 112 112 3. `tool_start`/`tool_end` - Tool calls (paired by `call_id`) 113 113 4. `thinking` - Model reasoning (if supported) ··· 205 205 ## See Also 206 206 207 207 - [JOURNAL.md](JOURNAL.md) - Directory structure and file formats 208 - - [CORTEX.md](CORTEX.md) - Agent system, events, personas 208 + - [CORTEX.md](CORTEX.md) - Agent system, events, configuration 209 209 - [CALLOSUM.md](CALLOSUM.md) - Message bus protocol
+3 -3
docs/JOURNAL.md
··· 649 649 Required fields: 650 650 - `timestamp` – Unix timestamp in milliseconds (13 digits) 651 651 - `model` – Model identifier (e.g., "gemini-2.5-flash", "gpt-5", "claude-sonnet-4-5") 652 - - `context` – Calling context (e.g., "agent.persona.agent_id" or "module.function:line") 652 + - `context` – Calling context (e.g., "agent.name.agent_id" or "module.function:line") 653 653 - `usage` – Token counts dictionary with normalized field names 654 654 655 655 Optional fields: ··· 680 680 Each line is a JSON object with an `event` field indicating the event type: 681 681 682 682 ```jsonl 683 - {"event": "start", "ts": 1755450767962, "persona": "helper", "prompt": "Help me with...", "facet": "work"} 683 + {"event": "start", "ts": 1755450767962, "name": "helper", "prompt": "Help me with...", "facet": "work"} 684 684 {"event": "text", "ts": 1755450768000, "content": "I'll help you with that."} 685 685 {"event": "tool_call", "ts": 1755450769000, "tool": "search", "params": {"query": "example"}} 686 686 {"event": "tool_result", "ts": 1755450770000, "tool": "search", "result": "..."} ··· 688 688 ``` 689 689 690 690 **Common event types:** 691 - - `start` – agent session started, includes persona, prompt, and facet 691 + - `start` – agent session started, includes name, prompt, and facet 692 692 - `text` – streaming text output from the agent 693 693 - `tool_call` – agent invoked an MCP tool 694 694 - `tool_result` – result returned from tool execution
+1 -1
docs/PROVIDERS.md
··· 127 127 - `mcp_server_url`: URL for MCP tool server 128 128 - `disable_mcp`: Skip MCP tool integration 129 129 - `tools`: Optional list of allowed tool names 130 - - `agent_id`, `persona`: Identity for logging and tool calls 130 + - `agent_id`, `name`: Identity for logging and tool calls 131 131 - `continue_from`: Agent ID for conversation continuation 132 132 133 133 **Event emission:**
+2 -2
docs/THINK.md
··· 95 95 # Create a request 96 96 agent_id = cortex_request( 97 97 prompt="Your task here", 98 - persona="default", 98 + name="default", 99 99 provider="openai" # or "google", "anthropic", "claude" 100 100 ) 101 101 ··· 165 165 # Create an agent request 166 166 request_file = cortex_request( 167 167 prompt="Your prompt", 168 - persona="default", 168 + name="default", 169 169 provider="openai" 170 170 ) 171 171
+3 -3
tests/integration/test_anthropic_provider.py
··· 55 55 "prompt": "what is 1+1? Just give me the number.", 56 56 "provider": "anthropic", 57 57 "model": CLAUDE_SONNET_4, 58 - "persona": "default", 58 + "name": "default", 59 59 "max_output_tokens": 100, 60 60 "disable_mcp": True, 61 61 } ··· 95 95 assert start_event["event"] == "start" 96 96 assert start_event["prompt"] == "what is 1+1? Just give me the number." 97 97 assert start_event["model"] == CLAUDE_SONNET_4 98 - assert start_event["persona"] == "default" 98 + assert start_event["name"] == "default" 99 99 assert isinstance(start_event["ts"], int) 100 100 101 101 # Check finish event ··· 158 158 { 159 159 "prompt": "What is the square root of 16? Just the number please.", 160 160 "provider": "anthropic", 161 - "persona": "default", 161 + "name": "default", 162 162 "model": CLAUDE_SONNET_4, 163 163 "max_output_tokens": 2048, 164 164 "disable_mcp": True,
+2 -2
tests/integration/test_callosum.py
··· 153 153 # Wait for client to connect 154 154 time.sleep(0.2) 155 155 156 - client.emit("cortex", "agent_start", agent_id="123", persona="analyst") 156 + client.emit("cortex", "agent_start", agent_id="123", name="analyst") 157 157 158 158 # Wait for broadcast 159 159 time.sleep(0.2) ··· 173 173 assert msg["tract"] == "cortex" 174 174 assert msg["event"] == "agent_start" 175 175 assert msg["agent_id"] == "123" 176 - assert msg["persona"] == "analyst" 176 + assert msg["name"] == "analyst" 177 177 178 178 # Cleanup 179 179 client.stop()
+5 -5
tests/integration/test_cortex.py
··· 76 76 77 77 # Create a request 78 78 agent_id = cortex_request( 79 - prompt="Test prompt", persona="default", provider="openai" 79 + prompt="Test prompt", name="default", provider="openai" 80 80 ) 81 81 82 82 time.sleep(0.2) ··· 85 85 assert len(received_messages) >= 1 86 86 request = [m for m in received_messages if m.get("event") == "request"][0] 87 87 assert request["prompt"] == "Test prompt" 88 - assert request["persona"] == "default" 88 + assert request["name"] == "default" 89 89 assert request["provider"] == "openai" 90 90 assert request["agent_id"] == agent_id 91 91 ··· 127 127 128 128 # Make a request (this will fail because no real agent, but we can verify the flow) 129 129 agent_id = cortex_request( 130 - prompt="Test end-to-end", persona="default", provider="openai" 130 + prompt="Test end-to-end", name="default", provider="openai" 131 131 ) 132 132 133 133 # Wait for at least request event ··· 165 165 "event": "request", 166 166 "ts": ts, 167 167 "prompt": "Test", 168 - "persona": "default", 168 + "name": "default", 169 169 "provider": "openai", 170 170 }, 171 171 f, ··· 206 206 # Make a request 207 207 agent_id = cortex_request( 208 208 prompt="Test error handling", 209 - persona="nonexistent_persona", # This may cause issues 209 + name="nonexistent_agent", # This may cause issues 210 210 provider="openai", 211 211 ) 212 212
+3 -3
tests/integration/test_google_provider.py
··· 54 54 { 55 55 "prompt": "what is 1+1? Just give me the number.", 56 56 "provider": "google", 57 - "persona": "default", 57 + "name": "default", 58 58 "model": GEMINI_FLASH, 59 59 "max_output_tokens": 100, 60 60 "disable_mcp": True, ··· 95 95 assert start_event["event"] == "start" 96 96 assert start_event["prompt"] == "what is 1+1? Just give me the number." 97 97 assert start_event["model"] == GEMINI_FLASH 98 - assert start_event["persona"] == "default" 98 + assert start_event["name"] == "default" 99 99 assert isinstance(start_event["ts"], int) 100 100 101 101 # Check finish event ··· 146 146 { 147 147 "prompt": "What is the square root of 16? Just the number please.", 148 148 "provider": "google", 149 - "persona": "default", 149 + "name": "default", 150 150 "model": GEMINI_PRO, # Pro model for thinking 151 151 "max_output_tokens": 2000, 152 152 "disable_mcp": True,
+4 -4
tests/integration/test_openai_provider.py
··· 54 54 { 55 55 "prompt": "what is 1+1? Just give me the number.", 56 56 "provider": "openai", 57 - "persona": "default", 57 + "name": "default", 58 58 "model": GPT_5_MINI, # Use cheap model for testing 59 59 "max_output_tokens": 100, 60 60 "disable_mcp": True, ··· 95 95 assert start_event["event"] == "start" 96 96 assert start_event["prompt"] == "what is 1+1? Just give me the number." 97 97 assert start_event["model"] == GPT_5_MINI 98 - assert start_event["persona"] == "default" 98 + assert start_event["name"] == "default" 99 99 assert isinstance(start_event["ts"], int) 100 100 101 101 # Check finish event ··· 151 151 { 152 152 "prompt": "If I have 3 apples and buy 5 more, then give away 2, how many do I have? Think through this step by step.", 153 153 "provider": "openai", 154 - "persona": "default", 154 + "name": "default", 155 155 "model": GPT_5_MINI, 156 156 "max_output_tokens": 500, 157 157 "disable_mcp": True, ··· 237 237 { 238 238 "prompt": "What project was mentioned in the context above? Just the name.", 239 239 "provider": "openai", 240 - "persona": "default", 240 + "name": "default", 241 241 "model": GPT_5_MINI, 242 242 "max_output_tokens": 50, 243 243 "disable_mcp": True,
+5 -5
tests/test_agents_ndjson.py
··· 31 31 prompt = config.get("prompt", "") 32 32 provider = config.get("provider", "") 33 33 model = config.get("model", "") 34 - persona = config.get("persona", "default") 34 + name = config.get("name", "default") 35 35 36 36 if on_event: 37 37 on_event( ··· 40 40 "prompt": prompt, 41 41 "provider": provider, 42 42 "model": model, 43 - "persona": persona, 43 + "name": name, 44 44 "ts": 1234567890, 45 45 } 46 46 ) ··· 76 76 { 77 77 "prompt": "What is 2+2?", 78 78 "provider": "openai", 79 - "persona": "default", 79 + "name": "default", 80 80 "model": GPT_5, 81 81 "max_output_tokens": 100, 82 82 "mcp_server_url": "http://localhost:5175/mcp", ··· 130 130 { 131 131 "prompt": "Third question", 132 132 "provider": "google", 133 - "persona": "technical", 133 + "name": "technical", 134 134 "mcp_server_url": "http://localhost:5175/mcp", 135 135 }, 136 136 ] ··· 163 163 assert start_events[1]["prompt"] == "Second question" 164 164 assert start_events[1]["provider"] == "anthropic" 165 165 assert start_events[2]["prompt"] == "Third question" 166 - assert start_events[2]["persona"] == "technical" 166 + assert start_events[2]["name"] == "technical" 167 167 168 168 169 169 def test_ndjson_invalid_json(mock_journal, monkeypatch, capsys):
+2 -2
tests/test_anthropic.py
··· 186 186 assert events[0]["event"] == "start" 187 187 assert isinstance(events[0]["ts"], int) 188 188 assert events[0]["prompt"] == "hello" 189 - assert events[0]["persona"] == "default" 189 + assert events[0]["name"] == "default" 190 190 assert events[0]["model"] == CLAUDE_SONNET_4 191 191 assert events[-1]["event"] == "finish" 192 192 assert isinstance(events[-1]["ts"], int) ··· 229 229 assert events[0]["event"] == "start" 230 230 assert isinstance(events[0]["ts"], int) 231 231 assert events[0]["prompt"] == "hello" 232 - assert events[0]["persona"] == "default" 232 + assert events[0]["name"] == "default" 233 233 assert events[0]["model"] == CLAUDE_SONNET_4 234 234 assert events[-1]["event"] == "finish" 235 235 assert isinstance(events[-1]["ts"], int)
+2 -2
tests/test_app_agents.py
··· 97 97 """Test get_agent loads system agents correctly.""" 98 98 config = get_agent("default") 99 99 100 - assert config["persona"] == "default" 100 + assert config["name"] == "default" 101 101 assert "system_instruction" in config 102 102 assert "user_instruction" in config 103 103 assert len(config["system_instruction"]) > 0 ··· 140 140 assert default is not None 141 141 assert default["source"] == "system" 142 142 assert "title" in default 143 - assert "persona" in default 143 + assert "name" in default 144 144 145 145 146 146 def test_get_agents_excludes_private_apps(fixture_journal, tmp_path, monkeypatch):
+20 -20
tests/test_cortex.py
··· 94 94 "ts": 123456789, 95 95 "prompt": "Test prompt", 96 96 "provider": "openai", 97 - "persona": "default", 97 + "name": "default", 98 98 "model": GPT_5, 99 99 } 100 100 ··· 119 119 assert ndjson["event"] == "request" 120 120 assert ndjson["prompt"] == "Test prompt" 121 121 assert ndjson["provider"] == "openai" 122 - assert ndjson["persona"] == "default" 122 + assert ndjson["name"] == "default" 123 123 assert ndjson["model"] == GPT_5 124 124 125 125 # Check stdin was closed ··· 157 157 "ts": 123456789, 158 158 "prompt": "Test", 159 159 "provider": "openai", 160 - "persona": "default", 160 + "name": "default", 161 161 "handoff_from": "parent123", 162 162 } 163 163 ··· 255 255 agent = AgentProcess(agent_id, mock_process, log_path) 256 256 cortex_service.running_agents[agent_id] = agent 257 257 cortex_service.agent_handoffs[agent_id] = { 258 - "persona": "matter_editor", 258 + "name": "matter_editor", 259 259 "facet": "test", 260 260 } 261 261 ··· 266 266 mock_handoff.assert_called_once_with( 267 267 agent_id, 268 268 "Create matter", 269 - {"persona": "matter_editor", "facet": "test"}, 269 + {"name": "matter_editor", "facet": "test"}, 270 270 ) 271 271 272 272 assert agent_id not in cortex_service.agent_handoffs ··· 396 396 parent_id = "parent123" 397 397 result = "Create a new matter for AI research" 398 398 handoff = { 399 - "persona": "matter_editor", 399 + "name": "matter_editor", 400 400 "provider": "anthropic", 401 401 "facet": "test", 402 402 "max_turns": 5, ··· 411 411 # Check cortex_request was called with correct parameters 412 412 mock_request.assert_called_once_with( 413 413 prompt=result, 414 - persona="matter_editor", 414 + name="matter_editor", 415 415 provider="anthropic", 416 416 handoff_from=parent_id, 417 417 config={"facet": "test", "max_turns": 5}, ··· 423 423 parent_id = "parent123" 424 424 result = "Parent result" 425 425 handoff = { 426 - "persona": "reviewer", 426 + "name": "reviewer", 427 427 "prompt": "Review this analysis", # Explicit prompt 428 428 } 429 429 ··· 431 431 cortex_service._spawn_handoff(parent_id, result, handoff) 432 432 433 433 # Check cortex_request was called with explicit prompt 434 - # Provider is None when not explicitly set - let the persona resolve its own 434 + # Provider is None when not explicitly set - let the agent resolve its own 435 435 mock_request.assert_called_once_with( 436 436 prompt="Review this analysis", # Uses explicit prompt 437 - persona="reviewer", 437 + name="reviewer", 438 438 provider=None, 439 439 handoff_from=parent_id, 440 440 config=None, ··· 476 476 # Test writing output 477 477 agent_id = "test_agent" 478 478 result = "This is the agent result content" 479 - config = {"output": "md", "persona": "my_agent"} 479 + config = {"output": "md", "name": "my_agent"} 480 480 481 481 cortex_service._write_output(agent_id, result, config) 482 482 483 - # Check file was created in insights/ with persona-derived filename 483 + # Check file was created in insights/ with name-derived filename 484 484 expected_path = mock_journal / test_date / "insights" / "my_agent.md" 485 485 assert expected_path.exists() 486 486 assert expected_path.read_text() == result ··· 496 496 # Make journal read-only to cause error 497 497 with patch("builtins.open", side_effect=PermissionError("Cannot write")): 498 498 with caplog.at_level(logging.ERROR): 499 - config = {"output": "md", "persona": "test"} 499 + config = {"output": "md", "name": "test"} 500 500 cortex_service._write_output("agent_id", "result", config) 501 501 502 502 # Check error was logged but didn't raise ··· 509 509 agent_id = "test_agent" 510 510 result = "This is the agent result content" 511 511 specified_day = "20240201" 512 - config = {"output": "md", "persona": "reporter", "day": specified_day} 512 + config = {"output": "md", "name": "reporter", "day": specified_day} 513 513 514 514 cortex_service._write_output(agent_id, result, config) 515 515 ··· 534 534 535 535 agent_id = "segment_agent" 536 536 result = "Segment analysis content" 537 - config = {"output": "md", "persona": "analyzer", "segment": "143000_600"} 537 + config = {"output": "md", "name": "analyzer", "segment": "143000_600"} 538 538 539 539 cortex_service._write_output(agent_id, result, config) 540 540 ··· 555 555 556 556 agent_id = "json_agent" 557 557 result = '{"key": "value"}' 558 - config = {"output": "json", "persona": "data_agent"} 558 + config = {"output": "json", "name": "data_agent"} 559 559 560 560 cortex_service._write_output(agent_id, result, config) 561 561 ··· 573 573 agent_id = "output_test" 574 574 active_path = mock_journal / "agents" / f"{agent_id}_active.jsonl" 575 575 576 - # Store request with output field (format only, path derived from persona) 576 + # Store request with output field (format only, path derived from name) 577 577 cortex_service.agent_requests = { 578 578 agent_id: { 579 579 "event": "request", 580 580 "prompt": "test", 581 581 "output": "md", 582 - "persona": "test_agent", 582 + "name": "test_agent", 583 583 } 584 584 } 585 585 ··· 606 606 with patch.object(cortex_service, "_has_finish_event", return_value=True): 607 607 cortex_service._monitor_stdout(agent) 608 608 609 - # Check result was written to insights/ with persona-derived filename 609 + # Check result was written to insights/ with name-derived filename 610 610 output_path = mock_journal / test_date / "insights" / "test_agent.md" 611 611 assert output_path.exists() 612 612 assert output_path.read_text() == "Test result" ··· 627 627 "event": "request", 628 628 "prompt": "test", 629 629 "output": "md", 630 - "persona": "daily_reporter", 630 + "name": "daily_reporter", 631 631 "day": specified_day, 632 632 } 633 633 }
+10 -10
tests/test_cortex_client.py
··· 86 86 # Create a request 87 87 agent_id = cortex_request( 88 88 prompt="Test prompt", 89 - persona="default", 89 + name="default", 90 90 provider="openai", 91 91 config={"model": GPT_5}, 92 92 ) ··· 99 99 assert msg["tract"] == "cortex" 100 100 assert msg["event"] == "request" 101 101 assert msg["prompt"] == "Test prompt" 102 - assert msg["persona"] == "default" 102 + assert msg["name"] == "default" 103 103 assert msg["provider"] == "openai" 104 104 assert msg["model"] == GPT_5 105 105 assert msg["agent_id"] == agent_id ··· 110 110 """Test that cortex_request returns agent_id string.""" 111 111 _ = callosum_server # Needed for side effects only 112 112 113 - agent_id = cortex_request(prompt="Test", persona="default", provider="openai") 113 + agent_id = cortex_request(prompt="Test", name="default", provider="openai") 114 114 115 115 # Verify agent_id is a string timestamp 116 116 assert isinstance(agent_id, str) ··· 124 124 125 125 cortex_request( 126 126 prompt="Continue analysis", 127 - persona="reviewer", 127 + name="reviewer", 128 128 provider="anthropic", 129 129 handoff_from="1234567890000", 130 130 ) ··· 133 133 134 134 msg = messages[0] 135 135 assert msg["handoff_from"] == "1234567890000" 136 - assert msg["persona"] == "reviewer" 136 + assert msg["name"] == "reviewer" 137 137 138 138 139 139 def test_cortex_request_unique_agent_ids(callosum_server): ··· 143 143 agent_ids = [] 144 144 for i in range(3): 145 145 agent_id = cortex_request( 146 - prompt=f"Test {i}", persona="default", provider="openai" 146 + prompt=f"Test {i}", name="default", provider="openai" 147 147 ) 148 148 agent_ids.append(agent_id) 149 149 time.sleep(0.002) ··· 200 200 "event": "request", 201 201 "ts": ts1, 202 202 "prompt": "Task 1", 203 - "persona": "default", 203 + "name": "default", 204 204 "provider": "openai", 205 205 }, 206 206 f, ··· 214 214 "event": "request", 215 215 "ts": ts2, 216 216 "prompt": "Task 2", 217 - "persona": "tester", 217 + "name": "tester", 218 218 "provider": "google", 219 219 }, 220 220 f, ··· 244 244 "event": "request", 245 245 "ts": ts1, 246 246 "prompt": "Old task", 247 - "persona": "reviewer", 247 + "name": "reviewer", 248 248 "provider": "anthropic", 249 249 }, 250 250 f, ··· 278 278 "event": "request", 279 279 "ts": ts, 280 280 "prompt": f"Task {i}", 281 - "persona": "default", 281 + "name": "default", 282 282 }, 283 283 f, 284 284 )
+16 -16
tests/test_entity_agents.py
··· 24 24 config = get_agent("entities:entities") 25 25 26 26 # Verify required fields 27 - assert config["persona"] == "entities:entities" 27 + assert config["name"] == "entities:entities" 28 28 assert "system_instruction" in config 29 29 assert "user_instruction" in config 30 30 assert len(config["system_instruction"]) > 0 ··· 44 44 config = get_agent("entities:entities_review") 45 45 46 46 # Verify required fields 47 - assert config["persona"] == "entities:entities_review" 47 + assert config["name"] == "entities:entities_review" 48 48 assert "system_instruction" in config 49 49 assert "user_instruction" in config 50 50 assert len(config["system_instruction"]) > 0 ··· 61 61 def test_entities_agent_instruction_content(fixture_journal): 62 62 """Test detection agent instruction contains expected sections.""" 63 63 config = get_agent("entities:entities") 64 - persona = config["user_instruction"] 64 + prompt = config["user_instruction"] 65 65 66 - # Check for key sections in the persona prompt 67 - assert "Core Mission" in persona 68 - assert "entity_detect" in persona 69 - assert "entity_list" in persona 70 - assert "Knowledge Graphs" in persona or "knowledge_graph" in persona 71 - assert "day-specific context" in persona.lower() 66 + # Check for key sections in the agent prompt 67 + assert "Core Mission" in prompt 68 + assert "entity_detect" in prompt 69 + assert "entity_list" in prompt 70 + assert "Knowledge Graphs" in prompt or "knowledge_graph" in prompt 71 + assert "day-specific context" in prompt.lower() 72 72 73 73 74 74 def test_entities_review_agent_instruction_content(fixture_journal): 75 75 """Test review agent instruction contains expected sections.""" 76 76 config = get_agent("entities:entities_review") 77 - persona = config["user_instruction"] 77 + prompt = config["user_instruction"] 78 78 79 - # Check for key sections in the persona prompt 80 - assert "Core Mission" in persona 81 - assert "entity_attach" in persona 82 - assert "entity_list" in persona 83 - assert "3+" in persona or "promotion" in persona.lower() 84 - assert "description" in persona.lower() 79 + # Check for key sections in the agent prompt 80 + assert "Core Mission" in prompt 81 + assert "entity_attach" in prompt 82 + assert "entity_list" in prompt 83 + assert "3+" in prompt or "promotion" in prompt.lower() 84 + assert "description" in prompt.lower() 85 85 86 86 87 87 def test_agent_context_includes_entities_by_facet(fixture_journal):
+2 -2
tests/test_formatters.py
··· 489 489 "ts": 1700000000000, 490 490 "agent_id": "test123", 491 491 "prompt": "Hello world", 492 - "persona": "default", 492 + "name": "default", 493 493 "provider": "openai", 494 494 }, 495 495 { ··· 497 497 "ts": 1700000000100, 498 498 "agent_id": "test123", 499 499 "model": "gpt-4", 500 - "persona": "default", 500 + "name": "default", 501 501 }, 502 502 {"event": "finish", "result": "Hello!", "ts": 1700000000200}, 503 503 ]
+1 -1
tests/test_google.py
··· 53 53 assert events[0]["event"] == "start" 54 54 assert isinstance(events[0]["ts"], int) 55 55 assert events[0]["prompt"] == "hello" 56 - assert events[0]["persona"] == "default" 56 + assert events[0]["name"] == "default" 57 57 assert events[0]["model"] == GEMINI_FLASH 58 58 assert events[-1]["event"] == "finish" 59 59 assert isinstance(events[-1]["ts"], int)
+4 -4
tests/test_openai.py
··· 56 56 assert events[0]["event"] == "start" 57 57 assert isinstance(events[0]["ts"], int) 58 58 assert events[0]["prompt"] == "hello" 59 - assert events[0]["persona"] == "default" 59 + assert events[0]["name"] == "default" 60 60 assert events[0]["model"] == GPT_5 61 61 assert events[-1]["event"] == "finish" 62 62 assert isinstance(events[-1]["ts"], int) ··· 145 145 "prompt": "audit headers", 146 146 "provider": "openai", 147 147 "model": GPT_5, 148 - "persona": "investigator", 148 + "name": "investigator", 149 149 "agent_id": "999", 150 150 "mcp_server_url": "http://localhost:5173/mcp", 151 151 } ··· 156 156 assert mcp_kwargs is not None 157 157 headers = mcp_kwargs["params"].get("headers", {}) 158 158 assert headers["X-Agent-Id"] == "999" 159 - assert headers["X-Agent-Persona"] == "investigator" 159 + assert headers["X-Agent-Name"] == "investigator" 160 160 161 161 162 162 def test_openai_outfile(monkeypatch, tmp_path, capsys): ··· 193 193 assert events[0]["event"] == "start" 194 194 assert isinstance(events[0]["ts"], int) 195 195 assert events[0]["prompt"] == "hello" 196 - assert events[0]["persona"] == "default" 196 + assert events[0]["name"] == "default" 197 197 assert events[0]["model"] == GPT_5 198 198 assert events[-1]["event"] == "finish" 199 199 assert isinstance(events[-1]["ts"], int)
+1 -1
think/agents.py
··· 55 55 event: Literal["start"] 56 56 ts: int 57 57 prompt: str 58 - persona: str 58 + name: str 59 59 model: str 60 60 provider: str 61 61
+25 -25
think/cortex.py
··· 316 316 f.write(json.dumps(continue_event) + "\n") 317 317 self.logger.info(f"Linked continuation: {continue_from} -> {agent_id}") 318 318 319 - # Load persona and merge with request 319 + # Load agent config and merge with request 320 320 from think.mcp import get_tools 321 321 from think.utils import get_agent 322 322 323 - persona = request.get("persona", "default") 323 + name = request.get("name", "default") 324 324 facet = request.get("facet") 325 - config = get_agent(persona, facet=facet) 325 + config = get_agent(name, facet=facet) 326 326 327 - # Merge request into config (request values override persona defaults) 328 - # Only override with non-None values from request to preserve persona defaults 327 + # Merge request into config (request values override agent defaults) 328 + # Only override with non-None values from request to preserve agent defaults 329 329 config.update({k: v for k, v in request.items() if v is not None}) 330 330 config["agent_id"] = agent_id 331 331 ··· 333 333 # Context format: agent.{app}.{name} where app="system" for system agents 334 334 from think.models import resolve_model_for_provider, resolve_provider 335 335 336 - if ":" in persona: 337 - app, name = persona.split(":", 1) 336 + if ":" in name: 337 + app, name = name.split(":", 1) 338 338 else: 339 - app, name = "system", persona 339 + app, name = "system", name 340 340 agent_context = f"agent.{app}.{name}" 341 341 342 342 # Resolve default provider and model from context 343 343 default_provider, model = resolve_provider(agent_context) 344 344 345 - # Provider can be overridden by request or persona config 345 + # Provider can be overridden by request or agent config 346 346 # Model is always resolved from context tier + final provider 347 347 provider = config.get("provider") or default_provider 348 348 ··· 552 552 from think.models import log_token_usage 553 553 554 554 model = original_request.get("model", "unknown") 555 - persona = original_request.get("persona", "unknown") 555 + name = original_request.get("name", "unknown") 556 556 557 557 # Build context in same format as model resolution: 558 558 # agent.{app}.{name} where app="system" for system agents 559 - if ":" in persona: 560 - app, name = persona.split(":", 1) 559 + if ":" in name: 560 + app, name = name.split(":", 1) 561 561 else: 562 - app, name = "system", persona 562 + app, name = "system", name 563 563 context = f"agent.{app}.{name}" 564 564 565 565 # Extract segment from config env if set ··· 728 728 def _write_output(self, agent_id: str, result: str, config: Dict[str, Any]) -> None: 729 729 """Write agent output to the appropriate location. 730 730 731 - Output path is derived from persona + output format + schedule: 732 - - Daily agents: YYYYMMDD/insights/{persona}.{ext} 733 - - Segment agents: YYYYMMDD/{segment}/{persona}.{ext} 731 + Output path is derived from name + output format + schedule: 732 + - Daily agents: YYYYMMDD/insights/{name}.{ext} 733 + - Segment agents: YYYYMMDD/{segment}/{name}.{ext} 734 734 """ 735 735 try: 736 736 from think.utils import day_path, get_output_path 737 737 738 738 output_format = config.get("output", "md") 739 - persona = config.get("persona", "default") 739 + name = config.get("name", "default") 740 740 segment = config.get("segment") # Set by dream.py for segment agents 741 741 day = config.get("day") 742 742 ··· 745 745 746 746 # Derive output path using shared utility 747 747 output_path = get_output_path( 748 - day_dir, persona, segment=segment, output_format=output_format 748 + day_dir, name, segment=segment, output_format=output_format 749 749 ) 750 750 751 751 # Ensure parent directory exists ··· 776 776 # Operate on a copy so callers keep their original config untouched. 777 777 handoff_config = copy.deepcopy(handoff) 778 778 779 - # Determine prompt/provider/persona before pruning extra keys. 779 + # Determine prompt/provider/name before pruning extra keys. 780 780 prompt = handoff_config.pop("prompt", None) or result 781 - persona = handoff_config.pop("persona", None) or "default" 781 + name = handoff_config.pop("name", None) or "default" 782 782 783 783 # Provider can be explicitly set in handoff config, otherwise let 784 - # the handoff persona resolve its own provider from context 784 + # the handoff agent resolve its own provider from context 785 785 provider = handoff_config.pop("provider", None) 786 786 787 787 # Ensure we do not propagate parent handoff metadata. ··· 802 802 # Use cortex_request to create the handoff agent 803 803 agent_id = cortex_request( 804 804 prompt=prompt, 805 - persona=persona, 805 + name=name, 806 806 provider=provider, 807 807 handoff_from=parent_id, 808 808 config=extra_config, ··· 837 837 agents.append( 838 838 { 839 839 "agent_id": agent_id, 840 - "persona": config.get("persona", "unknown"), 840 + "name": config.get("name", "unknown"), 841 841 "provider": config.get("provider", "unknown"), 842 842 "elapsed_seconds": int( 843 843 time.time() - agent_proc.start_time ··· 1169 1169 prompt = prompt[:200] + "..." 1170 1170 header_lines.append(f"**Prompt:** {prompt}\n") 1171 1171 1172 - persona = request_event.get("persona", "default") 1172 + name = request_event.get("name", "default") 1173 1173 provider = request_event.get("provider", "") 1174 1174 model = start_event.get("model", "") if start_event else "" 1175 1175 1176 - meta_parts = [f"**Persona:** {persona}"] 1176 + meta_parts = [f"**Persona:** {name}"] 1177 1177 if provider: 1178 1178 meta_parts.append(f"**Provider:** {provider}") 1179 1179 if model:
+4 -4
think/cortex_client.py
··· 21 21 22 22 def cortex_request( 23 23 prompt: str, 24 - persona: str, 24 + name: str, 25 25 provider: Optional[str] = None, 26 26 handoff_from: Optional[str] = None, 27 27 config: Optional[Dict[str, Any]] = None, ··· 30 30 31 31 Args: 32 32 prompt: The task or question for the agent 33 - persona: Agent persona - system (e.g., "default") or app-qualified (e.g., "entities:entity_assist") 33 + name: Agent name - system (e.g., "default") or app-qualified (e.g., "entities:entity_assist") 34 34 provider: AI provider - openai, google, or anthropic 35 35 handoff_from: Previous agent ID if this is a handoff request 36 36 config: Provider-specific configuration (model, max_output_tokens, thinking_budget, etc.) ··· 63 63 "agent_id": agent_id, 64 64 "prompt": prompt, 65 65 "provider": provider, 66 - "persona": persona, 66 + "name": name, 67 67 } 68 68 69 69 # Add optional fields ··· 379 379 # Extract basic info 380 380 agent_info = { 381 381 "id": agent_id, 382 - "persona": request.get("persona", "default"), 382 + "name": request.get("name", "default"), 383 383 "start": request.get("ts", 0), 384 384 "status": status, 385 385 "prompt": request.get("prompt", ""),
+14 -14
think/dream.py
··· 291 291 292 292 # Group agents by priority 293 293 priority_groups: dict[int, list[tuple[str, dict]]] = {} 294 - for persona_id, config in agents.items(): 294 + for agent_name, config in agents.items(): 295 295 if config.get("schedule") == "daily": 296 296 priority = config.get("priority", 50) 297 - priority_groups.setdefault(priority, []).append((persona_id, config)) 297 + priority_groups.setdefault(priority, []).append((agent_name, config)) 298 298 299 299 if not priority_groups: 300 300 logging.info("No scheduled daily agents found") ··· 350 350 351 351 spawned_ids = [] 352 352 353 - for persona_id, config in agents_list: 353 + for agent_name, config in agents_list: 354 354 try: 355 355 # Check if this is a multi-facet agent 356 356 if config.get("multi_facet"): ··· 360 360 # Skip inactive facets unless agent has always=true 361 361 if not always_run and facet_name not in active_facets: 362 362 logging.info( 363 - f"Skipping {persona_id} for {facet_name}: " 363 + f"Skipping {agent_name} for {facet_name}: " 364 364 f"no activity on {day_formatted}" 365 365 ) 366 366 continue 367 367 368 - logging.info(f"Spawning {persona_id} for facet: {facet_name}") 368 + logging.info(f"Spawning {agent_name} for facet: {facet_name}") 369 369 agent_id = cortex_request( 370 370 prompt=f"Processing facet '{facet_name}' for {day_formatted}: {input_summary}. Use get_facet('{facet_name}') to load context.", 371 - persona=persona_id, 371 + name=agent_name, 372 372 config={"facet": facet_name}, 373 373 ) 374 374 spawned_ids.append(agent_id) 375 375 logging.info( 376 - f"Started {persona_id} for {facet_name} (ID: {agent_id})" 376 + f"Started {agent_name} for {facet_name} (ID: {agent_id})" 377 377 ) 378 378 else: 379 379 # Regular single-instance agent 380 380 agent_id = cortex_request( 381 381 prompt=f"Running daily scheduled task for {day_formatted}: {input_summary}.", 382 - persona=persona_id, 382 + name=agent_name, 383 383 ) 384 384 spawned_ids.append(agent_id) 385 - logging.info(f"Started {persona_id} agent (ID: {agent_id})") 385 + logging.info(f"Started {agent_name} agent (ID: {agent_id})") 386 386 except Exception as e: 387 - logging.error(f"Failed to spawn {persona_id}: {e}") 387 + logging.error(f"Failed to spawn {agent_name}: {e}") 388 388 total_failed += 1 389 389 390 390 # Wait for this priority group to complete ··· 448 448 agents = get_agents() 449 449 spawned = 0 450 450 451 - for persona_id, config in agents.items(): 451 + for agent_name, config in agents.items(): 452 452 if config.get("schedule") == "segment": 453 453 try: 454 454 cortex_request( 455 455 prompt=f"Processing segment {segment} from {day}. Use available tools to analyze this specific recording window.", 456 - persona=persona_id, 456 + name=agent_name, 457 457 config={"segment": segment, "env": {"SEGMENT_KEY": segment}}, 458 458 ) 459 459 spawned += 1 460 - logging.info(f"Spawned segment agent: {persona_id}") 460 + logging.info(f"Spawned segment agent: {agent_name}") 461 461 except Exception as e: 462 - logging.error(f"Failed to spawn {persona_id}: {e}") 462 + logging.error(f"Failed to spawn {agent_name}: {e}") 463 463 464 464 return spawned 465 465
+9 -9
think/facets.py
··· 66 66 67 67 68 68 def _get_actor_info(context: Context | None = None) -> tuple[str, str | None]: 69 - """Extract actor (persona) and agent_id from meta or HTTP headers. 69 + """Extract actor (name) and agent_id from meta or HTTP headers. 70 70 71 71 Priority: meta (stdio/anthropic/google) > HTTP headers (openai) 72 72 ··· 86 86 meta_dict = { 87 87 k: v for k, v in meta.model_dump().items() if v is not None 88 88 } 89 - persona = meta_dict.get("persona") 89 + name = meta_dict.get("name") 90 90 agent_id = meta_dict.get("agent_id") 91 - if persona or agent_id: 92 - actor = persona if persona else "mcp" 91 + if name or agent_id: 92 + actor = name if name else "mcp" 93 93 return actor, agent_id 94 94 except Exception: 95 95 pass ··· 100 100 # Normalize headers to lowercase for case-insensitive lookup 101 101 headers_lower = {k.lower(): v for k, v in headers.items()} 102 102 103 - persona = headers_lower.get("x-agent-persona") 103 + name = headers_lower.get("x-agent-name") 104 104 agent_id = headers_lower.get("x-agent-id") 105 105 106 - actor = persona if persona else "mcp" 106 + actor = name if name else "mcp" 107 107 return actor, agent_id 108 108 except Exception: 109 109 # Not in HTTP context (stdio, tests) ··· 132 132 action: Action type (e.g., "todo_add", "entity_attach") 133 133 params: Dictionary of action-specific parameters 134 134 source: Origin type - "tool" for MCP agents, "app" for web UI 135 - actor: For tools: persona name. For apps: app name 135 + actor: For tools: agent name. For apps: app name 136 136 day: Day in YYYYMMDD format (defaults to today) 137 137 agent_id: Optional agent ID (only for tool actions) 138 138 """ ··· 182 182 """Log an agent-initiated action from an MCP tool. 183 183 184 184 Creates a JSONL log entry for tracking successful modifications made via 185 - MCP tools. Automatically extracts actor identity (persona) from FastMCP 185 + MCP tools. Automatically extracts actor identity (agent name) from FastMCP 186 186 context. 187 187 188 188 When facet is provided, writes to facets/{facet}/logs/{day}.jsonl. ··· 193 193 facet: Facet name where the action occurred, or None for journal-level 194 194 action: Action type (e.g., "todo_add", "entity_attach") 195 195 params: Dictionary of action-specific parameters 196 - context: Optional FastMCP context for extracting persona/agent_id 196 + context: Optional FastMCP context for extracting agent name/agent_id 197 197 day: Day in YYYYMMDD format (defaults to today) 198 198 """ 199 199 actor, agent_id = _get_actor_info(context)
+1 -1
think/models.py
··· 589 589 cached_tokens, reasoning_tokens, requests} 590 590 Response objects: Gemini GenerateContentResponse with usage_metadata attribute 591 591 context : str, optional 592 - Context string (e.g., "module.function:123" or "agent.persona.id"). 592 + Context string (e.g., "module.function:123" or "agent.name.id"). 593 593 If None, auto-detects from call stack. 594 594 segment : str, optional 595 595 Segment key (e.g., "143022_300") for attribution.
+7 -7
think/providers/anthropic.py
··· 137 137 mcp_client: Any, 138 138 callback: JSONEventCallback, 139 139 agent_id: str | None = None, 140 - persona: str | None = None, 140 + name: str | None = None, 141 141 ) -> None: 142 142 self.mcp = mcp_client 143 143 self.callback = callback 144 144 self.agent_id = agent_id 145 - self.persona = persona 145 + self.name = name 146 146 147 147 async def execute_tool(self, tool_use: ToolUseBlock) -> dict: 148 148 """Execute ``tool_use`` and return a Claude ``tool_result`` block.""" ··· 160 160 meta = {} 161 161 if self.agent_id: 162 162 meta["agent_id"] = self.agent_id 163 - if self.persona: 164 - meta["persona"] = self.persona 163 + if self.name: 164 + meta["name"] = self.name 165 165 166 166 try: 167 167 try: ··· 291 291 "thinking_budget" 292 292 ) # None = use computed default 293 293 disable_mcp = config.get("disable_mcp", False) 294 - persona = config.get("persona", "default") 294 + name = config.get("name", "default") 295 295 296 296 callback = JSONEventCallback(on_event) 297 297 ··· 306 306 { 307 307 "event": "start", 308 308 "prompt": prompt, 309 - "persona": persona, 309 + "name": name, 310 310 "model": model, 311 311 "provider": "anthropic", 312 312 } ··· 353 353 tools = await _get_mcp_tools(mcp, allowed_tools) 354 354 agent_id = config.get("agent_id") 355 355 tool_executor = ToolExecutor( 356 - mcp, callback, agent_id=agent_id, persona=persona 356 + mcp, callback, agent_id=agent_id, name=name 357 357 ) 358 358 359 359 thinking_budget, effective_max_tokens = _resolve_agent_thinking_params(
+7 -7
think/providers/google.py
··· 475 475 self, 476 476 writer: JSONEventCallback, 477 477 agent_id: str | None = None, 478 - persona: str | None = None, 478 + name: str | None = None, 479 479 ) -> None: 480 480 self.writer = writer 481 481 self._counter = 0 482 482 self.session = None 483 483 self.agent_id = agent_id 484 - self.persona = persona 484 + self.name = name 485 485 486 486 def attach(self, session: Any) -> None: 487 487 self.session = session ··· 503 503 meta = {} 504 504 if self.agent_id: 505 505 meta["agent_id"] = self.agent_id 506 - if self.persona: 507 - meta["persona"] = self.persona 506 + if self.name: 507 + meta["name"] = self.name 508 508 509 509 result = await original( 510 510 name=name, ··· 576 576 max_output_tokens = config.get("max_output_tokens", _DEFAULT_MAX_TOKENS) 577 577 thinking_budget = config.get("thinking_budget") # None = dynamic (-1) 578 578 disable_mcp = config.get("disable_mcp", False) 579 - persona = config.get("persona", "default") 579 + name = config.get("name", "default") 580 580 581 581 callback = JSONEventCallback(on_event) 582 582 ··· 590 590 { 591 591 "event": "start", 592 592 "prompt": prompt, 593 - "persona": persona, 593 + "name": name, 594 594 "model": model, 595 595 "provider": "google", 596 596 } ··· 654 654 # Attach tool logging hooks to the MCP session 655 655 agent_id = config.get("agent_id") 656 656 tool_hooks = ToolLoggingHooks( 657 - callback, agent_id=agent_id, persona=persona 657 + callback, agent_id=agent_id, name=name 658 658 ) 659 659 tool_hooks.attach(mcp.session) 660 660
+4 -4
think/providers/openai.py
··· 240 240 { 241 241 "event": "start", 242 242 "prompt": prompt, 243 - "persona": config.get("persona", "default"), 243 + "name": config.get("name", "default"), 244 244 "model": model, 245 245 "provider": "openai", 246 246 "ts": _now_ms(), ··· 274 274 ) 275 275 276 276 agent_id = str(config.get("agent_id", "")).strip() 277 - persona = str(config.get("persona", "")).strip() 277 + name = str(config.get("name", "")).strip() 278 278 mcp_params = {"url": http_uri} 279 279 headers: dict[str, str] = {} 280 280 if agent_id: 281 281 headers["X-Agent-Id"] = agent_id 282 - if persona: 283 - headers["X-Agent-Persona"] = persona 282 + if name: 283 + headers["X-Agent-Name"] = name 284 284 if headers: 285 285 mcp_params["headers"] = headers 286 286
+16 -16
think/utils.py
··· 779 779 day_dir: 780 780 Day directory path (YYYYMMDD). 781 781 key: 782 - Insight key or agent persona (e.g., "activity", "chat:sentiment", 782 + Insight key or agent name (e.g., "activity", "chat:sentiment", 783 783 "decisionalizer", "entities:observer"). 784 784 segment: 785 785 Optional segment key (HHMMSS_LEN) for segment-level output. ··· 1006 1006 return result 1007 1007 1008 1008 1009 - def _resolve_agent_path(persona: str) -> tuple[Path, str]: 1010 - """Resolve agent persona to directory path and agent name. 1009 + def _resolve_agent_path(name: str) -> tuple[Path, str]: 1010 + """Resolve agent name to directory path and agent filename. 1011 1011 1012 1012 Parameters 1013 1013 ---------- 1014 - persona: 1015 - Agent key - either system agent name (e.g., "default") or 1014 + name: 1015 + Agent name - either system agent (e.g., "default") or 1016 1016 app-namespaced agent (e.g., "chat:helper"). 1017 1017 1018 1018 Returns ··· 1020 1020 tuple[Path, str] 1021 1021 (agent_directory, agent_name) tuple. 1022 1022 """ 1023 - if ":" in persona: 1023 + if ":" in name: 1024 1024 # App agent: "chat:helper" -> apps/chat/muse/helper 1025 - app, agent_name = persona.split(":", 1) 1025 + app, agent_name = name.split(":", 1) 1026 1026 agent_dir = Path(__file__).parent.parent / "apps" / app / "muse" 1027 1027 else: 1028 1028 # System agent: "default" -> muse/default 1029 1029 agent_dir = MUSE_DIR 1030 - agent_name = persona 1030 + agent_name = name 1031 1031 return agent_dir, agent_name 1032 1032 1033 1033 ··· 1185 1185 return result 1186 1186 1187 1187 1188 - def get_agent(persona: str = "default", facet: str | None = None) -> dict: 1189 - """Return complete agent configuration for a persona. 1188 + def get_agent(name: str = "default", facet: str | None = None) -> dict: 1189 + """Return complete agent configuration by name. 1190 1190 1191 1191 Loads configuration from .md file with JSON frontmatter and instruction text, 1192 1192 merges with runtime context. 1193 1193 1194 1194 Parameters 1195 1195 ---------- 1196 - persona: 1197 - Name of the persona to load. Can be a system agent name (e.g., "default") 1196 + name: 1197 + Agent name to load. Can be a system agent (e.g., "default") 1198 1198 or an app-namespaced agent (e.g., "chat:helper" for apps/chat/muse/helper). 1199 1199 facet: 1200 1200 Optional facet name to focus on. When provided, includes detailed ··· 1208 1208 extra_context, model, backend, etc. 1209 1209 """ 1210 1210 # Resolve agent path based on namespace 1211 - agent_dir, agent_name = _resolve_agent_path(persona) 1211 + agent_dir, agent_name = _resolve_agent_path(name) 1212 1212 1213 1213 # Verify agent prompt file exists 1214 1214 md_path = agent_dir / f"{agent_name}.md" 1215 1215 if not md_path.exists(): 1216 - raise FileNotFoundError(f"Agent persona not found: {persona}") 1216 + raise FileNotFoundError(f"Agent not found: {name}") 1217 1217 1218 1218 # Load config from frontmatter 1219 1219 post = frontmatter.load( ··· 1239 1239 if instructions["extra_context"]: 1240 1240 config["extra_context"] = instructions["extra_context"] 1241 1241 1242 - # Set persona name 1243 - config["persona"] = persona 1242 + # Set agent name 1243 + config["name"] = name 1244 1244 1245 1245 return config 1246 1246