Concepts

What Oak is

Oak is the agentic substrate for software development β€” the version-control and storage layer that autonomous coding agents build on. It's where your agent (Claude Code, Codex, Cursor, etc.) reads, writes, branches, and collaborates on code and assets.

Oak doesn't run agents β€” bring your own. What it provides is the foundation underneath them, shaped around how agents actually work: branch-per-session as the unit of work, branch descriptions in place of per-commit messages, and content-addressed lazy mounts that get an agent editing any repo in seconds. Because it's content-addressed and hydrates on demand, it's also far faster than git for agent workloads β€” but that speed is a consequence of the design, not the pitch.

Oak ships today for macOS on Apple Silicon and Linux x86_64.

Quickstart

Install the CLI, log in, and get a repo onto Oak in a couple of minutes. Supported today on macOS (Apple Silicon) and Linux x86_64.

# Install the CLI, then authenticate
curl -fsSL https://oak.space/install | sh
oak login

# Start a fresh repo β€” drops you on a personal feature branch off main
oak init my-app && cd my-app

# ...make some edits, then review and snapshot them
oak status
oak diff
oak commit

# Describe the change, then push (the remote repo is created on first push)
oak desc "Initial version"
oak push

Already have a repo on Oak? oak clone <org>/<repo> grabs it and drops you on your own feature branch, ready to edit.

The very first push of a brand-new repo needs an owner. Run interactively, oak push prompts you to pick an organization; in a script or an agent run (no terminal prompt) pass it explicitly with oak push --repo <org>/<name>. After that first link, plain oak push just works.

Working against a large monorepo? Skip the full clone and mount it instead β€” you're editing in seconds.

When the change is ready you merge it onto main yourself β€” see Merging and conflicts. Every command and flag is in the CLI reference tab.

Branches and descriptions

The unit of work in Oak is a branch, not a commit. oak init and oak clone auto-create a personal feature branch off main β€” you never work directly on main locally. This matches how agents work: one branch per session, merged when the session is done.

Commits on feature branches have no commit message. Instead, the branch description is the source of truth for what a change introduces. Create a branch with oak switch -c <name> and set its description with oak desc "...".

Merging a feature branch into main produces a single squash commit whose message is the branch description. The squash commit retains a pointer to the original branch tip, so the pre-squash history stays reachable for tooling. Direct pushes to main are refused.

Working with agents

Oak is the substrate your agent works on β€” it doesn't run agents itself, so bring your own (Claude Code, Codex, Cursor, etc.). The model below is what keeps an agent-driven workflow tidy:

  • One branch per session. oak clone and oak init already drop you onto a personal feature branch off main. Start each agent run on its own branch so a session you don't like can be abandoned without touching main. Branches are cheap by design.
  • Have the agent set the branch description. Commits carry no message, so oak desc "..." is the source of truth for what the change does β€” and it becomes the squash-merge message on main. Ask the agent to write it for a reviewer, not as a list of touched files.
  • Give the agent the docs. Use the Copy page button at the top of this page to paste the concepts and CLI reference straight into your agent's context, so it knows the commit workflow before it starts.
  • Agent commits and publishes, you merge. Let the agent checkpoint on the feature branch, push it for review, and stop there. Review the diff and run the merge to main yourself β€” merging is a human decision.
  • Mount large repos instead of cloning them. For monorepos, oak mount gets the agent editing in seconds rather than waiting on a full pull.

Oak makes no AI calls on your behalf and never trains on your code. Whatever agent you bring is its own integration with its own privacy posture.

Merging and conflicts

You work on a feature branch and never push to main directly β€” direct pushes to main are refused. When a change is ready, you merge the branch into its parent (main).

Review first: oak diff shows the working-tree changes and oak log the commits on the branch. When it looks right, oak merge lands the whole branch on main as a single squash commit whose message is the branch description (it falls back to the branch name if you never set one). The squash commit keeps a pointer back to the original branch tip, so the pre-squash history stays reachable for tooling.

If main has moved since you branched, bring your branch up to date first with oak pull β€” it fetches new commits and merges the latest main into your branch. A clean merge needs nothing more; a conflict drops you into a resolution state.

To resolve a conflict, edit the conflicted files, then continue or back out:

  • oak pull --continue / oak pull --abort β€” finish or cancel the parent-merge during a pull.
  • oak merge --continue / oak merge --abort β€” finish or cancel a merge.

Merging is a human decision: let your agent checkpoint on the feature branch, push it for review, and stop there; then review the diff and run the merge yourself. See Working with agents.

Lazy mounts

