Viewing files at commit b297e8a25d08Commit detailsView latest

Browse files
prompts/oak-fix-handoff.md 196 lines · 9.8 KB

Oak Fix Handoff — findings from benchmark instrumentation, 2026-06-10/11

Everything below was observed live against oak 0.95.0/0.96.0 + Oak Mount app v0.96.0 on macOS (FSKit), while building and running the oak-benchmarks suite. Each item: what happened, why it matters for agents, suggested fix, and how to verify. Evidence lives in oak-benchmarks results JSONL and branch descriptions on oak/benchmarks.

Verification harness (run from an oak-benchmarks checkout, your built binary):

python3 scripts/devloop.py --skip-build --oak-local-bin <your>/target/release/oak
python3 scripts/mount_probe.py --oak-bin <your>/target/release/oak
python3 scripts/opportunities.py    # ranked current state

P0 — error-recovery traps (agents and humans get stuck; cheap fixes)

1. oak pull divergence error is self-contradictory

Observed output, verbatim:

error: Conflict detected: local has commits not in remote history.
Use 'oak pull --force' to discard local commits and sync with remote.
error: Conflict detected: remote has diverged. Pull changes first.

Two stacked errors; the second tells you to run the command that just failed; the only suggested action is the DESTRUCTIVE one (--force, discards commits). The actual safe path — oak push the local branch, then oak fetch, then branch off updated main — is never mentioned and worked first try. An error message is a prompt: agents will follow it, and this one points at data loss. Fix: single coherent message; recommend the non-destructive recovery; only mention --force as the explicit last resort.

2. oak branch rename can't find the current branch

oak branch rename bench-diff-contract <new> → error: Branch not found: bench-diff-contract while that branch was checked out (oak branch list showed it with *). Either rename takes different args (then the error should show usage) or current-branch lookup is broken. Fix + make the error print the accepted syntax.

3. Same-name branch collision gives wrong advice

Pushing branch X from a repo when the server has an unrelated branch X:

Use 'oak pull --force' to discard local commits and sync with remote.
error: Conflict detected: remote has diverged. Pull changes first.

The histories are UNRELATED — "pull changes first" cannot work and "diverged" is misleading. Fix: detect unrelated history and say so: "a different branch named X exists on the remote; rename yours (oak branch rename ...) or push under a new name." Agents hit this constantly (every swarm run that reuses a task-slug branch name).

4. Mount success message delivered as an error

First mount auto-install prints:

error: Server error: Installed "Oak Mount.app" to /Applications.
One more one-time step: enable the OakFS file-system extension ...

A SUCCESSFUL install + a correct instruction, wrapped in error: Server error: with nonzero exit. Also the instruction says toggle "OakFS" but System Settings displays it as "OakFSExtension" — the user looked for the wrong name. Fix: success exit + success prefix for the install, then a clear "action required" block; match the exact name System Settings shows.

5. Mounting an empty repo is a dead end

oak mount of a freshly created repo: error: Server error: repository has no commits — nothing to mount (also double-wrapped error: Server error: ... error: Server error:). There is no guidance for the obvious next step, and seeding the default branch is non-obvious: a local oak init + push --repo ORG/R lands on a child branch, NOT main — you must then oak merge. Fix: error should say exactly that ("push an initial commit and merge it to main: ..."), and/or oak push to an empty remote should offer to initialize the default branch. De-duplicate the nested error prefixes everywhere.

6. oak mount end refusing dirty/unpushed mounts needs an escape hatch

The refusal is good policy (verified: it protects work), but the agent recovery path is undocumented: you must cd inside, oak commit, oak push, then end. Fix: the refusal message should print those exact steps, and a oak mount end --discard (explicit, scary-named) should exist for disposable work. Same for mounting onto a non-empty directory (interrupted_recovery): refusal is fine, remediation hint is missing.


P1 — performance cliffs (first live mount/remote numbers, n=1, one host)

7. First write into a mount costs 1.4–1.7s, then ~66ms

The single biggest cliff measured. Every task's first edit pays it (time-to-first-useful-work for an editing agent ≈ mount 1.0s + first-write 1.5s). Suspect write-path initialization (upcall setup, COW bootstrap?). Steady-state writes are fine. Verify: mount_probe scenario first_use_read_edit, row fs.first_edit.

8. Warm mount.start is not faster than cold (~1.0–1.2s both)

Nothing is being reused across mounts of the same repo in one process lifetime. ~1.0s also looks like a floor worth attacking (server roundtrips in series?). Verify: scenarios cold_mount_startup vs warm_mount_startup.

