Skip to content

Commit 2f970e4

Browse files
committed
문서: 가지 계층을 공개 표면 전체에 정합시킨다
릴리즈 전에 문서가 실물과 같은 말을 하게 한다. api.md의 history 절에 attempts(경쟁 동사, 직렬인 이유 포함)와 내구 가지 일습(branch/branches/recoverBranch/adopt/deleteBranch, note provenance 4KB 상한, 마커 v2 전방 호환, milestones)을 0.0.14 표식과 함께 적고, KernelElection 절에 선출 파이프라인 상속과 recoverBranch 의도적 부재를 적고, 비용 영수증에 ref/commit과 adopt 결과를 더했다. README 두 판의 Quick start에 "가지와 채택" 절이 실린다: 내구 머신의 branch/adopt/이정표 예제와 merge가 없는 이유(힙 상태는 병합이 성립하지 않는다). 에이전트 패턴 2를 "후보 분기" 에서 "경쟁·검증·채택"으로 올렸다 - attempts로 경주, 파이썬 단정으로 채점, adopt로 채택, note로 해결 경로가 1급 역사가 되는 흐름과 Machine 데모의 라이브 경주를 가리킨다. 검증: npm test 3375 passed 0 failed(api 앵커·README 표면·목차·북극성 렌더 게이트 포함).
1 parent c6bdfe4 commit 2f970e4

3 files changed

Lines changed: 97 additions & 12 deletions

File tree

README.ko.md

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,27 @@ console.log(machine.run("len(values)")); // 3
195195
전체 재해시 경로로 복원한다 - 느려질 뿐 조용히 오염되지 않는다. 라이브 프록시 핸들로
196196
파이썬을 호출했다면 `machine.markDirty()`로 신고한다.
197197