oak mount lets a working tree live on top of a remote repository without a full local clone. Files download lazily as you touch them, so you can work against a multi-gigabyte monorepo with seconds-to-first-edit instead of waiting for a full pull. Each mount runs as a detached background daemon β€” the command returns as soon as the mount is live and hands your terminal back.

The spec decides what gets mounted: oak mount org/repo mounts one repo at ./<repo>, and oak mount org/repo ./task/repo mounts it at an explicit destination. By default that starts a fresh virtual branch off the trunk; oak mount org/repo --branch name mounts an existing remote branch instead β€” its history and files become the mount's, so you can continue a pushed branch or resolve its merge conflicts without a full clone. Run oak mount list to see active mounts. Use oak mount finish to preflight, describe, checkpoint, publish, and tear a task down, or oak mount end to unmount and remove local mount state.

Edits go into a virtual branch backed by a local mount cache β€” nothing leaves your machine until you push. The overlay (modified, deleted, and renamed paths) is the active commit β€” the logical commit you're amending as you edit. oak commit checkpoints that active commit locally onto the virtual branch; oak push, oak commit --push, or oak finish sends it upstream.

Inside a mounted directory the normal commands (oak status, oak commit, oak desc, oak diff, oak log, oak hash, oak push, oak pull) automatically route to the virtual branch, so mounted and locally-cloned repos feel identical at the command level.