9. No-op sync costs 281ms (oak fetch with nothing new)

Agents sync constantly; an up-to-date check should be one cheap HEAD comparison. push.first 446ms / clone.cold 694ms for a 2-file repo are also trend targets (network transport, oak-vs-oak trend only). Verify: core lane remote.net.* rows with OAK_BENCH_REMOTE=oak/oak-benchmarks-tmp.

10. Lifecycle loop steady-state is push-dominated (~1.2s/iter, ~850ms push)

edit+commit+desc are fast (commit 12-16ms — excellent); push dominates. oak desc also costs a 250-330ms server roundtrip every call. Consider batching desc with push, or async desc. Verify: task_lifecycle_loop loop.push_desc.total / per-iteration rows.


P2 — output contracts (token cost + tooling compatibility)

11. Piped oak diff is 0% unified-diff compatible (git: 100%)

No ---/+++/@@ structure → patch, git apply-style tools, and scripts that grep hunks all break. Oak's compactness is NOT the problem (information recall is 1.0 at ~45 bytes/file — keep that); the fix is emitting unified structure when stdout is not a TTY (or a --unified flag the agent-default contract can adopt). This also flips a permanent benchmark quality-gate liability into a win. Verify: diff.full.inforecall rows, pipe_compatible_unified.

12. Output bytes slightly worse than git on hot paths

status.dirty +16.7% bytes vs git status --short at equal recall; branch.create +28.9% (git prints "Switched to a new branch 'x'", oak prints more). Every byte of stdout is agent input tokens, paid thousands of times daily. Worth a pass over hot-path message verbosity. Verify: opportunities.py section 1.

13. --version/banner verbosity

The ASCII-art banner (↟↟↟↟ block) is charming for humans but appears in contexts agents pay for; proc.spawn cost-weighted tokens are +4.8% vs git. Keep the art for TTY, plain oak <semver> otherwise. Verify: proc.spawn rows.

14. Missing compact/porcelain modes (agent-default contract gaps)

From the command-semantics contract: no status --porcelain equivalent, no diff --name-only, no commit --quiet. status --json exists — good — but the porcelain-style stable text contract is what shell-pipeline agents use. Each gap forces verbose output or a follow-up call (vcs_info_followup_calls_total measures this in the agent lane).


P3 — capability and observability gaps

15. Oak should self-report hydration bytes

FSKit reports allocated blocks ≈ full logical size for lazy files, so the benchmark CANNOT measure lazy hydration from the disk side. The lazy story is real (mounting a 64MB repo costs the same ~1.0s as a 2-file repo; 4KB read of the 64MB file didn't pull it) but unprovable in bytes until oak prints hydrated: N bytes / downloaded: N bytes somewhere parseable (mount output, oak space status, or a stats file). The harness already parses those exact patterns from mount output — emit them and the benchmark lights up.

16. No integrity check (fsck equivalent)

Contention-lane integrity for oak falls back to log -n 1 + status sanity. A real oak verify/fsck would strengthen every durability claim.

17. History interrogation gaps

No show <commit>:<path>, no blame, no pickaxe/term search, no file-scoped log. The history_archaeology workflow measures agents doing these constantly; oak currently substitutes closest-available commands at a token/capability penalty.

18. oak finish not shipped

The benchmark has a capability-gated slot (oak.finish in the mount lane) that lights up automatically when the binary gains the subcommand.

19. CLI↔Mount-app version coupling is fragile

v0.95.0 CLI hit HTTP 404 (no app published for that version); behavior of a locally-built CLI against a different installed app version is undefined — this will bite every developer testing mount changes. Define and document the compatibility rule (exact match? same minor?) and make the mismatch error say which versions it sees.

20. oak commit on a clean tree exits 0 silently

(Observed on 0.95.) An agent can't distinguish "committed" from "nothing to commit" without parsing absence of output. Print "nothing to commit" and consider a distinct exit code (git uses 1).


What's already strong (don't regress these)

  • Commit inside mount: 11-16ms. Status/diff inside mount: 10-12ms.
  • Shared-checkout contention: oak lock-wait 65ms vs git 641ms at 4 workers; zero lost updates in all runs.
  • Information density: recall 1.0 at ~45 bytes/changed-file vs git full diff's ~8.4MB on binary scenarios.
  • One-call snapshots (vs git's add+commit) — with tool-call envelopes priced, each saved call is worth ~72 tokens before output.
  • space clean correctly tears down only clean+pushed mounts (445ms).
  • Mount cost is content-independent (lazy hydration works in time terms).