personal memory agent
1# Cortex API and Eventing
2
3The Cortex system manages AI talent execution through the Callosum message bus with file-based persistence. It acts as a process manager for talent instances, receiving requests via Callosum and writing execution events to both JSONL files (for persistence) and the message bus (for real-time distribution).
4
5For details on the Callosum protocol and message format, see [CALLOSUM.md](CALLOSUM.md).
6
7## Architecture
8
9### Event Flow
101. **Request Creation**: Client calls `cortex_request()` which broadcasts to Callosum (`tract="cortex"`, `event="request"`)
112. **Request Reception**: Cortex receives message via Callosum callback and creates `<name>/<timestamp>_active.jsonl`
123. **Talent Spawning**: Cortex spawns a talent process via `python -m think.talents` with merged configuration
134. **Event Emission**: Talents write JSON events to stdout (captured by Cortex)
145. **Event Distribution**: Cortex appends events to JSONL file AND broadcasts to Callosum
156. **Agent Completion**: Cortex renames file to `<name>/<timestamp>.jsonl` when agent finishes
16
17### Key Components
18- **Message Bus Integration**: Cortex connects to Callosum to receive requests and broadcast events
19- **Process Management**: Spawns talent subprocesses (both tool talents and generators)
20- **Configuration Delegation**: Passes raw requests to `python -m think.talents`, which handles all config loading, validation, and hydration
21- **Event Capture**: Monitors agent stdout/stderr and appends to JSONL files
22- **Dual Event Distribution**: Events go to both persistent files and real-time message bus
23- **NDJSON Input Mode**: Agent processes accept newline-delimited JSON via stdin containing the full merged configuration
24
25### File States
26- `<name>/<timestamp>_active.jsonl`: Talent currently executing (Cortex is appending events)
27- `<name>/<timestamp>.jsonl`: Talent completed (contains full event history)
28
29**Note**: Files provide persistence and historical record, while Callosum provides real-time event distribution to all interested services.
30
31## Request Format
32
33Requests are created via `cortex_request()` from `think.cortex_client`, which broadcasts to Callosum. The request message follows this format:
34
35```json
36{
37 "event": "request",
38 "ts": 1234567890123, // Required: millisecond timestamp (must match use_id in filename)
39 "prompt": "Analyze this code for security issues", // Required for talents (not generators)
40 "name": "default", // Optional: talent name from talent/*.md
41 "provider": "openai", // Optional: override provider (openai, google, anthropic)
42 "max_output_tokens": 8192, // Optional: maximum response tokens
43 "thinking_budget": 10000, // Optional: thinking token budget (ignored by OpenAI)
44 "session_id": "sess-abc123", // Optional: CLI session ID for continuation
45 "chat_id": "1234567890122", // Optional: chat ID for reverse lookup
46 "facet": "my-project", // Optional: project context
47 "output": "md", // Optional: output format ("md" or "json"), writes to talents/
48 "day": "20250109", // Optional: YYYYMMDD format, defaults to current day
49 "env": { // Optional: environment variables for subprocess
50 "API_KEY": "secret",
51 "DEBUG": "true"
52 }
53}
54```
55
56The model is automatically resolved based on the talent context (`talent.{source}.{name}`)
57and the configured tier in `journal.json`. Provider can optionally be overridden at
58request time, which will resolve the appropriate model for that provider at the same tier.
59
60## Generator Request Format
61
62Generators are spawned via Cortex when a request has an `output` field but no `tools` field. They produce analysis output (markdown or JSON) from clustered transcripts.
63
64```json
65{
66 "event": "request",
67 "ts": 1234567890123, // Required: millisecond timestamp
68 "name": "activity", // Required: generator name from talent/*.md
69 "day": "20250109", // Required: day in YYYYMMDD format
70 "output": "md", // Required: output format ("md" or "json")
71 "segment": "120000_300", // Optional: single segment key (HHMMSS_duration)
72 "span": ["120000_300", "120500_300"], // Optional: list of sequential segment keys
73 "output_path": "/path/to/file.md", // Optional: override output location
74 "refresh": false, // Optional: regenerate even if output exists
75 "provider": "google", // Optional: AI provider override
76 "model": "gemini-2.0-flash" // Optional: model override
77}
78```
79
80### Generator Events
81
82Generators emit the same event types as talents:
83- `start` - When generation begins
84- `finish` - On completion, with `result` containing generated content
85- `error` - On failure
86
87The `finish` event may include a `skipped` field when generation is skipped:
88- `"no_input"` - Insufficient transcript content to analyze
89- `"disabled"` - Generator is marked as disabled in frontmatter
90
91### Conversation Continuations
92
93All providers (Anthropic, OpenAI, Google) support continuing conversations via CLI
94session resumption. Include a `session_id` field in the request with the CLI session
95ID from a previous talent's finish event. The provider CLI tool resumes the conversation
96internally using its native session management (e.g., `claude --resume`, `codex exec resume`).
97
98Chats are locked to their original provider — continuations must use the same provider
99that started the conversation. The `chat_id` field enables reverse lookup from an
100talent back to its parent chat.
101
102## Agent Event Format
103
104All subsequent lines are JSON objects with `event` and millisecond `ts` fields. The `ts` field is automatically added by Cortex if not provided by the provider. Additionally, Cortex automatically adds an `use_id` field (matching the timestamp component in the filename) to all events for tracking purposes.
105
106### request
107The initial spawn request (first line of file, written by client).
108```json
109{
110 "event": "request",
111 "ts": 1234567890123,
112 "use_id": "1234567890123",
113 "prompt": "User's task or question",
114 "provider": "openai",
115 "name": "default",
116 "output": "md",
117 "day": "20250109"
118}
119```
120
121### start
122Emitted when a talent run begins.
123```json
124{
125 "event": "start",
126 "ts": 1234567890123,
127 "use_id": "1234567890123",
128 "name": "default",
129 "model": "gpt-4o",
130 "session_id": "sess-abc",
131 "chat_id": "1234567890122"
132}
133```
134
135### tool_start
136Emitted when a tool execution begins.
137```json
138{
139 "event": "tool_start",
140 "ts": 1234567890123,
141 "use_id": "1234567890123",
142 "tool": "search_journal",
143 "args": {"query": "search terms", "limit": 10},
144 "call_id": "search_journal-1"
145}
146```
147
148### tool_end
149Emitted when a tool execution completes.
150```json
151{
152 "event": "tool_end",
153 "ts": 1234567890123,
154 "use_id": "1234567890123",
155 "tool": "search_journal",
156 "args": {"query": "search terms"},
157 "result": ["result", "array", "or", "object"],
158 "call_id": "search_journal-1"
159}
160```
161
162### thinking
163Emitted when the model produces reasoning/thinking content (model-dependent, primarily o1 models).
164```json
165{
166 "event": "thinking",
167 "ts": 1234567890123,
168 "use_id": "1234567890123",
169 "summary": "Model's internal reasoning about the task...",
170 "model": "o1-mini"
171}
172```
173
174### talent_updated
175Emitted when control is handed off to a different agent (multi-agent scenarios).
176```json
177{
178 "event": "talent_updated",
179 "ts": 1234567890123,
180 "use_id": "1234567890123",
181 "agent": "SpecializedAgent"
182}
183```
184
185### finish
186Emitted when the talent run completes successfully.
187```json
188{
189 "event": "finish",
190 "ts": 1234567890123,
191 "use_id": "1234567890123",
192 "result": "Final response text to the owner"
193}
194```
195
196### error
197Emitted when an error occurs during execution.
198```json
199{
200 "event": "error",
201 "ts": 1234567890123,
202 "use_id": "1234567890123",
203 "error": "Error message",
204 "trace": "Full stack trace..."
205}
206```
207
208### info
209Emitted when non-JSON output is captured from agent stdout.
210```json
211{
212 "event": "info",
213 "ts": 1234567890123,
214 "use_id": "1234567890123",
215 "message": "Non-JSON output line from agent"
216}
217```
218
219## Tool Call Tracking
220
221Tool events use `call_id` to pair `tool_start` and `tool_end` events. This allows tracking:
222- Which tools are currently running
223- Tool execution duration
224- Tool inputs and outputs
225- Concurrent tool executions
226
227The frontend uses this to show real-time status updates as tools execute, changing from "running..." to "✓" when complete.
228
229## Agent Output
230
231When an agent completes successfully, its result can be automatically written to a file. This uses the same output path logic as generators.
232
233- Include an `output` field in the agent's frontmatter with the format ("md" or "json")
234- Output path is derived from agent name + format + schedule:
235 - Daily agents: `YYYYMMDD/talents/{name}.{ext}`
236 - Segment agents: `YYYYMMDD/{segment}/{name}.{ext}`
237- Writing occurs before completion
238- Write failures are logged but don't interrupt the agent flow
239- Commonly used for scheduled agents that generate daily reports
240
241## Talent Configuration
242
243Talents use configurations stored in the `talent/` directory. Each talent is a `.md` file containing:
244- JSON frontmatter with metadata and configuration
245- The talent-specific prompt and instructions in the content
246
247When spawning a talent:
2481. Cortex passes the raw request to `python -m think.talents` via stdin (NDJSON format)
2492. The talent process (`think/talents.py`) handles all config loading via `prepare_config()`:
250 - Loads talent configuration using `get_talent()` from `think/talent.py`
251 - Merges request parameters with talent defaults
252 - Resolves provider and model based on context
2533. The agent validates the config via `validate_config()` before execution
2544. Instructions are built with three components:
255 - `system_instruction`: `journal.md` (shared base prompt, cacheable)
256 - `extra_context`: Runtime context (facets, generators list, datetime)
257 - `user_instruction`: The agent's `.md` file content
258
259Agents define specialized behaviors and facet expertise. Available agents can be discovered using `get_talent_configs(type="cogitate")` or by listing files in the `talent/` directory.
260
261### Agent Configuration Options
262
263The JSON frontmatter for an agent can include:
264- `max_tokens`: Maximum response token limit
265- `schedule`: Scheduling configuration for automated execution
266 - `"daily"`: Run automatically at midnight each day
267- `priority`: Execution order for scheduled prompts (integer, **required** for scheduled prompts)
268 - Lower numbers run first (e.g., priority 10 runs before priority 40)
269 - See [THINK.md](THINK.md#unified-priority-execution) for priority bands
270- `multi_facet`: Boolean flag for facet-aware agents (default: false)
271 - When true, the agent is spawned once for each **active** facet (see Multi-Facet Agents section)
272 - Each instance receives a facet-specific prompt with the facet name
273 - Useful for creating per-facet reports, newsletters, or analyses
274- `always`: Override active facet detection for multi-facet agents (default: false)
275 - When true, agent runs for all non-muted facets regardless of activity
276- `env`: Environment variables to set for the agent subprocess (object)
277 - Keys are variable names, values are coerced to strings
278 - Request-level `env` overrides agent defaults
279 - Note: `SOLSTONE_JOURNAL` is inherited by Cortex from the managed wrapper / test fixture / sandbox env, and child processes inherit it through `os.environ`
280
281### Model Resolution
282
283Models are resolved automatically based on context and tier:
2841. Each talent config has a context pattern: `talent.{source}.{name}` (e.g., `talent.system.default`)
2852. The context determines the tier (pro/flash/lite) from `journal.json` or system defaults
2863. The tier + provider determines the actual model to use
287
288This allows controlling model selection via tier configuration rather than hardcoding models:
289```json
290{
291 "providers": {
292 "contexts": {
293 "talent.system.default": {"tier": 1},
294 "talent.*": {"tier": 2}
295 }
296 }
297}
298```
299
300## Agent Providers
301
302The system supports multiple AI providers, each implementing the same event interface:
303
304- **OpenAI** (`think/providers/openai.py`): GPT models with OpenAI Agents SDK
305- **Google** (`think/providers/google.py`): Gemini models with Google AI SDK
306- **Anthropic** (`think/providers/anthropic.py`): Claude models with Anthropic SDK
307
308All providers:
309- Emit JSON events to stdout (one per line)
310- Are spawned as subprocesses by Cortex
311- Use consistent event structures across providers
312- Process events are written to stdout for Cortex to capture
313
314## Scheduled Agents and Generators
315
316Both agents and generators support scheduling via `sol think`. Agents have `"schedule": "daily"` and generators have `"schedule": "segment"` or `"schedule": "daily"`.
317
318### Execution Order
319Scheduled items run in priority order (lower numbers first):
3201. Items are sorted by their `priority` field (required for all scheduled prompts)
3212. Items with the same priority run in parallel, then think waits for completion
3223. After each generator completes, incremental indexing runs for its output
323
324**Priority bands (recommended):**
325- **10-30**: Generators (content-producing prompts)
326- **40-60**: Analysis agents
327- **90+**: Late-stage agents
328- **99**: Fun/optional prompts
329
330### Multi-Facet Agents
331When an agent has `"multi_facet": true`:
3321. The agent is spawned once for each **active** facet
3332. Each instance receives a prompt including the facet name
3343. The agent should call `get_facet(facet_name)` to load facet context
3354. This enables per-facet reports, newsletters, and analyses
336
337#### Daily Multi-Facet Agents
338
339**Active Facet Detection**: By default, daily multi-facet agents only run for facets that had activity the previous day. `think/facets.py:get_active_facets()` determines activity by scanning segment-level `facets.json` files from the previous day, not facet event files. This prevents unnecessary agent runs for inactive facets.
340
341To force an agent to run for all facets regardless of activity, set `"always": true`:
342
343```json
344{
345 "title": "Facet Newsletter Generator",
346 "schedule": "daily",
347 "priority": 10,
348 "multi_facet": true
349}
350```
351
352```json
353{
354 "title": "Facet Auditor",
355 "schedule": "daily",
356 "multi_facet": true,
357 "always": true
358}
359```
360
361#### Segment Multi-Facet Agents
362
363Segment agents can also be multi-facet. Active facets are determined from the `facets.json` output written by the facets generator (priority 90) during segment processing.
364
365```json
366{
367 "title": "Facet Activity Tracker",
368 "schedule": "segment",
369 "multi_facet": true
370}
371```
372
373The facets generator outputs an array of detected facets for each segment:
374```json
375[
376 {"facet": "work", "activity": "Code review", "level": "high"},
377 {"facet": "personal", "activity": "Email check", "level": "low"}
378]
379```
380
381Multi-facet segment agents spawn once per non-muted facet in this array. Muted facets are filtered out, consistent with daily agent behavior. If no enabled facets are detected (empty array, missing file, or all facets muted), the agent simply doesn't spawn for that segment.
382
383**Note**: The `"always"` flag is not supported for segment agents since facet detection is inherent to the segment content.
384
385## Process Management
386
387The `sol supervisor` command provides process management for the Cortex ecosystem:
388- Starts and monitors the Cortex file watcher service
389- Handles process restarts on failure
390- Monitors system health indicators
391- Triggers `sol think` at midnight for daily processing (generators + agents)
392
393This is distinct from agent lifecycle management, which Cortex handles internally through file state transitions.