mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 08:59:40 +02:00
docs: Read My Mind user guide (enable, capture rules, privacy, API, troubleshooting)
docs/readmymind.md covers phase 1 as a user guide: how to enable the synced readMyMindEnabled setting via the API (no UI checkbox until phase 2), exactly what is and is not captured, the hooks dependency (Docker bridge / remote-SSH caveats), storage and wipe paths, curl examples for the three endpoints, the agent-skill ground rules, and a troubleshooting table. Cross-linked from the CLAUDE.md Key Patterns entry and the api-reference section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -210,7 +210,7 @@ Codeman is a Claude Code session manager with web interface and autonomous Ralph
|
|||||||
|
|
||||||
**Approvals Inbox** (cross-session queue of prompts waiting on a human; `approvalsInboxEnabled`, SYNCED, default OFF: every surface is opt-in; only the store and answer endpoints run regardless, so flipping it ON shows anything already pending): `web/approval-inbox.ts` is a `sessionWaits`-style singleton fed by `/api/hook-event`, holding at most ONE item per session (a new prompt supersedes), claude-mode only, in-memory. Cards are answered via `POST /api/approvals/:id/answer`, which sends a digit / Esc / idle-prompt text through `writeViaMux` (menu answers never carry `\r`). ⚠️ `option` digits are accepted ONLY when they match options parsed from the captured pane frame, and the answer path RE-CAPTURES the pane first (a dialog that no longer parses on screen means the keystroke would land in the composer, so refuse with 409). ⚠️ Resolution on the heuristic `working` signal is restricted to `idle` items; permission/question items clear only on definitive signals (`stop`, `elicitation_complete`/`elicitation_response`, exit/delete, answer, supersede, 12h TTL). The frontend seeds from `GET /api/approvals` in `handleInit` (which is what makes tab alerts survive reloads), but only with the setting ON; push Approve/Deny buttons are also gated on it (`sendPushNotifications` strips `actions`/`approvalId` when OFF) and are answered from `sw.js` directly so they work with no tab open. Surfaces (all gated on the setting): header bell (marker-hidden until count > 0, phones never show it) + drawer (`approvals-ui.js`), phone overview NEEDS YOU answer strips (`mobile-overview.js`). Design: `docs/approvals-inbox-plan.md`.
|
**Approvals Inbox** (cross-session queue of prompts waiting on a human; `approvalsInboxEnabled`, SYNCED, default OFF: every surface is opt-in; only the store and answer endpoints run regardless, so flipping it ON shows anything already pending): `web/approval-inbox.ts` is a `sessionWaits`-style singleton fed by `/api/hook-event`, holding at most ONE item per session (a new prompt supersedes), claude-mode only, in-memory. Cards are answered via `POST /api/approvals/:id/answer`, which sends a digit / Esc / idle-prompt text through `writeViaMux` (menu answers never carry `\r`). ⚠️ `option` digits are accepted ONLY when they match options parsed from the captured pane frame, and the answer path RE-CAPTURES the pane first (a dialog that no longer parses on screen means the keystroke would land in the composer, so refuse with 409). ⚠️ Resolution on the heuristic `working` signal is restricted to `idle` items; permission/question items clear only on definitive signals (`stop`, `elicitation_complete`/`elicitation_response`, exit/delete, answer, supersede, 12h TTL). The frontend seeds from `GET /api/approvals` in `handleInit` (which is what makes tab alerts survive reloads), but only with the setting ON; push Approve/Deny buttons are also gated on it (`sendPushNotifications` strips `actions`/`approvalId` when OFF) and are answered from `sw.js` directly so they work with no tab open. Surfaces (all gated on the setting): header bell (marker-hidden until count > 0, phones never show it) + drawer (`approvals-ui.js`), phone overview NEEDS YOU answer strips (`mobile-overview.js`). Design: `docs/approvals-inbox-plan.md`.
|
||||||
|
|
||||||
**Read My Mind intent profiles** (phase 1 of `docs/readmymind-plan.md`; `readMyMindEnabled`, SYNCED, default OFF): per-CASE profiles (user-stated `goals` + the user's recent real prompts), keyed by owner + realpath(workingDir) so they survive `/clear`/respawns and multi-user scoping is structural. Capture rides the transcript (`transcript:user_prompt` from `transcript-watcher.ts`), NOT the input paths: `POST /input` sees only programmatic prompts and the WS channel is raw keystrokes. The listener lives inside `startTranscriptWatcher()`'s `if (!watcher)` block (outside it would duplicate per hook event) and is claude-only + gated on the setting per event. Store: `src/intent-store.ts` singleton, `intents.json` written 0600 tmp+rename (prompts can contain secrets; never fed to `/api/search`). Endpoints: GET/PUT/DELETE `/api/sessions/:id/intent` (`readmymind-routes.ts`, ownership via `findSessionOrFail` WITH `req`). The predictor/button are phase 2; nothing auto-sends, ever.
|
**Read My Mind intent profiles** (phase 1 of `docs/readmymind-plan.md`; `readMyMindEnabled`, SYNCED, default OFF): per-CASE profiles (user-stated `goals` + the user's recent real prompts), keyed by owner + realpath(workingDir) so they survive `/clear`/respawns and multi-user scoping is structural. Capture rides the transcript (`transcript:user_prompt` from `transcript-watcher.ts`), NOT the input paths: `POST /input` sees only programmatic prompts and the WS channel is raw keystrokes. The listener lives inside `startTranscriptWatcher()`'s `if (!watcher)` block (outside it would duplicate per hook event) and is claude-only + gated on the setting per event. Store: `src/intent-store.ts` singleton, `intents.json` written 0600 tmp+rename (prompts can contain secrets; never fed to `/api/search`). Endpoints: GET/PUT/DELETE `/api/sessions/:id/intent` (`readmymind-routes.ts`, ownership via `findSessionOrFail` WITH `req`). The predictor/button are phase 2; nothing auto-sends, ever. User guide: `docs/readmymind.md`.
|
||||||
|
|
||||||
**Agent Teams**: `TeamWatcher` polls `~/.claude/teams/`, matches to sessions via `leadSessionId`. Teammates are in-process threads appearing as subagents. Enable: `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`. See `docs/agent-teams/`.
|
**Agent Teams**: `TeamWatcher` polls `~/.claude/teams/`, matches to sessions via `leadSessionId`. Teammates are in-process threads appearing as subagents. Enable: `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`. See `docs/agent-teams/`.
|
||||||
|
|
||||||
|
|||||||
@@ -441,7 +441,8 @@ goals plus the user's recently submitted prompts, captured from the Claude
|
|||||||
session transcript while the opt-in `readMyMindEnabled` setting is on (default
|
session transcript while the opt-in `readMyMindEnabled` setting is on (default
|
||||||
OFF). Keyed by owner + workingDir, so the profile survives `/clear`, respawns,
|
OFF). Keyed by owner + workingDir, so the profile survives `/clear`, respawns,
|
||||||
and session churn. Stored in `~/.codeman/intents.json` (mode 0600); never fed
|
and session churn. Stored in `~/.codeman/intents.json` (mode 0600); never fed
|
||||||
into `/api/v1/search`. Design: [`readmymind-plan.md`](readmymind-plan.md).
|
into `/api/v1/search`. Design: [`readmymind-plan.md`](readmymind-plan.md);
|
||||||
|
user guide: [`readmymind.md`](readmymind.md).
|
||||||
|
|
||||||
- `GET /api/v1/sessions/:id/intent` -> `{ intent: IntentProfile }` for the
|
- `GET /api/v1/sessions/:id/intent` -> `{ intent: IntentProfile }` for the
|
||||||
session's case. `IntentProfile`: `{ key, workingDir, updatedAt, goals,
|
session's case. `IntentProfile`: `{ key, workingDir, updatedAt, goals,
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# Read My Mind
|
||||||
|
|
||||||
|
Codeman's per-case memory of what you are trying to accomplish. Each case gets an **intent profile**: a freeform `goals` text (written by you or your agent) plus the prompts you actually submitted, captured automatically while the feature is on. Phase 1 (this document) ships the profile itself, its API, and the agent-skill verbs. Phase 2 adds the 🧠 button that turns the profile into a predicted next prompt you can accept, edit, or rethink; the design for that lives in [`readmymind-plan.md`](readmymind-plan.md). Nothing is ever sent to a session automatically, in any phase.
|
||||||
|
|
||||||
|
## What it does today (phase 1)
|
||||||
|
|
||||||
|
- Captures the prompts you submit in Claude sessions into a per-case history (50 most recent, bounded).
|
||||||
|
- Lets you (or your agent) record explicit goals per case.
|
||||||
|
- Exposes the profile over the HTTP API, and to agents through the `codeman` skill, so an agent can ground its work in what you actually want instead of guessing from the last screenful.
|
||||||
|
|
||||||
|
## Turning it on
|
||||||
|
|
||||||
|
The synced setting `readMyMindEnabled` (default **OFF**) gates capture. There is no App Settings checkbox yet (that arrives with the phase-2 UI), so flip it over the API:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sk -X PUT https://localhost:3000/api/settings \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"readMyMindEnabled": true}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Add `-u user:password` if your install has `CODEMAN_PASSWORD` set, and drop `-k`/use `http://` for a plain-HTTP dev server. Turning it OFF stops capture immediately; existing profiles stay until you delete them (below).
|
||||||
|
|
||||||
|
## What gets captured, exactly
|
||||||
|
|
||||||
|
Capture reads the Claude session transcript, not your keystrokes: when a user turn lands in the transcript, its text is folded into the case's profile. Filters applied on the way in:
|
||||||
|
|
||||||
|
- **Claude-mode sessions only.** Shell, OpenCode, Codex, Gemini, and Antigravity sessions are never captured (they have no transcript watcher).
|
||||||
|
- Tool results, local slash-command echo (`/model` and friends), system wrappers, and interrupt markers are skipped.
|
||||||
|
- Entries shorter than 3 characters are skipped (menu digits, Esc artifacts).
|
||||||
|
- Consecutive duplicates collapse (auto-resume's "continue" spam counts once per run).
|
||||||
|
- Each prompt is stored as one line, truncated to 500 characters; the history caps at 50 prompts FIFO.
|
||||||
|
|
||||||
|
Because the transcript path arrives via Claude Code hooks, capture needs hooks to reach the server, the same condition as hook-based idle detection. Docker cases against a loopback-only server need `CODEMAN_DOCKER_BRIDGE_HOOKS=1`; remote-SSH cases do not capture.
|
||||||
|
|
||||||
|
## What is never captured
|
||||||
|
|
||||||
|
- Anything while `readMyMindEnabled` is OFF (capture is not retroactive).
|
||||||
|
- Terminal output, keystrokes, passwords typed into shells: only submitted Claude prompts are read.
|
||||||
|
- Nothing leaves the machine, and profiles are never fed into `/api/search`.
|
||||||
|
|
||||||
|
## Where it lives, and how to wipe it
|
||||||
|
|
||||||
|
Profiles live in `~/.codeman/intents.json`, written atomically at mode 0600 (captured prompts can contain secrets). The file is per Codeman instance. Keys derive from owner + the case's resolved working directory, so profiles survive `/clear`, respawn cycles, and session churn, and in multi-user mode two owners of the same directory get separate profiles.
|
||||||
|
|
||||||
|
Forget one case: `DELETE /api/sessions/:id/intent` (below). Forget everything: stop the server and delete `~/.codeman/intents.json`.
|
||||||
|
|
||||||
|
## The API
|
||||||
|
|
||||||
|
Three endpoints, session-scoped so ownership is enforced by the session itself (`/api/v1/` aliases work too; full spec in [`api-reference.md`](api-reference.md)):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Read the profile for a session's case
|
||||||
|
curl -sk https://localhost:3000/api/sessions/$SID/intent | jq '.data.intent'
|
||||||
|
|
||||||
|
# Record goals (REPLACES the text: read + merge if you want to append)
|
||||||
|
curl -sk -X PUT https://localhost:3000/api/sessions/$SID/intent \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"goals":"ship 1.17; then mobile polish"}'
|
||||||
|
|
||||||
|
# Forget the case
|
||||||
|
curl -sk -X DELETE https://localhost:3000/api/sessions/$SID/intent
|
||||||
|
```
|
||||||
|
|
||||||
|
A case with nothing recorded answers an empty profile with `updatedAt: 0`; reads never persist anything. Goals cap at 8192 characters and the schema is strict, so unknown fields or over-long goals answer `400 INVALID_INPUT`. A session you do not own answers `404 NOT_FOUND`, indistinguishable from a nonexistent one.
|
||||||
|
|
||||||
|
## For agents (the skill)
|
||||||
|
|
||||||
|
The `codeman` agent skill documents the same three verbs (SKILL.md §3 plus `reference/endpoints.md`), with the ground rules: read the profile to understand what the user wants, record goals the user actually stated, merge instead of blind-writing (PUT replaces), and never delete a profile unprompted. It is the user's memory, not the agent's.
|
||||||
|
|
||||||
|
## What phase 2 adds
|
||||||
|
|
||||||
|
The 🧠 button and the predictor: a context assembler feeds the profile, the last assistant turn, tool activity, git state, away context, and any pending approval dialog to a one-shot opus call, and the suggested next prompt appears in an approval dialog (Send / Insert to edit / Rethink with a steer note / Dismiss). See [`readmymind-plan.md`](readmymind-plan.md) for the full design, including the trust-tier rules that keep terminal output from steering suggestions.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Symptom | Cause / fix |
|
||||||
|
| ------- | ----------- |
|
||||||
|
| Profile stays empty although I am prompting | `readMyMindEnabled` was OFF at the time (capture is not retroactive), the session is not claude-mode, or hooks are not reaching the server (Docker case on a loopback bind without `CODEMAN_DOCKER_BRIDGE_HOOKS=1`, or a remote-SSH case) |
|
||||||
|
| Short answers I typed are missing | Entries under 3 characters are filtered by design (menu digits, Esc artifacts) |
|
||||||
|
| My goals text vanished after an agent wrote to it | PUT replaces the whole text; the skill tells agents to read + merge, but a blind write wins. Re-state the goals; consider phrasing them in the session so capture keeps the evidence |
|
||||||
|
| Two profiles for what I think is one case | Different owners in multi-user mode, or genuinely different directories; paths are realpath-resolved, so symlink spellings converge but distinct checkouts do not |
|
||||||
|
| `400 INVALID_INPUT` on PUT | Goals over 8192 chars, or an extra field in the body (strict schema) |
|
||||||
|
|
||||||
|
## Where the code lives
|
||||||
|
|
||||||
|
`src/intent-store.ts` (store + pure helpers, singleton), the `transcript:user_prompt` event in `src/transcript-watcher.ts`, capture wiring in `src/web/server.ts` (`captureIntentPrompt`), routes in `src/web/routes/readmymind-routes.ts`, schema in `src/web/schemas.ts`. Tests: `test/intent-store.test.ts`, `test/routes/readmymind-routes.test.ts`, and the capture cases in `test/transcript-watcher.test.ts`.
|
||||||
Reference in New Issue
Block a user