Files
Codeman/docs/readmymind.md
T
Codeman maintainer 94abcf29dc feat: Read My Mind phase 2, the predictor and the brain button
The feature as pitched in docs/readmymind-plan.md: pressing the header
brain button predicts the prompt you were about to type, from the case's
intent profile plus everything the session already knows.

Backend:
- readmymind-context.ts: pure budgeted context assembler (9 ranked
  sources: pending approval dialog, user goals, last assistant turn tail,
  recent prompts, tool activity, git workspace signals, away context,
  sibling sessions, rethink state; 30 KB budget, whole-section drop from
  the bottom of the ranking, trust tiers stated in the prompt)
- readmymind-collectors.ts: transcript tail reader (the live watcher
  keeps only a 500-char snippet) and git signal collection (execFile,
  2s timeout, skipped for remote-SSH cases)
- readmymind-predictor.ts: one-shot claude -p in a throwaway tmux
  session, opus by default (readMyMindModel setting), strict JSON
  contract with 1-3 suggestions (continue / verify / redirect), newline
  stripping, 90s timeout; mutable singleton so route tests can stub it
- POST /api/sessions/:id/readmymind: claude-mode only (400), one
  prediction in flight per session (409 CONFLICT), rethink body
  { steer, rejected }; ownership via findSessionOrFail

Frontend:
- readmymind-ui.js (loadorder 11.3): header brain button, marker-hidden
  until readMyMindEnabled is ON, desktop only (phone key is phase 3);
  modal with editable suggestion + rationale and Send / Insert /
  Rethink / Dismiss; suggestion text rendered via value/textContent only
  and nothing ever auto-sends
- App Settings -> Panels checkbox for readMyMindEnabled; en + zh-CN
  strings

Verified end to end against a live isolated instance: transcript
capture, a real opus prediction grounded in the stated goals, rethink
steering, the 409, and the browser modal incl. Insert leaving the text
unsubmitted on the composer. 41 new unit/route tests; full test:ci
sweep green (4680 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 23:05:11 +02:00

9.5 KiB

Read My Mind

Codeman's per-case memory of what you are trying to accomplish, and the 🧠 button that turns it into a predicted next prompt. 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. Pressing 🧠 feeds that profile and the live session signals to a one-shot model call and shows the predicted prompt for you to send, edit, or rethink. Nothing is ever sent to a session automatically. Design doc: readmymind-plan.md.

What it does

  • 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.
  • Predicts your next prompt on demand (the 🧠 header button, or POST .../readmymind for agents): the suggestion arrives in a modal with Send / Insert / Rethink / Dismiss.
  • 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

App Settings → Panels → Read My Mind (synced setting readMyMindEnabled, default OFF). It gates everything: capture, the header button, and nothing shows anywhere while it is off. The API equivalent:

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).

The 🧠 button

On a Claude session, press the brain button in the header (desktop; the phone surface is a planned keyboard-accessory key). Codeman assembles everything it already knows: your goals, your recent prompts (with your voice: length, tone, shorthand), the tail of the last assistant reply, recent tool activity, git state (branch, dirty files, pending changesets), how long you have been away and what happened meanwhile, sibling sessions in the same case, and any dialog the session is currently waiting on. A one-shot model call (opus by default, readMyMindModel to override) turns that into 1-3 suggestions; the top one lands in an editable field with its rationale.

  • Send submits it to the session (with Enter).
  • Insert drops it on the CLI composer without Enter, so you can edit it in the terminal before sending.
  • Rethink re-runs with the shown suggestion recorded as rejected.
  • Dismiss closes; nothing happens.

A prediction takes 5-90 seconds and costs real tokens; one runs per session at a time. If the session is sitting on a permission/question dialog, the suggestion is usually an answer to that dialog: that is intentional.

Security note: the prediction reads observable content (assistant output, tool logs, git output) which a hostile repo could try to steer. The predictor is told user-stated intent outranks anything observed, and, more importantly, a suggestion is only ever proposed: your click is the boundary. No auto-send path exists, including for agents.

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 beyond the model call you explicitly trigger, 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

Four endpoints, session-scoped so ownership is enforced by the session itself (/api/v1/ aliases work too; full spec in api-reference.md):

# 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

# Predict the next prompt (claude-mode only; takes 5-90 s)
curl -sk -X POST https://localhost:3000/api/sessions/$SID/readmymind \
  -H 'Content-Type: application/json' -d '{}' | jq '.data.suggestions'

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. Predict answers { suggestions: [{ prompt, why, kind }], durationMs } (kind: continue / verify / redirect), 409 CONFLICT while one is already running, 400 INVALID_INPUT on non-claude sessions, and 502 OPERATION_FAILED when the model produced no usable JSON. The rethink flow passes {"steer":"…","rejected":["…"]}.

For agents (the skill)

The codeman agent skill documents the same 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), never delete a profile unprompted, and never send a predicted suggestion into a session unless the user asked. It is the user's memory, not the agent's.

What comes next (phase 3+)

Phone keyboard-accessory 🧠 key, a steer-note input on Rethink, and tappable alternate suggestions. Explicitly later: proactive predict-on-idle, auto-compaction of the prompt history into goals, non-Claude capture. See the phases section of readmymind-plan.md.

Troubleshooting

Symptom Cause / fix
No 🧠 button in the header readMyMindEnabled is OFF (App Settings → Panels), you are on a phone (desktop-only in this phase), or the active session is not claude-mode
Prediction feels generic The profile is thin: record goals (PUT or ask your agent to), and let capture accumulate a few real prompts first
"A prediction is already running" (409) One per session at a time; wait for the current one (up to 90 s)
Prediction fails (502) The model returned no usable JSON, or the CLI could not start; retry. Check readMyMindModel if you overrode it
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), context assembly in src/readmymind-context.ts (pure) + src/readmymind-collectors.ts (transcript tail + git IO), the predictor in src/readmymind-predictor.ts, routes in src/web/routes/readmymind-routes.ts, schemas in src/web/schemas.ts, frontend in src/web/public/readmymind-ui.js. Tests: test/intent-store.test.ts, test/readmymind-context.test.ts, test/readmymind-collectors.test.ts, test/readmymind-predictor.test.ts, test/routes/readmymind-routes.test.ts, and the capture cases in test/transcript-watcher.test.ts.