Mounts are supported on macOS (Apple's FSKit, via the Oak Mount app β€” macOS 26 or later) and Linux (FUSE). See Setting up mounts below for the one-time setup on each platform.

When an agent works across several of an org's repos, drive the mounts from an agent space β€” one directory per task, with a mount per repo inside it.

Setting up mounts

Lazy mounts rely on a userspace filesystem, which needs a one-time setup per machine. On macOS that's Apple's FSKit and the Oak Mount app; on Linux it's FUSE. No kernel extension is involved on either platform.

macOS β€” the Oak Mount app (macOS 26+)

On macOS the filesystem runs inside a signed app extension (OakFS) that ships in the Oak Mount app. The app is just the carrier: it has to be installed and its extension enabled once, but it doesn't need to be running for mounts to work β€” everything is driven by the oak CLI, and one install serves every mount on the machine.

  1. Run your first oak mount. The CLI downloads the Oak Mount app from the release channel, verifies its checksum, installs it to /Applications (or ~/Applications), and opens it once so macOS registers the extension.
  2. Enable the extension: System Settings β†’ General β†’ Login Items & Extensions β†’ File System Extensions, then toggle OakFS on. The Oak Mount app's window has a button that takes you straight there. No reboot required.
  3. Re-run your oak mount command β€” it attaches immediately, and from then on mounting needs no further interaction.

Keep the app in /Applications; if the extension is later toggled off, oak mount tells you exactly that instead of failing mysteriously. One macOS quirk to know: mounting into ~/Documents, ~/Desktop, ~/Downloads, or iCloud Drive requires giving your terminal Full Disk Access β€” pick a destination outside those folders (like ~/oaktree, the default) to avoid it.

Linux (FUSE)

  1. Install FUSE: sudo apt install fuse3 on Debian/Ubuntu (use your distro's equivalent elsewhere). Oak mounts via the fusermount3 helper that package provides.
  2. If your agent or editor runs as a different user β€” or in a sandbox β€” and needs to see into the mount, enable user_allow_other by uncommenting that line in /etc/fuse.conf.
  3. On distros that gate FUSE behind a group, make sure your user is a member (commonly the fuse group), then re-log so the membership takes effect.

Verify the setup with oak mount org/repo followed by oak mount list. If the mount fails to attach, the error almost always points back at one of the steps above.

Agent spaces

An agent space is a directory where a coding agent works across one org's repos, mounting whichever repos a task needs. A space spans the whole org rather than a single repo: each task gets its own subdirectory, and inside it you run one oak mount per repo the task touches β€” each on its own virtual branch. It's like a set of git worktrees, but without a full clone behind each one (mounts hydrate content lazily), so spinning up a fresh task is near-instant.

Scaffold one with oak space new <org>. It creates the space directory (default ./<org>) with an AGENTS.md that explains the workflow to the agent, a CLAUDE.md pointer, a .claude/settings.json, and a .oak-space marker recording the org.

The per-task loop

  1. Pick a short kebab-case slug for the task (e.g. fix-auth-redirect).
  2. List the org's repos with oak space repos and decide which the task needs.
  3. Mount each one under the task directory: oak mount <org>/<repo> ./<slug>/<repo>. A single-repo task becomes ./<slug>/<repo>/; a cross-repo task gets several repo mounts side by side.
  4. Let the agent edit inside each repo mount. oak status, oak diff, oak commit, and oak push all route to that mount's virtual branch when run from inside it.
  5. Finalize each repo you touched with oak finish --desc-file <file> --json (from inside the mount) or oak mount finish ./<slug>/<repo> --desc-file <file> --json (from the space root). It preflights before mutation, then sets the branch description, checkpoints any pending overlay, pushes the virtual branch, and tears the mount down after the push succeeds. If a later phase fails, the mount stays intact and JSON names the next manual command.
  6. Sweep up finished tasks with oak space clean, which tears down every mount whose working tree is clean and leaves dirty ones alone.

Spaces build on the same lazy-mount machinery, so the one-time macOS / Linux setup in Setting up mounts applies here too.

Partial clones

A partial (sparse) clone checks out only part of a monorepo β€” Perforce-style β€” when a full lazy mount isn't what you want. Pass one or more path prefixes to oak clone:

oak clone acme/monorepo --path services/api --path libs/shared

Only files under the cone (the set of prefixes) are downloaded and written to disk; the rest of the repo isn't fetched. Because Oak's tree objects are content-addressed and hash-verified, the full directory structure still comes down β€” so out-of-cone files are listed but their content is withheld, exactly like a path-permissioned file. Commits made from a sparse clone carry the out-of-cone tree forward untouched: narrowing a checkout never deletes the paths it leaves out, and oak status never reports them as missing.

Adjust the cone after cloning with oak sparse:

  • oak sparse β€” show the active cone (or --json).
  • oak sparse set <prefix>… β€” replace the cone and re-sync the working tree.
  • oak sparse add <prefix>… β€” widen the cone; newly-included files that weren't downloaded yet are reported so you can oak pull to hydrate them.
  • oak sparse disable β€” return to a full checkout.

When you're working against a very large repo, a lazy mount is usually the better default β€” it hydrates any path on demand with no cone to manage. Reach for a sparse clone when you want a plain on-disk checkout scoped to a subtree, without the FUSE/FSKit setup a mount needs.

A related escape hatch: if a server is ever in a broken state and a normal clone/pull errors on a missing blob, set OAK_ALLOW_PARTIAL_CLONE=1 to skip the missing files (each one is reported) instead of failing. This is for recovery, not everyday use β€” prefer a sparse clone when you intentionally want only part of a repo.

Organizations

An organization is the owner of repositories. Every account gets a personal organization (named after your username); you can also create shared organizations for teams and companies.

The organization is also the deduplication boundary for storage. Blobs and chunks are shared across every repo within the same organization, so a binary asset committed to two repos in the same org is stored once. Storage and bandwidth quotas are accounted at the organization level.

Repository URLs are <organization>/<repo> β€” for example oakspace/oak.

Path permissions

Path permissions are directory-level access control on a repo: an org owner or admin can restrict a subtree so that only granted users can read the content of files under it. Manage them from the repo's Settings β†’ Path permissions β€” add a rule for a path prefix (e.g. infra/secrets), then grant specific users access to it.

Enforcement happens at the content layer, not the directory listing. Because Oak's trees and manifests are content-addressed and the client re-derives (and verifies) each commit's manifest hash, the server can't redact entries β€” so a restricted file's name and size stay visible, but its blob content is withheld from anyone without a grant. On oak clone / oak mount a restricted file materializes empty or absent; the web tree/raw views and the blob/chunk download endpoints refuse its bytes too. Org owners and admins always have full access. Rules nest β€” the most specific prefix wins β€” so you can lock scripts but re-open scripts/public underneath it.

This shares the same mechanism as partial clones: both withhold out-of-scope blob content while keeping the tree structure intact. The difference is who decides β€” path permissions are an access-control policy set by repo admins; a sparse clone is a bandwidth choice the person cloning opts into.

Git import and export

oak clone <gitrepo> is the import path from git into Oak. When the argument looks like a git remote URL, Oak shells out to git clone, initializes an Oak repository in the destination directory, then replays the cloned git history into Oak on main. By default the destination directory is derived from the git repo name; pass a destination path as the second argument to choose it yourself.

The CLI's oak export <dest> command is the local counterpart: replay your Oak history into a fresh git repo on disk. This is the documented escape hatch β€” your data is never trapped in Oak's format.

oak clone <gitrepo> replays git history into Oak, while oak export replays Oak history back into git. Push code with oak push.

Data portability

The trust story is the export path above: any Oak repo can be replayed into a fully-functional git repo at any time with oak export.

If you want to leave, you can β€” with full history intact.