198+
가지와 채택. 내구 Machine은 실행 상태에 대해 git의 동사를 말한다: 경쟁하는 상태를 이름 있는
199+
가지로 커밋하고, 왜인지를 커밋 자체에 사는 provenance note로 남기고, 승자를 채택한다. 힙
200+
상태는 병합이 성립하지 않으므로 소비 동사는 merge가 아니라 `adopt`다:
201+
202+
```js
203+
const m = await open({ name: "lab", milestones: { keep: 7 } });
204+
await m.run("model = trainStep({})");
205+
await m.branch("adamRun", { note: { attempt: "adam", lr: 0.001 } });
206+
await m.run("model = trainStep({'optimizer': 'sgd'})");
207+
await m.branch("sgdRun", { note: { attempt: "sgd" } });
208+
await m.adopt("adamRun", { note: { reason: "validation passed" } }); // 그 상태가 이제 HEAD다
209+
await m.adopt("auto-2026-08-05"); // 또는 어제로 돌아간다
210+
```
211+
212+
`branches()`가 모든 가지를 갈림점과 note와 함께 나열하므로, 결정 전체 - 무엇을 시도했고
213+
무엇이 판정했고 무엇이 이겼나 - 가 역사로 되읽힌다. `milestones: { keep }`이면 날마다 그날의
214+
마지막 커밋을 가리키는 `auto-<날짜>` 가지가 추가 상태 비용 없이 생긴다. 휘발 머신에서는
215+
`machine.history.attempts([codes])`가 한 기반에서 후보들을 경주시키고 사이마다 힙을 되감아
216+
실패한 후보가 다음을 오염시키지 못한다. [Machine 데모](examples/machine.html)가 그 경주를
217+
라이브로 돌린다.
218+
198219
> 위 기본은 Chromium 브라우저만 있으면 된다. `PyProc`(프로세스 OS)와 소켓은 `crossOriginIsolated`(`COOP: same-origin`, `COEP: require-corp`)와 same-origin 워커도 필요하다 - [셋업](#셋업) 참조. `checkEnvironment()`로 확인하라.
199220
200221
## 제품 진입점
@@ -223,13 +244,17 @@ console.log(machine.run("len(values)")); // 3
223244
환경 준비 -> 체크포인트 -> AI 코드 실행 -> (실패) -> 복원 -> 수정 코드 실행
224245
```
225246

226-
**패턴 2 - 후보 분기.** 공통 데이터와 패키지를 한 번 로드하고, 같은 준비 상태에서 여러 접근을 각각 격리해 실행한다 - `PyProc` 워커로, 또는 한 체크포인트에서 반복 복원으로.
247+
**패턴 2 - 경쟁·검증·채택.** 공통 데이터를 한 번 로드하고, `history.attempts([...])`로 같은
248+
준비 상태에서 후보 해법들을 경주시킨다: 후보마다 형제 가지로 체크포인트되고 사이마다 힙이
249+
기반으로 되감기므로 실패한 후보가 다음을 오염시키지 못한다. 각 끝 상태를 파이썬 단정으로
250+
채점하고, 승자를 `adopt`한 뒤 이름 있는 내구 가지에 커밋하면 - 무엇을 시도했고 왜 이겼는지가
251+
note에 남아 - 에이전트의 해결 경로가 채팅 로그 고고학이 아니라 1급 역사가 된다.
227252

228253
```text
229254
데이터 + 패키지 로드
230-
|-- pandas 접근
231-
|-- SQL 접근
232-
\-- NumPy 접근
255+
|-- pandas 접근 -> 채점
256+
|-- SQL 접근 -> 채점
257+
\-- NumPy 접근 -> 채점 -> 승자 채택(note: 무엇을 돌렸고 무엇이 판정했고 무엇이 이겼나)
233258
```
234259

235260
**패턴 3 - 로컬 우선 데이터.** 사용자 파일은 탭에서 분석되고, 요약만 나간다. 에이전트 코드를 실행하기 전에 fail-closed CSP를 적용해 코드가 외부 endpoint를 열지 못하게 하고, 신뢰한 agent 제어 채널이 돌려주는 값도 제한한다.

README.md

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,27 @@ If the boundary was not closed (an exception mid-run, a stray mutation), `cp.res
195195
it and falls back to a full rehash automatically - slower, never silently corrupt. After calling
196196
Python through a live proxy handle, report it with `machine.markDirty()`.
197197

198+
Branch and adopt. The durable Machine speaks git's verbs about execution state: commit competing
199+
states to named branches, record why in a provenance note that lives in the commit itself, and adopt
200+
the winner. Heap states cannot be merged, so the consuming verb is `adopt`, not merge:
201+
202+
```js
203+
const m = await open({ name: "lab", milestones: { keep: 7 } });
204+
await m.run("model = trainStep({})");
205+
await m.branch("adamRun", { note: { attempt: "adam", lr: 0.001 } });
206+
await m.run("model = trainStep({'optimizer': 'sgd'})");
207+
await m.branch("sgdRun", { note: { attempt: "sgd" } });
208+
await m.adopt("adamRun", { note: { reason: "validation passed" } }); // its state is now HEAD
209+
await m.adopt("auto-2026-08-05"); // or go back to yesterday
210+
```
211+
212+
`branches()` lists every branch with its fork parent and note, so the whole decision - what was
213+
tried, what judged, what won - reads back as history. With `milestones: { keep }`, every day gets an
214+
`auto-<date>` branch pointing at that day's last commit at no extra state cost. On transient
215+
machines, `machine.history.attempts([codes])` races candidates from one base with the heap rewound
216+
in between, so a failing candidate cannot contaminate the next; the
217+
[Machine demo](examples/machine.html) runs that race live.
218+
198219
> The basics above need only a Chromium browser. `PyProc` (process OS) and sockets also need `crossOriginIsolated` (`COOP: same-origin`, `COEP: require-corp`) and same-origin workers - see [Setup](#setup). Run `checkEnvironment()` to check.
199220
200221
## Product entrances
@@ -224,13 +245,18 @@ of silently losing the replay guarantee.
224245
prepare env -> checkpoint -> run AI code -> (fails) -> restore -> run fixed code
225246
```
226247

227-
**Pattern 2 - branch candidates.** Load shared data and packages once, then run several approaches from the same prepared state, each isolated - via `PyProc` workers, or by repeated restore from one checkpoint.
248+
**Pattern 2 - compete, verify, adopt.** Load shared data once, then race candidate solutions from
249+
the same prepared state with `history.attempts([...])`: each candidate is checkpointed as a sibling
250+
branch and the heap is rewound in between, so a failing candidate cannot contaminate the next. Judge
251+
each end state with a Python assertion, `adopt` the winner, and commit it to a named durable branch
252+
whose note records what was tried and why it won - the agent's solution path becomes first-class
253+
history, not chat-log archaeology.
228254

229255
```text
230256
load data + packages
231-
|-- pandas approach
232-
|-- SQL approach
233-
\-- NumPy approach
257+
|-- pandas approach -> judge
258+
|-- SQL approach -> judge
259+
\-- NumPy approach -> judge -> adopt the winner (note: what ran, what judged, what won)
234260
```
235261

236262
**Pattern 3 - local-first data.** The user's file is analyzed in the tab; only the summary leaves. Apply a fail-closed CSP before agent code runs so it cannot open an external endpoint, and constrain what the trusted agent control channel returns.

docs/reference/api.md

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,14 @@ Volatile verbs:
170170
valve). Restoring a pruned node throws `PYPROC_CHECKPOINT_PRUNED`.
171171
- `stats()` reports exact controller-owned base/delta/hash bytes, node/branch counts,
172172
live depth, and the last pressure event.
173+
- `attempts(codes)` races candidate solutions from the current state. Each candidate runs
174+
serially, its end state is checkpointed as a sibling branch of the same base, and the
175+
heap is rewound to the base in between, so a failing candidate cannot contaminate the
176+
next; the machine ends at the base state. The result carries `{ base, attempts, adopt }`:
177+
each attempt records `{ index, code, ok, value, error, checkpoint }`, and `adopt(i)`
178+
restores attempt `i`'s end state. Serial on purpose: `restoreLive` is cheap, while
179+
parallel attempts cost one heap each (`proc().map` is the parallel tool). This is the
180+
compete-verify-adopt loop an agent drives. Available from 0.0.14.
173181
- `setRetentionPolicy(policy)` sets observable budgets (`maxNodes`, `maxDeltaBytes`,
174182
`maxTotalBytes`). `pruneBranches: true` may remove only off-live-path branches, so a
175183
linear history - the shape a per-statement checkpoint produces - gets nothing back from
@@ -195,6 +203,24 @@ one journal instance):
195203
interrupts execution). Durable-claim failures are observable via `onStatus`
196204
(`PYPROC_JOURNAL_IO`), never silently swallowed.
197205
- `pack(opts)` compacts live blobs into one pack file and drops loose/stale files.
206+
- `branch(name, opts)` commits the current state to a named branch ref without touching
207+
HEAD/PREV; the commit's parents record the fork point and `opts.note` (canonical JSON,
208+
4KB cap) rides the commit as provenance - what was tried and why lives in the same
209+
object as the state. `branches(opts)` lists branches with commit address, creation
210+
time, note, and fork parents. `recoverBranch(name, opts)` materializes a branch onto
211+
the heap (same `h0` contract as recover; corruption is an explicit error, never a
212+
first-boot masquerade). `adopt(name, opts)` materializes the branch and commits it as
213+
the new HEAD - the adopting commit's parents point at the branch commit and its note
214+
records `adoptedFrom`. `deleteBranch(name, opts)` removes the ref (blob reclamation is
215+
the next prune). Heap states cannot be merged, so the consuming verb is adopt, not
216+
merge. A journal carrying branches marks itself format version 2: an older pyproc
217+
refuses it fail-closed instead of pruning branch data it cannot see; deleting the last
218+
branch restores version 1. Available from 0.0.14.
219+
- `milestones: { keep: N }` (journal config) opts into daily auto branches: every HEAD
220+
commit updates `auto-<date>` to point at it, so a day's milestone converges to that
221+
day's last state, and dates beyond `keep` are trimmed oldest-first. A milestone is one
222+
tiny ref file - the content-addressed commit already exists - so going back to
223+
yesterday is `adopt("auto-<date>")` at no extra state cost. Available from 0.0.14.
198224
- `export(opts?)` exports a signed portable bundle (`PYBUNDLE1`). Deterministic boots
199225
only.
200226
- `save(dir, name)` saves the session delta to OPFS; revival is
@@ -217,8 +243,11 @@ Every state verb returns its cost; nothing is free and nothing hides:
217243
(`kind` is `"base"` or `"delta"`).
218244
- `restore(...)` returns `{ pagesWritten, mbWritten, rehashed }` (`rehashed` reports
219245
whether the boundary-violation rehash path ran).
220-
- `commit(...)` returns `{ pages, wrote, mb, committedAt, home?, autoPack?, pruned? }`
221-
(`wrote` is after content-address dedupe; `home` reports the file-tree generation).
246+
- `commit(...)` returns `{ ref, commit, pages, wrote, mb, committedAt, home?, autoPack?, pruned? }`
247+
(`wrote` is after content-address dedupe; `home` reports the file-tree generation;
248+
`ref`/`commit` name the moved ref and the commit's content address).
249+
- `adopt(...)` returns the commit receipt plus `{ adopted, applied }` (which branch, and
250+
the materialization receipt).
222251
- `pack(...)` returns `{ liveKeys, packed, bytes, mb, looseRemoved, packsRemoved }`.
223252
- `prune(target)` returns `{ freedNodes, freedMB, keptNodes }`.
224253

@@ -394,8 +423,13 @@ calls immediately (`PYPROC_PROCESS_UNAVAILABLE`) instead of hanging.
394423
### `KernelElection`
395424

396425
The handle returned by `open()` or `open({ name })`, and the
397-
underlying election/RPC contract: `join` / `run` / `commit` / `ready` / `status` /
398-
`subscribe` / `role` / `leave`. Tabs elect one leader over Web Locks; only the leader
426+
underlying election/RPC contract: `join` / `run` / `commit` / `branch` / `branches` /
427+
`adopt` / `deleteBranch` / `ready` / `status` / `subscribe` / `role` / `leave`. The
428+
branch verbs ride the same command pipeline as `run`, so exactly-once outcome recording,
429+
succession, and epoch fencing apply to branching unchanged; `adopt` materializes the
430+
branch and commits the new HEAD in one command, which is why this handle has no
431+
`recoverBranch` (the leader never sits on a heap that diverges from HEAD). Daily
432+
milestones opt in through `open({ name, milestones: { keep } })`. Available from 0.0.14. Tabs elect one leader over Web Locks; only the leader
399433
boots the kernel (deterministic session + journal); followers are RPC views over
400434
BroadcastChannel. When the leader tab dies, the lock releases, a follower promotes and
401435
resumes from the journal. Errors: `PYPROC_LEADER_UNAVAILABLE` (retryable),

0 commit comments

Comments
 (0)