personal memory agent
0
fork

Configure Feed

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

docs: add SOLCLI.md — sol CLI developer guide

Comprehensive guide covering the two-tier CLI architecture (top-level
system commands vs sol call tool-callable functions), how to add commands
at each tier, the auto-discovery pattern for apps/*/call.py, conventions
(env defaults, action logging, --consent flag), full command inventory,
skill system integration, and file maintenance checklists.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+338
+338
docs/SOLCLI.md
··· 1 + # Sol CLI Developer Guide 2 + 3 + How the `sol` CLI is organized, how to add new commands, and what files to maintain. 4 + 5 + ## Architecture 6 + 7 + The CLI has two tiers with distinct purposes: 8 + 9 + | Tier | Pattern | Framework | Purpose | 10 + |------|---------|-----------|---------| 11 + | **Top-level** | `sol <cmd>` | Custom dispatcher + argparse | System infrastructure — pipelines, daemons, orchestration | 12 + | **Call** | `sol call <app> <cmd>` | Typer (auto-discovered) | Tool-callable functions — what agents and humans invoke for data operations | 13 + 14 + ### The boundary 15 + 16 + **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. 17 + 18 + **If it's system plumbing → `sol <cmd>`.** Processing pipelines, supervisor, services, capture — things that cron or systemd runs. 19 + 20 + **Interactive entry points** (`sol chat`, `sol help`, `sol engage`) are top-level for discoverability even though they're user-facing. Agents don't invoke these. 21 + 22 + ## Top-Level Commands (`sol <cmd>`) 23 + 24 + ### How they work 25 + 26 + `sol.py` contains a static `COMMANDS` dict mapping command names to module paths: 27 + 28 + ```python 29 + COMMANDS: dict[str, str] = { 30 + "dream": "think.dream", 31 + "import": "think.importers.cli", 32 + ... 33 + } 34 + ``` 35 + 36 + Each module must export a `main()` function. The dispatcher does `importlib.import_module(path)` then calls `module.main()`. 37 + 38 + Commands are organized into `GROUPS` for help display, and `ALIASES` provide shortcuts (e.g., `sol up` → `sol service up`). 39 + 40 + ### Adding a top-level command 41 + 42 + 1. **Create the module** with a `main()` function: 43 + 44 + ```python 45 + # think/my_cmd.py 46 + import argparse 47 + 48 + def main() -> None: 49 + parser = argparse.ArgumentParser(prog="sol my-cmd") 50 + parser.add_argument("--day", help="Day YYYYMMDD") 51 + args = parser.parse_args() 52 + # ... implementation 53 + ``` 54 + 55 + 2. **Register in `sol.py`** — add to `COMMANDS`: 56 + 57 + ```python 58 + COMMANDS: dict[str, str] = { 59 + ... 60 + "my-cmd": "think.my_cmd", 61 + } 62 + ``` 63 + 64 + 3. **Add to a group** in `GROUPS` for help display. 65 + 66 + 4. **No skill or AGENTS.md changes needed** — top-level commands aren't agent tools. 67 + 68 + ### Files to maintain 69 + 70 + | File | What to do | 71 + |------|-----------| 72 + | `sol.py` COMMANDS dict | Register the command | 73 + | `sol.py` GROUPS dict | Add to appropriate group | 74 + | Module file (e.g., `think/my_cmd.py`) | Implement with `main()` | 75 + 76 + ## Call Commands (`sol call <app> <cmd>`) 77 + 78 + ### How they work 79 + 80 + `think/call.py` is the gateway. It creates a root `typer.Typer()` and mounts sub-apps from two sources: 81 + 82 + **Auto-discovered apps** — scans `apps/*/call.py` at import time: 83 + ``` 84 + apps/todos/call.py → sol call todos ... 85 + apps/calendar/call.py → sol call calendar ... 86 + apps/entities/call.py → sol call entities ... 87 + ``` 88 + 89 + Each `call.py` must export `app = typer.Typer()`. The directory name becomes the sub-command name. Errors in one app don't prevent others from loading. 90 + 91 + **Manually mounted built-ins** — for commands tightly coupled to `think/` internals: 92 + ```python 93 + # think/call.py 94 + from think.tools.call import app as journal_app 95 + from think.tools.routines import app as routines_app 96 + from think.tools.sol import app as sol_app 97 + 98 + call_app.add_typer(journal_app, name="journal") 99 + call_app.add_typer(routines_app, name="routines") 100 + call_app.add_typer(sol_app, name="sol") 101 + ``` 102 + 103 + These live under `think/tools/` because they import think-internal APIs directly. 104 + 105 + ### Adding a new auto-discovered app 106 + 107 + This is the happy path for most new commands. 108 + 109 + 1. **Create `apps/<name>/call.py`**: 110 + 111 + ```python 112 + # apps/myapp/call.py 113 + import typer 114 + from think.facets import log_call_action 115 + 116 + app = typer.Typer(help="Short description of what this app does.") 117 + 118 + @app.command("list") 119 + def list_items( 120 + day: str | None = typer.Option(None, "--day", "-d", help="Day YYYYMMDD."), 121 + facet: str | None = typer.Option(None, "--facet", "-f", help="Facet name."), 122 + ) -> None: 123 + """List items.""" 124 + from think.utils import resolve_sol_day, resolve_sol_facet 125 + day = resolve_sol_day(day) 126 + facet = resolve_sol_facet(facet) 127 + # ... implementation 128 + ``` 129 + 130 + 2. **That's it for the CLI.** Auto-discovery picks it up on next run. 131 + 132 + 3. **Create the agent skill** (if agents should use these commands): 133 + 134 + ```markdown 135 + # apps/myapp/muse/myapp/SKILL.md 136 + --- 137 + name: myapp 138 + description: > 139 + What this skill does. When to trigger it. 140 + TRIGGER: keyword1, keyword2, keyword3. 141 + --- 142 + 143 + # MyApp CLI Skill 144 + 145 + Common pattern: 146 + \`\`\`bash 147 + sol call myapp <command> [args...] 148 + \`\`\` 149 + 150 + ## list 151 + 152 + \`\`\`bash 153 + sol call myapp list [-d DAY] [-f FACET] 154 + \`\`\` 155 + 156 + List items for a day. 157 + 158 + - `-d, --day`: day in `YYYYMMDD` (default: `SOL_DAY` env). 159 + - `-f, --facet`: facet name (default: `SOL_FACET` env). 160 + ``` 161 + 162 + 4. **Run `make skills`** to create the symlink in `.agents/skills/`. 163 + 164 + 5. **Update AGENTS.md** — add the skill to the Skills table. 165 + 166 + ### Adding a manually-mounted built-in 167 + 168 + Use this when the command depends heavily on `think/` internals and shouldn't live in `apps/`. 169 + 170 + 1. **Create `think/tools/<name>.py`** with `app = typer.Typer()`. 171 + 2. **Mount in `think/call.py`**: 172 + ```python 173 + from think.tools.mytools import app as mytools_app 174 + call_app.add_typer(mytools_app, name="mytools") 175 + ``` 176 + 3. **Optionally create a skill** in `muse/<name>/SKILL.md`. 177 + 178 + ### Files to maintain for a new call command 179 + 180 + | File | What to do | Required? | 181 + |------|-----------|-----------| 182 + | `apps/<name>/call.py` | Typer app with commands | Yes | 183 + | `apps/<name>/muse/<name>/SKILL.md` | Skill doc for agents | If agents should use it | 184 + | `.agents/skills/<name>` | Symlink (via `make skills`) | Auto-generated | 185 + | `AGENTS.md` Skills table | Add trigger description | If skill exists | 186 + | `tests/test_<name>_call.py` | CLI tests | Yes | 187 + 188 + ## Conventions 189 + 190 + ### Environment defaults 191 + 192 + Commands that take `--day` or `--facet` should respect `SOL_DAY` and `SOL_FACET` environment variables as defaults. Use the helpers: 193 + 194 + ```python 195 + from think.utils import resolve_sol_day, resolve_sol_facet 196 + 197 + day = resolve_sol_day(day_arg) # Falls back to SOL_DAY env 198 + facet = resolve_sol_facet(facet_arg) # Falls back to SOL_FACET env 199 + ``` 200 + 201 + ### Action logging 202 + 203 + All mutating `sol call` commands should log their actions for audit: 204 + 205 + ```python 206 + from think.facets import log_call_action 207 + 208 + log_call_action( 209 + facet=facet, 210 + action="myapp_create", 211 + params={"key": "value"}, 212 + day=day, 213 + ) 214 + ``` 215 + 216 + This writes to `facets/{facet}/logs/{day}.jsonl` (or `config/actions/{day}.jsonl` if facet is None). 217 + 218 + ### The `--consent` flag 219 + 220 + Commands that agents invoke proactively (without the user explicitly asking) should accept a `--consent` flag: 221 + 222 + ```python 223 + consent: bool = typer.Option( 224 + False, 225 + "--consent", 226 + help="Assert that explicit user approval was obtained before calling this command (agent audit trail).", 227 + ) 228 + ``` 229 + 230 + This is for audit trail — it records that the agent confirmed user consent before acting. 231 + 232 + ### Output patterns 233 + 234 + - **JSON output**: Use `--json` flag for machine-readable output. Default to human-friendly text. 235 + - **Errors**: Write to stderr via `typer.echo(..., err=True)` and `raise typer.Exit(1)`. 236 + - **Confirmations**: Use `--yes` to skip interactive confirmation for destructive operations. 237 + - **Pagination**: Use `--limit` / `--cursor` for list commands. 238 + 239 + ### Typer command naming 240 + 241 + ```python 242 + @app.command("list") # Verb as command name 243 + @app.command("show") # Singular operations 244 + @app.command("create") # CRUD verbs 245 + ``` 246 + 247 + Use lowercase, single-word names. Hyphenated names for multi-word (`check-nudges`, `set-name`). 248 + 249 + ## Directory Structure 250 + 251 + ``` 252 + solstone/ 253 + ├── sol.py # Entry point + COMMANDS registry 254 + ├── think/ 255 + │ ├── call.py # sol call gateway (Typer root + mounts) 256 + │ ├── tools/ 257 + │ │ ├── call.py # sol call journal (built-in) 258 + │ │ ├── routines.py # sol call routines (built-in) 259 + │ │ └── sol.py # sol call sol (built-in) 260 + │ └── *.py # Top-level command modules 261 + ├── apps/ 262 + │ ├── todos/ 263 + │ │ ├── call.py # sol call todos (auto-discovered) 264 + │ │ ├── todo.py # Data models 265 + │ │ └── muse/todos/SKILL.md # Agent skill doc 266 + │ ├── calendar/ 267 + │ │ ├── call.py # sol call calendar (auto-discovered) 268 + │ │ └── muse/calendar/SKILL.md 269 + │ ├── entities/call.py 270 + │ ├── speakers/call.py 271 + │ ├── support/call.py 272 + │ ├── transcripts/call.py 273 + │ ├── agent/call.py 274 + │ ├── awareness/call.py 275 + │ └── ... (web-only apps without call.py) 276 + ├── muse/ 277 + │ ├── journal/SKILL.md # Skills not tied to an app 278 + │ ├── coding/SKILL.md 279 + │ └── *.md # Agent prompt files 280 + ├── .agents/skills/ # Symlinks (generated by make skills) 281 + └── AGENTS.md # Sol identity + skill table 282 + ``` 283 + 284 + ### The `apps/` dual role 285 + 286 + `apps/` contains both CLI apps (with `call.py`) and convey web routes (without). The presence of `call.py` is the marker for "this app exposes CLI commands." Web-only apps (home, search, settings, stats, etc.) only serve the convey UI. 287 + 288 + ## Current Command Inventory 289 + 290 + ### Top-level (`sol <cmd>`) 291 + 292 + | Group | Commands | 293 + |-------|----------| 294 + | Think (processing) | `import`, `dream`, `planner`, `indexer`, `supervisor`, `schedule`, `top`, `health`, `callosum`, `notify`, `heartbeat` | 295 + | Service | `service` (+ aliases `up`, `down`, `start`) | 296 + | Observe (capture) | `transcribe`, `describe`, `sense`, `sync`, `transfer`, `remote` | 297 + | Muse (AI agents) | `agents`, `cortex`, `muse`, `call`, `engage` | 298 + | Convey (web UI) | `convey`, `restart-convey`, `screenshot`, `maint` | 299 + | Specialized | `config`, `streams`, `journal-stats`, `formatter`, `detect-created` | 300 + | Help | `help`, `chat` | 301 + 302 + ### Call (`sol call <app> <cmd>`) 303 + 304 + | App | Source | Commands | 305 + |-----|--------|----------| 306 + | `todos` | `apps/todos/call.py` | list, add, done, cancel, move, upcoming, check-nudges | 307 + | `calendar` | `apps/calendar/call.py` | list, add, update, cancel, move, import-day | 308 + | `entities` | `apps/entities/call.py` | list, show, search, observe, merge | 309 + | `speakers` | `apps/speakers/call.py` | list, show, detect-owner, confirm-owner, clusters, suggest | 310 + | `transcripts` | `apps/transcripts/call.py` | list, read, segments | 311 + | `support` | `apps/support/call.py` | register, search, article, create, list, show, reply, attach, feedback, announcements, diagnose | 312 + | `agent` | `apps/agent/call.py` | name, set-name, reset, thickness, set-owner, sol-init | 313 + | `awareness` | `apps/awareness/call.py` | status, onboarding, imports, log, log-read | 314 + | `journal` | `think/tools/call.py` | search, events, facets, facet (show/create/update/rename/mute/unmute/delete/merge), news, agents, read, imports, import, retention purge, storage-summary | 315 + | `routines` | `think/tools/routines.py` | list, templates, create, edit, delete, run, output, suggestions, suggest-respond, suggest-state | 316 + | `sol` | `think/tools/sol.py` | self, partner, agency, pulse, briefing | 317 + | `navigate` | `think/call.py` (inline) | *(single command)* | 318 + 319 + ## Skill System 320 + 321 + Skills are documented in `SKILL.md` files and symlinked into `.agents/skills/` by `make skills`. 322 + 323 + **Skill locations:** 324 + - App skills: `apps/<name>/muse/<name>/SKILL.md` 325 + - Core skills: `muse/<name>/SKILL.md` 326 + 327 + **Skill ≠ call command.** Not every skill has a corresponding `call.py`, and not every `call.py` has a skill: 328 + - `health`, `coding`, `vit`, `onboarding` have skills but no `call.py` 329 + - Some call apps provide the CLI while the skill provides agent behavioral context 330 + 331 + Skills document the CLI commands but also add behavioral guidance beyond what `--help` shows (e.g., "check upcoming before adding a future todo to avoid duplicates"). 332 + 333 + ### Keeping skills in sync 334 + 335 + When you add or change a `sol call` command, update the corresponding SKILL.md. The skill doc is what agents actually read — they don't parse `--help` output. Include: 336 + - Full command syntax with all flags 337 + - Behavior notes (edge cases, defaults, validation) 338 + - Examples showing common usage patterns