You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 31a69cf
Browse filesBrowse the repository at this point in the historyBrowse files
runner: repo memory (notes + profile), read before a run and written after
- MemoryStore with an in-memory stub (tests) and a file store under ~/.codehands/memory
- detectRepoProfile(): language, package manager, test command, Node version, needsInstall. Read from the worktree, no model call
- start() saves the profile and passes the notes to Claude as --append-system-prompt (capped at 6,000 chars)
- A finished run adds a one-line-summary note before approval.requested. Stopped and failed runs add nothing
- Memory errors never fail a run
- Tests: 9 profile, 13 memory, 4 runner memory (58 total). README updated, including approval.requested
R-->>D: task.finished with diffStat (status: awaiting_approval)
45
+
R->>R: add a note about this run
46
+
R-->>D: task.finished with diffStat, then approval.requested (status: awaiting_approval)
45
47
D->>R: diff(sessionId)
46
48
R-->>D: { patch, stat }
47
49
alt approve
@@ -62,7 +64,7 @@ When it ends:
62
64
63
65
## Status
64
66
65
-
Week 1 (Oct 19 to 23, 2026).
67
+
Week 1 (Oct 19 to 23, 2026), plus the first part of week 2.
66
68
67
69
| Piece | File | State |
68
70
| --- | --- | --- |
@@ -76,7 +78,9 @@ Week 1 (Oct 19 to 23, 2026).
76
78
|`stop`: kill the whole process tree, keep the worktree |[runner/src/process/kill-tree.ts](runner/src/process/kill-tree.ts)| Done. `task.stopped` comes out of the `start()` stream; `stop()` resolves once the session is `stopped`|
77
79
|`resume`: same worktree, `seq` continues |[runner/src/runner.ts](runner/src/runner.ts)| Done. Runs `claude -p --resume <sessionId>` through the same `runAgent()` as `start()`, so it ends in `awaiting_approval` or `task.failed`|
78
80
| Codex: `codex exec --json` to events |[runner/src/agents/codex.ts](runner/src/agents/codex.ts)| To do. `start()` with `agent: "codex"` fails cleanly for now |
79
-
| Repo notes read before a run, summary appended after |[runner/src/memory/notes.ts](runner/src/memory/notes.ts)| To do. `repoKey()` is done |
81
+
|`approval.requested` after `task.finished`|[runner/src/runner.ts](runner/src/runner.ts)| Done (contracts v0.2.0). Real and fake runners, never after a stop or a failure |
82
+
| Repo memory: notes read before a run, a note added after |[runner/src/memory/](runner/src/memory/)| Done. `MemoryStore` with an in-memory stub and a file store. Stopped and failed runs add nothing, and a memory error never fails a run |
83
+
| Repo profile: language, package manager, test command, Node version |[runner/src/memory/profile.ts](runner/src/memory/profile.ts)| Done. Read from files in the worktree, no model call. The sandbox reads it next |
80
84
81
85
Next, from the [design](https://github.com/devweekends/codehands-docs#who-owns-what): a model per phase, naming provider errors as one `provider.status` event (`rate_limited`, `auth_expired`, `usage_limit`, `subscription_inactive`, `down`), memory and user preferences, and two models on one task.
82
86
@@ -109,11 +113,15 @@ const { branch, commit } = await runner.approve(sessionId, "Fix the date bug");
109
113
110
114
| Export | What |
111
115
| --- | --- |
112
-
|`createRunner(options?)`| The real runner. `worktreeRoot` overrides `~/.codehands/worktrees`. `agentCommand` swaps the program it runs, which is how the tests replay a recording instead of calling Claude |
113
-
|`createFakeRunner({ delayMs? })`| Emits 12 scripted events, 300 ms apart, and supports every method including stop and resume. Costs nothing, needs no repo. Build against it, and keep it as the demo backup |
116
+
|`createRunner(options?)`| The real runner. `worktreeRoot` overrides `~/.codehands/worktrees`. `memory` swaps where repo memory lives (default: files under `~/.codehands/memory`). `agentCommand` swaps the program it runs, which is how the tests replay a recording instead of calling Claude |
117
+
|`createFakeRunner({ delayMs? })`| Emits 12 scripted events, 300 ms apart, then `approval.requested`, and supports every method including stop and resume. Costs nothing, needs no repo. Build against it, and keep it as the demo backup |
114
118
|`EventSequence`| Hands out events with the next `seq`|
115
119
|`SessionStore`, `SessionRecord`| In-memory session records with the legal-move check from the contracts |
|`MemoryStore`, `createMemoryStub()`, `createFileMemory(root?)`| Repo memory: `notes`, `addNote`, `profile`, `saveProfile`. The stub keeps it in a `Map` for tests |
122
+
|`formatNote`, `runNote`, `trimNotes`, `notesForPrompt`| How a note is written, built from a run, trimmed to about 200 lines, and capped at 6,000 characters for the prompt |
123
+
|`detectRepoProfile(dir)`, `RepoProfile`| What a repo is, read from its files. Never throws: an unknown repo gives `language: "unknown"`|
124
+
|`repoKey(repoPath)`| The repo's id, which names its worktree and memory folders |
117
125
118
126
The runner keeps session records in memory only. The daemon owns the real database. A runner that restarts forgets its sessions; stop, resume and crash recovery are where that changes.
119
127
@@ -147,11 +155,14 @@ Some details that matter:
147
155
| Path | What |
148
156
| --- | --- |
149
157
|`~/.codehands/worktrees/<repoKey>/<first 8 of sessionId>`| The task's worktree. Outside your repo so worktrees never nest, and short because of Windows path limits |
|`~/.codehands/memory/<repoKey>/notes.md`| Per-repo notes, newest last, trimmed to about 200 lines by dropping the oldest whole notes |
159
+
|`~/.codehands/memory/<repoKey>/profile.json`| The repo profile from the latest run |
151
160
|`codehands/<slug>-<first 8 of sessionId>`| The task's branch, in your repo |
152
161
153
162
`repoKey` is the first 12 characters of the SHA-1 of the repo's `origin` URL, or of its absolute path when there is no origin. Two clones of the same repo share a key.
154
163
164
+
All of it stays on this machine. Memory reaches Claude only as `--append-system-prompt` on the same laptop. Nothing is synced or uploaded yet.
165
+
155
166
## Safety rules
156
167
157
168
The agent runs on your machine with no sandbox until the Docker box lands in week 2. Until then:
@@ -211,8 +222,10 @@ The scripts in `runner/scripts/`:
211
222
| File | Covers |
212
223
| --- | --- |
213
224
|[claude-parser.test.ts](runner/test/claude-parser.test.ts)| The whole recorded run replays in order; each mapping above; blocked commands; failed tests hidden behind exit code 0; error results; broken lines; `raw` on every event |
214
-
|[runner.test.ts](runner/test/runner.test.ts)| A worktree on a new branch with your repo untouched; clean failures for a repo with no commits, an agent with no result line, a missing binary and an unsupported agent; diff with new files; approve commits on the branch and leaves `main` alone; reject deletes the branch; nothing to approve; stop kills the agent and its children, keeps the worktree and refuses a session that is not running; resume continues the numbering, passes the prompt, can be stopped again and refuses a session that is not stopped |
215
-
|[fake-runner.test.ts](runner/test/fake-runner.test.ts)| Event order and `seq`; approve only after finishing; stop then resume with `seq` continuing |
225
+
|[runner.test.ts](runner/test/runner.test.ts)| A worktree on a new branch with your repo untouched; clean failures for a repo with no commits, an agent with no result line, a missing binary and an unsupported agent; diff with new files; approve commits on the branch and leaves `main` alone; reject deletes the branch; nothing to approve; stop kills the agent and its children, keeps the worktree and refuses a session that is not running; resume continues the numbering, passes the prompt, can be stopped again and refuses a session that is not stopped; `approval.requested` right after `task.finished`; memory: the second run gets the first run's note, the profile is saved, and failed or stopped runs add no note |
226
+
|[fake-runner.test.ts](runner/test/fake-runner.test.ts)| Event order and `seq`; approve only after finishing; stop then resume with `seq` continuing; no `approval.requested` after a stop |
227
+
|[memory.test.ts](runner/test/memory.test.ts)| The stub keeps notes in order and repos apart; trimming drops whole notes and keeps the newest; the prompt cap keeps the newest end; a note's summary stays on one line; the file store reads back from disk and treats missing files as empty |
To record a new fixture, run `scripts/spike.sh` (or a Codex equivalent), read every line of the output, then write tests that replay it. Never call a real agent from a test.
0 commit comments