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 cloneandoak initalready drop you onto a personal feature branch offmain. Start each agent run on its own branch so a session you don't like can be abandoned without touchingmain. 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 onmain. 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
mainyourself β merging is a human decision. - Mount large repos instead of cloning them. For monorepos,
oak mountgets 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.
- 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. - 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.
- Re-run your
oak mountcommand β 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)
- Install FUSE:
sudo apt install fuse3on Debian/Ubuntu (use your distro's equivalent elsewhere). Oak mounts via thefusermount3helper that package provides. - If your agent or editor runs as a different user β or in a sandbox β and needs to see into the mount, enable
user_allow_otherby uncommenting that line in/etc/fuse.conf. - On distros that gate FUSE behind a group, make sure your user is a member (commonly the
fusegroup), 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
- Pick a short kebab-case slug for the task (e.g.
fix-auth-redirect). - List the org's repos with
oak space reposand decide which the task needs. - 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. - Let the agent edit inside each repo mount.
oak status,oak diff,oak commit, andoak pushall route to that mount's virtual branch when run from inside it. - Finalize each repo you touched with
oak finish --desc-file <file> --json(from inside the mount) oroak 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. - 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 canoak pullto 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.
Overview
Oak imports a git repository by replaying its commit history β every commit, with its original author and timestamp β into Oak's content-addressed storage. You can do it from the web with nothing installed, or from the oak CLI.
Three ways in, from least to most hands-on:
- Import from GitHub on the web β no CLI; oak.space clones and converts a branch you pick, server-side.
- Clone a remote git repo β
oak clone <git-url>fetches and converts it in one step. - Convert a local git repo in place β
oak initin a directory that already has a.git.
The CLI paths replay the full HEAD history and leave you on a feature branch ready to push; the web importer brings one branch straight onto the server. Either way the import is reversible β export back to git replays your Oak history into a fresh git repo.
Import from GitHub (no CLI)
Prefer not to touch the command line? oak.space pulls a GitHub repository straight into Oak from the web β the server does the clone and conversion, so you don't need git or the oak CLI installed.
One-time: connect GitHub
From Settings β GitHub, choose Connect GitHub and install Oak's GitHub App. You can grant it every repository or just the ones you pick β GitHub's install screen offers that choice. Oak only ever asks for read access (repository contents + metadata) and mints a short-lived, per-repo token for each import; it never gets write access to your GitHub.
Import a repo
- Open the Import from GitHub panel β it lists the repositories your connection can see β and choose one.
- In the dialog, pick the branch to import and the destination organization (your personal org by default, or a shared one). Optionally override the repository name, mark it public, and list collaborators to invite β Oak checks the name is free as you type.
- Start the import. Oak creates the destination repo and replays that branch's history into it; a progress page streams status and sends you to the new Oak repo when it's done.
Unlike the CLI paths below, the web importer brings the single branch you choose and lands it directly on the server β there's nothing to oak push afterward. Run oak clone <org>/<repo> (or mount it) to get a working copy.
Clone a remote git repo
Point oak clone at a git remote URL. Oak shells out to your system git to fetch it, then converts the history into a new Oak repo:
# HTTPS or SSH, any git host
oak clone https://github.com/owner/repo.git
oak clone [email protected]:owner/repo.git
# Pick the destination directory (defaults to the repo name)
oak clone https://github.com/owner/repo.git my-dir
This needs the git binary on your PATH (Oak runs git clone under the hood). It then initializes an Oak repo in the destination, replays every commit onto main, drops you on a personal feature branch at the tip, and offers to delete the now-redundant .git directory (kept if you decline, or if there's no terminal to ask).
What counts as a git URL. oak clone hands the argument to git β rather than reading it as an Oak org/repo spec β when it starts with git@, ssh://, or git://, ends with .git, or is a github.com / gitlab.com / bitbucket.org URL. For a self-hosted host over HTTPS, make the URL end in .git (or use an ssh:// / git@ form) so Oak recognizes it as git.
The --branch flag is only for cloning Oak repos; it's rejected on a git URL, since the importer always brings the whole history.
Convert a local git repo in place
Already have the repo checked out? Run oak init inside it. When Oak sees an existing .git directory it offers to import that history in place β the same conversion as oak clone <git-url>, just pointed at your local work tree:
cd my-existing-git-repo
oak init
# -> "Detected an existing git repository here. Import its history into oak?"
Accept, and Oak replays the git history onto main, fast-forwards your personal branch to the tip, and offers to remove .git. Decline, and Oak leaves your .git untouched β handy if you want to keep using git alongside for a while.
Either way you finish on a feature branch parented onto main, exactly where a fresh git-URL import leaves you.
What's preserved, what's dropped
The conversion is history-faithful, with a few well-defined limits.
Preserved
- The full commit graph in order, including merge commits (both of a merge's parents).
- Each commit's original author, email, message, and timestamp β imported commits keep their git dates, they aren't restamped to today.
- File modes: regular, executable, and symlink entries all carry over.
Dropped or simplified
- Submodules (gitlinks) are skipped β Oak has no submodule concept and won't silently fetch them.
- Octopus merges (3+ parents) keep the first two parents; the extras are dropped.
- Only one line of history comes across β the checked-out
HEADfor the CLI paths, or the branch you pick in the web importer; other branches and tags aren't recreated as Oak branches. - The
.gitdirectory is removed only if you confirm β otherwise it's left for you to delete later.
After import, your first oak status is clean: Oak rewrites the working tree from its own manifest, so .gitattributes smudge filters and leftover submodule directories don't show up as spurious changes.
Publishing to Oak
A CLI import (oak clone or oak init) is local β your converted history lives on your machine until you push. (The web importer already landed on the server, so it skips this step.) Send it up with oak push; on the very first push of a freshly imported repo, name the owner so Oak can create the remote:
oak login
oak push --repo <org>/<name> # first push: creates + links the server repo
After that, plain oak push works. From there it's the normal Oak workflow β the Quickstart and Merging and conflicts sections on the Concepts page cover what comes next.
Leaving again: export back to git
Importing into Oak isn't a one-way door. oak export <dest> replays your Oak history into a brand-new git repository on disk, preserving each commit's original author and timestamp:
oak export ./my-repo-as-git
This is Oak's documented escape hatch β your code and its history are never trapped in Oak's format. (For read-only access with stock tooling, an Oak repo's current main can also be cloned over HTTPS as a snapshot: git clone https://oak.space/<owner>/<name>.git.)
Overview
The oak CLI is the primary way to drive Oak from your terminal β create, commit, branch, pull, mount, and export repositories.
Pass --help to any command to see its flags. A global --verbose flag (before the subcommand) prints phase timings.
Installation
Install the latest release with one command:
curl -fsSL https://oak.space/install | sh
The binary installs to ~/.local/bin/oak. To upgrade in place:
oak upgrade
Getting Started
Start a new local repository, commit, and push to the server:
# Create and enter a new repo
oak init my-game && cd my-game
# Add files, then checkpoint locally
oak status
oak commit
# Log in and push β the remote repo is created on first push
oak login
oak push
Or clone an existing repo from the server:
oak clone owner/repo
cd repo
For large repos β or agent work that spans several of an org's repos β skip the full clone and mount instead. Mounting needs a one-time per-machine setup (the Oak Mount app on macOS, FUSE on Linux); see Setting up mounts. Once that's done:
# Scaffold an agent space for an org (one task per subdirectory)
oak space new acme && cd acme
# See which repos the task needs, then mount them under the task dir
oak space repos
oak mount acme/web ./fix-auth/web
# Edit inside the mount; oak commands route to its virtual branch
cd fix-auth/web
oak status
oak commit
# Finalize: preflight, describe, checkpoint if needed, publish, and unmount
oak finish --desc-file desc.txt --json
Core VCS
oak init [PATH]
Initialize a new local repository, auto-creating a personal feature branch parented onto main.
PATH
directory to initialize (default: current directory)
oak status [--json [--compact] | --porcelain | --short] [--reconcile]
Show the status of the working directory β modified, added, and deleted files.
--json
emit machine-readable JSON
--compact
with --json, emit bounded recall-oriented JSON
--porcelain, --short
emit stable compact changed-path rows; --short is git-compatible
--reconcile
apply pending remote-merge reconciliation before printing
oak diff [PATHS...] [--json] [--print | --stat | --name-only]
Show changes between the working directory and HEAD.
PATHS
limit the diff to these files or directories
--json
emit machine-readable summary
--changed-files-limit N
cap changed-file summaries in --json output
--changed-files-offset N
start changed-file summaries at this offset
--print
print hunks to stdout instead of opening the browser
--stat
show per-file +added/-removed counts
--name-only
show only changed paths
oak commit [PATHS...] [--push] [--json] [--quiet] [--no-verify]
Create a local checkpoint on the current branch. Plain commit never publishes. Commits carry no message β the branch description (set with oak desc) becomes the squash-merge commit message when the branch lands on main.
PATHS
commit only changes under these files or directories
-m, --message TEXT
refused compatibility flag; Oak commits are messageless
--push
after checkpointing locally, explicitly publish pending branch commits
--json
emit machine-readable JSON
--quiet
suppress human success/no-op text
--no-verify
skip pre-commit and post-commit hooks
oak log [-n LIMIT] [--verbose | --oneline] [--json] [PATHS...] [-S TERM]
Show commit history.
-n, --limit LIMIT
maximum number of commits to display
--verbose
include changed files in each entry
--oneline
show one compact commit per line
--json
emit machine-readable JSON
PATHS
only show commits touching these paths
-S, --search TERM
only show commits that add or remove TERM
oak hash
Print the current HEAD commit hash.
oak rev-parse [--short] HEAD
Compatibility alias for oak hash. Oak currently supports only HEAD here; --short HEAD matches git rev-parse --short HEAD for scripts.
--short
print the short hash
oak info [--json]
Show repository and branch metadata.
--json
emit machine-readable JSON
oak switch [NAME] [-c] [--clean] [-d]
Switch to a branch, create one, or detach HEAD at a commit. With no NAME, prompts you to pick a branch interactively.
NAME
branch name or commit hash (omit for interactive picker)
-c, --create
create a new branch and switch to it; omit NAME to generate one
--clean
start from latest available main and discard current working-tree changes
-d, --detach
treat NAME as a commit hash and detach HEAD there
--discard
hidden deprecated alias for --clean, accepted for compatibility
oak checkout REFERENCE
Detach HEAD at a specific commit, given by full hash or a unique prefix (β₯ 4 hex chars).
REFERENCE
commit hash or unique short prefix
oak desc [DESCRIPTION | --file FILE]
Set the current branch's description β the source of truth for what the branch introduces, and the message used when it squash-merges to main.
DESCRIPTION
new description text
--file FILE
read description from a UTF-8 file, or '-' for stdin
oak finish [--desc TEXT | --desc-file FILE] [--json]
Finalize a branch with preflight, push, and description sync.
--desc TEXT
save this branch description before finishing
--desc-file FILE
read branch description from a UTF-8 file, or '-' for stdin
--json
emit machine-readable JSON
oak close [NAME] [--remote] [--reason REASON] [--json]
Close a branch (defaults to the current branch).
NAME
branch name (default: current branch)
--remote
close the configured remote branch without switching to it
--reason REASON
explicit audit reason
--json
emit machine-readable JSON
oak restore [PATHS...] [-s COMMIT] [-f]
Restore files to their HEAD state, or to a source commit. Restores everything when no path is given.
PATHS
paths to restore
-s, --source COMMIT
restore from this commit instead of HEAD
-f, --force
skip confirmation prompt
oak reset [PATH] [-f]
Reset the working directory (or a specific path) to HEAD, discarding uncommitted changes.
PATH
file or directory to reset; omit to reset everything
-f, --force
skip confirmation prompt
oak merge [BRANCH] [--continue | --abort | --dry-run] [--wait [MINUTES]] [--force] [--json]
Merge a branch into its parent. Use --continue to resume after resolving conflicts, --abort to cancel, --dry-run to preview, --wait to wait for CI, or --force to bypass the CI gate after inspection.
BRANCH
branch to merge (default: current branch)
--continue
continue a merge after resolving conflicts
--abort
abort a merge in progress
--dry-run
preview locally without pushing, fetching, or changing files
--wait [MINUTES]
wait for CI to conclude, then merge on success
--force
bypass the server-side CI merge gate
--json
emit machine-readable JSON for --dry-run or the actual merge result
oak split [--from BRANCH] [--plan FILE] [--dry-run]
Split a branch's commits into independent branches, or reorder/drop commits. Compatibility alias: oak histedit.
--from BRANCH
edit this branch instead of the current one
--plan FILE
apply a todo-list plan non-interactively; use '-' for stdin
--dry-run
print the resulting branch/commit structure without writing
Branches
oak branch [--json] [--show-current]
With no subcommand, list branches. --show-current prints the current branch name, compatible with git branch --show-current.
--json
emit machine-readable JSON
--show-current
print only the current branch name
oak branch list [--json] [--remote] [--status STATUS]
List branches.
--json
emit machine-readable JSON
--remote
read branch metadata from the configured remote
--status STATUS
filter by status, such as open or closed
oak branch show NAME [--json] [--remote]
Show one branch.
NAME
branch name
--json
emit machine-readable JSON
--remote
read branch metadata from the configured remote
oak branch diff NAME [--against BRANCH] [--diff-mode MODE] [--json] [--remote]
Show a checkout-free diff summary for a branch.
--against BRANCH
branch to compare against (default: main)
--diff-mode MODE
tree, contribution, or net-merge
--changed-files-limit N
cap changed-file summaries in JSON output
--changed-files-offset N
start changed-file JSON output at this offset
--json
emit machine-readable JSON
--remote
review the configured remote branch without switching to it
oak branch review NAME [--merge-preview] [--json] [--remote]
Show checkout-free branch review evidence.
--merge-preview
include local merge conflict prediction
--changed-files-limit N
cap changed-file summaries in JSON output
--changed-files-offset N
start changed-file JSON output at this offset
--json
emit machine-readable JSON
--remote
review the configured remote branch without switching to it
oak branch triage [--remote] [--against BRANCH] [--status STATUS] [--analysis-depth DEPTH] [--only BUCKET] [--limit N] [--json]
Batch branch triage over many branches without switching checkout.
--remote
read branch metadata from the configured remote
--against BRANCH
branch to compare against (default: main)
--status STATUS
filter by status, such as open or closed
--analysis-depth DEPTH
how deeply to analyze each branch (default: manifest)
--only BUCKET
return only rows matching a triage bucket
--limit N
maximum number of branches to analyze in depth
--json
emit machine-readable JSON
oak branch rename OLD_NAME NEW_NAME
Rename a branch. Temporarily disabled (returns 503) pending a storage-integrity fix.
OLD_NAME
existing branch name
NEW_NAME
new branch name
Agent & conflicts
oak agent state --json [--refresh] [--compact]
Print one compact JSON document with the next useful agent actions.
--json
emit machine-readable JSON
--refresh
refresh remote freshness fields before printing
--compact
omit null/default fields and redundant aliases
oak conflict status [--json]
Summarize any in-progress merge, pull sync, or mount pull conflict.
--json
emit machine-readable JSON
oak conflict show [--json]
Show per-path facts for any in-progress conflict.
--json
emit machine-readable JSON
oak conflict take PATH [--ours | --theirs] [--json]
Resolve one checkout conflict marker file by taking one side.
PATH
repo-relative, cwd-relative, or absolute conflicted file path
--ours
keep this branch's side of every marker block
--theirs
keep the parent/remote side of every marker block
--json
emit machine-readable JSON
Remote operations
oak login [-r URL]
Log in to an Oak server. Credentials are stored in the OS keychain.
-r, --remote URL
server URL (default: https://oak.space)
oak logout [-r URL]
Log out of an Oak server.
-r, --remote URL
server URL (default: https://oak.space)
oak whoami [-r URL]
Print the logged-in username for an Oak server.
-r, --remote URL
server URL (default: https://oak.space)
oak clone [ORG/REPO] [DEST] [--branch NAME] [--shallow] [--path PREFIX]
Clone a repository from the server. With no repository argument, opens an interactive picker. A git remote URL is accepted instead and imported into Oak. Repeat --path for a Perforce-style sparse (partial) clone scoped to those subtrees; manage the cone afterward with oak sparse.
ORG/REPO
repo spec; a bare name resolves to your personal org. Omit to pick interactively
DEST
destination directory (default: the repo name, git-style)
-r, --remote URL
server URL (default: https://oak.space)
--branch NAME
after cloning, switch to this remote branch
--shallow
clone only the most recent commit on the default branch
--path PREFIX
sparse clone: check out only files under this prefix (repeatable / comma-separated)
--full
hidden deprecated no-op accepted for compatibility
oak push [-r URL] [-f] [--repo ORG/REPO]
Push commits on the current branch to the remote. The remote repository is created automatically on the first push.
-r, --remote URL
server URL (default: https://oak.space)
-f, --force
overwrite remote history even when diverged
--repo ORG/REPO
link a fresh repo to an existing org/repo without the interactive picker
oak pull [-r URL] [-f] [--continue | --abort]
Bring the local clone up to date: fetch new commits on the current branch, then merge in changes from the parent branch.
-r, --remote URL
server URL (default: https://oak.space)
-f, --force
discard local commits not on remote and sync with remote HEAD
--continue
continue a parent-sync after resolving conflicts
--abort
abort a parent-sync in progress
oak fetch [-r URL]
Refresh the local copy of main from the remote without touching the working tree or running a merge.
-r, --remote URL
server URL (default: https://oak.space)
oak sparse [list | set PREFIX⦠| add PREFIX⦠| disable]
Manage a sparse (partial) checkout β Perforce-style. The cone is the set of path prefixes the working tree is scoped to: files outside it aren't written to disk or downloaded, but stay in the repo and are carried forward untouched by commits. With no subcommand, prints the active cone. set/add re-sync the working tree; widening reports any newly-included files that still need oak pull to hydrate.
set PREFIXβ¦
replace the cone with these prefixes (repeatable / comma-separated)
add PREFIXβ¦
widen the cone by these prefixes
disable
drop the cone and return to a full checkout
--json
machine-readable output for the default list view
oak serve [-d DIR] [-p PORT] [--token TOKEN]
Run a minimal self-hosted Oak server backed by SQLite. Intended for localhost or trusted networks.
-d, --dir DIR
data directory (default: ./oak-data)
-p, --port PORT
port to listen on (default: 8080)
--token TOKEN
optional shared bearer token, also via OAK_SERVE_TOKEN
Mount
oak mount ORG/REPO [DEST]
Mount a remote repository as a lazy virtual filesystem, detached in the background. Inside a mount, normal commands (oak status, commit, desc, diff, log, hash, push, pull) operate on the virtual branch. Hidden mount internals such as __serve, __resume, worktree hooks, and FSKit broker commands are implementation details and are intentionally not part of this public reference.
ORG/REPO
DEST
destination directory for a single -r, --remote URL
server URL (default: https://oak.space)
oak mount list [--json]
List active mounts.
--json
emit machine-readable JSON
oak mount finish [DEST] --desc-file FILE [--json]
Finalize a mounted task as a preflighted saga: validate blockers before mutation, set the branch description from FILE, checkpoint any pending overlay, push the virtual branch, then unmount and remove the directory. Inside a mount, oak finish --desc-file FILE is the same thing for the current directory.
DEST
mount directory (default: the current directory)
--desc-file FILE
read the final branch description from a UTF-8 file, or '-' for stdin
--json
emit one machine-readable JSON result on success or failure
oak mount end [DEST] [-f]
Unmount, drop local state, and remove the mount directory. With no DEST, tears down every mount under ~/oaktree. Refuses to discard uncommitted changes unless forced.
DEST
mount directory (omit to end every mount under ~/oaktree)
-f, --force
discard uncommitted overlay changes instead of refusing
oak mount forget DEST [-f]
Remove a mount's registry entry without unmounting or touching its on-disk state β for stale registrations left behind by a crash or reboot.
-f, --force
drop the registration even if the mount looks live
Spaces & sites
oak space new ORG [DEST]
Scaffold an agent space for an org β a directory where an agent works across the org's repos, one task per subdirectory. A legacy
ORG
org slug
DEST
directory to create the space in (default: ./-r, --remote URL
server URL (default: https://oak.space)
oak space repos [ORG]
List the repos in the space's org so you can pick which to mount for a task.
ORG
org slug to list (default: the current space's org)
-r, --remote URL
server URL (default: https://oak.space)
oak space clean [DEST] [-f]
Tear down finished mounts in a space β those whose working tree is clean. Dirty mounts are left alone unless forced.
DEST
space directory to clean (default: the current directory)
-f, --force
also tear down mounts with uncommitted changes, discarding them
oak site enable [--repo ORG/REPO] [--remote URL] [--branch BRANCH] [--source PATH]
Enable Pages-style hosting for an organization, serving the chosen repo's main branch.
--repo ORG/REPO
repo to publish; defaults to the current directory's repo
--remote URL
server URL, also via OAK_REMOTE
--branch BRANCH
branch whose head is served; currently main
--source PATH
repo-relative source directory (default: /)
oak site disable [--organization ORG] [--remote URL]
Disable Pages-style hosting for an organization.
--organization ORG
organization slug; defaults from the current repo
--remote URL
server URL, also via OAK_REMOTE
oak site show [--organization ORG] [--remote URL]
Show the site config for an organization.
--organization ORG
organization slug; defaults from the current repo
--remote URL
server URL, also via OAK_REMOTE
oak site list [--remote URL]
List all organization sites the caller can see.
--remote URL
server URL, also via OAK_REMOTE
Tools
oak archive [-o PATH]
Create a zip archive of the current directory.
-o, --output PATH
output file path (default: oak export DEST [-b BRANCH] [--git-branch NAME] [-f]
Replay this Oak repository's history into a fresh git repo at DEST β the documented escape hatch. Preserves original author and timestamp.
-b, --branch BRANCH
Oak branch to export (default: current branch)
--git-branch NAME
name for the resulting git branch (default: same as the Oak branch)
-f, --force
write into DEST even if it already contains files
oak open
Open the current repository in the web browser.
oak upgrade [-f] [--canary]
Upgrade the Oak CLI to the latest release.
-f, --force
skip confirmation
--canary
track the rolling canary channel instead of the latest stable release
oak maintenance compact
Compact the local database and VACUUM to reclaim space.
Releases
oak release new TAG [-t TITLE] [-n NOTES] [-c COMMIT] [--draft] [--prerelease]
Cut a new GitHub-style release. This command group is gated: set OAK_FEATURES=releases (or OAK_FEATURES=1) to use it.
TAG
free-form tag name, such as v1.0.0
-t, --title TITLE
human-friendly title (defaults to the tag)
-n, --notes NOTES
Markdown release notes
-c, --commit COMMIT
source commit hash
--draft
create as a draft
--prerelease
mark as a pre-release
oak release list
List releases for this repo.
oak release show TAG
Show release details.
TAG
tag name
oak release edit TAG [-t TITLE] [-n NOTES] [--draft BOOL] [--prerelease BOOL]
Edit a release's title, notes, draft state, or prerelease state.
TAG
tag name
-t, --title TITLE
new title
-n, --notes NOTES
new Markdown notes
--draft BOOL
flip the draft flag
--prerelease BOOL
flip the prerelease flag
oak release publish TAG
Publish a draft release.
TAG
tag name
oak release upload TAG FILES...
Upload one or more artifacts to a release.
TAG
tag name
FILES
one or more files to attach
oak release delete-asset TAG FILENAME
Remove an asset from a release.
TAG
tag name
FILENAME
asset filename
oak release delete TAG
Delete a release entirely.
TAG
tag name
Overview
Oak exposes a JSON HTTP API for scripting auth, repositories, branches, organizations, webhooks, ideas, sites, live streams, and releases β the same API the oak CLI is built on. Use it to wire Oak into CI, internal tooling, or your own agents.
All endpoints are rooted at:
https://oak.space/api
Requests and responses are JSON (Content-Type: application/json). Paths use the {owner}/{name} form, where owner is a username or organization slug.
Authentication
Authenticate with a personal API key sent as a bearer token:
Authorization: Bearer oak_β¦
Create and revoke keys from Settings β API keys, or over the API itself. A newly created key's full secret is returned once β store it somewhere safe; afterwards only its prefix is shown.
# List your repositories
curl https://oak.space/api/repos \
-H "Authorization: Bearer $OAK_API_KEY"
Unauthenticated requests can read public resources; anything that mutates state, or touches a private repo or organization, requires a key with access.
Conventions & errors
Successful responses return 200/201 with a JSON body. Errors return the appropriate status code (400, 401, 403, 404, 409) and a body of the form:
{ "error": "human-readable message" }
Auth
Account auth, GitHub OAuth/App callbacks, and personal API-key management. API keys authenticate other endpoints as Authorization: Bearer oak_...; POST /api-keys returns the full secret once.
/auth/signup
Create an account.
/auth/login
Log in and issue a session token.
/auth/forgot-password
Start password reset.
/auth/reset-password
Complete password reset.
/auth/github/start
Start GitHub OAuth.
/auth/github/callback
Handle GitHub OAuth callback.
/github/app/install
Start GitHub App installation.
/github/app/callback
Handle GitHub App install callback.
/github/connect/install
Start connecting GitHub to an existing Oak account.
/github/connect/callback
Handle GitHub connect callback.
/api-keys
List your API keys (prefixes only).
/api-keys
Create a key. Returns the full secret once.
/api-keys/{id}
Revoke a key.
Repositories & protocol
Repository CRUD plus the push/pull and chunk/blob protocol endpoints used by the CLI. POST /repos takes {"name", "description?", "is_public?", "organization_slug?"}.
/repos?sort=name|updated|created
List repositories you can access.
/repos
Create a repository.
/{owner}/{name}
Fetch a repository (name, head, visibility, β¦).
/{owner}/{name}
Delete a repository.
/{owner}/{name}/visibility
Set public/private.
/{owner}/{name}/transfer
Transfer the repo to another organization.
/{owner}/{name}/push
Push commits, branches, and object references.
/{owner}/{name}/pull
Pull repository state and branch/object metadata.
/{owner}/{name}/size
Storage used by the repository.
/{owner}/{name}/blobs/check
Check which blob objects the server already has.
/{owner}/{name}/chunks/check
Check which content chunks the server already has.
/{owner}/{name}/chunks/download
Download a batch of chunks.
/{owner}/{name}/blobs/info
Fetch blob metadata.
/{owner}/{name}/chunks/uploaded
Confirm chunks were uploaded.
/{owner}/{name}/chunks/batch
Upload a batch of chunks.
/{owner}/{name}/chunks/{hash}
Upload one chunk by hash.
Branches
POST β¦/branches takes {"name", "description?", "parent_branch?"}. Merging a branch parented on main squashes it onto main, carrying the branch description as the commit message.
/{owner}/{name}/branches
List branches.
/{owner}/{name}/branches
Create a branch.
/{owner}/{name}/branches/{brname}
Fetch one branch.
/{owner}/{name}/branches/{brname}/merge
Squash-merge the branch onto its parent.
/{owner}/{name}/branches/{brname}/sync
Update the branch from its parent.
/{owner}/{name}/branches/{brname}/rename
Rename a branch. Temporarily disabled (returns 503) pending a storage-integrity fix.
Commits & content
Inspect history and read file content at any commit. Trees and raw files are addressed by commit hash.
/{owner}/{name}/commits/{hash}/revert
Revert a commit.
/{owner}/{name}/commits/info
Look up commit metadata by hash.
/{owner}/{name}/tree/{commit}
List the root tree at a commit.
/{owner}/{name}/tree/{commit}/{*path}
List a subtree at a commit.
/{owner}/{name}/raw/{commit}/{*path}
Download a file's bytes at a commit.
Organizations & audit
Create and manage shared organizations, members, storage, and audit logs. Member roles are owner, admin, and member.
/orgs
List your organizations.
/orgs
Create an organization.
/orgs/{slug}
Fetch an organization.
/orgs/{slug}
Update name or description.
/orgs/{slug}
Delete an organization.
/orgs/{slug}/members
List members.
/orgs/{slug}/members
Add a member.
/orgs/{slug}/members/{username}
Change a member's role.
/orgs/{slug}/members/{username}
Remove a member.
/orgs/{slug}/storage
Organization storage usage.
/orgs/{slug}/audit
List organization audit events.
Webhooks
Register per-repository webhooks fired on branch.created, push, and merge. Discord URLs are detected and posted as channel messages; other URLs receive a JSON payload signed with X-Oak-Signature-256 when a secret is set.
/{owner}/{name}/webhooks
List a repo's webhooks.
/{owner}/{name}/webhooks
Create a webhook.
/{owner}/{name}/webhooks/{id}
Delete a webhook.
Ideas
Repository idea boards for prompts-to-do-work. Agents usually create and patch cards; humans claim and reorder them. A board can be enabled per repo and made public (anyone signed in may submit) even on a private repo β see repo settings.
/{owner}/{name}/ideas
List idea cards.
/{owner}/{name}/ideas
Create an idea card.
/{owner}/{name}/ideas/images
Upload an inline image (raw bytes; Content-Type names the format). Returns a markdown image URL.
/{owner}/{name}/ideas/{id}
Update an idea card.
/{owner}/{name}/ideas/{id}
Delete an idea card.
/{owner}/{name}/ideas/{id}/claim
Claim an idea card.
/{owner}/{name}/ideas/{id}/reorder
Move an idea card.
Sites
Manage Pages-style organization site config. Serving itself lives in the web host middleware; these routes manage the stored site row.
/orgs/{slug}/site
Fetch an organization's site config.
/orgs/{slug}/site
Create or replace an organization's site config.
/orgs/{slug}/site
Disable an organization's site.
/sites
List sites the caller can see.
Live streams
Server-sent event streams for long-running browser workflows.
/imports/{id}/stream
Stream GitHub import progress.
Releases
Oak CLI distribution endpoints for listing, uploading, downloading, and verifying release artifacts.
/releases
List uploaded CLI releases.
/releases
Upload a release artifact.
/releases/latest
Fetch latest release metadata.
/releases/{version}/{platform}
Download a release artifact.
/releases/{version}/{platform}/sha256
Fetch artifact SHA-256.
/releases/{version}/{platform}/minisig
Fetch artifact minisign signature.
This page is for people who want to know how Oak actually works under the hood β object model, chunking algorithm, and how it compares to the VCSes that influenced it. Oak is written in Rust; the CLI ships as a single static binary.
Manifests and commits, like Mercurial
Oak's object model is closer to Mercurial's design than Git's. There are no tree objects β each commit points at a flat manifest that records the full directory snapshot as a list of (path, blob_hash, mode) tuples.
Everything is content-addressed via BLAKE3. A blob's identity is its hash; storing the same file twice costs one chunk write. Diffs are computed by diffing two manifests β walk the path lists, compare hashes, done.
Branch descriptions (not commit messages) are the narrative layer. Local commits don't carry messages; only the squash commit that lands on main gets one, derived from the branch description. This keeps the history clean without requiring rebase discipline.
FastCDC for content-defined chunking
Large files are split into variable-length chunks using FastCDC, a content-defined chunking algorithm. Cut points are determined by the content of the file itself β not fixed offsets β so inserting a line near the top of a 100 MB file only invalidates a handful of chunks around the insertion point, not everything after it.
Git stores each version of a large file as a separate blob and relies on pack-file delta compression after the fact. Oak's chunking happens at write time, so deduplication is immediate and works across files that share content β two binary assets with a common header share those chunks in storage.
Oak's defaults produce chunks roughly in the 256 KB β 4 MB range (FastCDC's normalization keeps the distribution tight around the target). Each chunk is hashed independently with BLAKE3, and the manifest records the ordered chunk list per blob. Push and pull transfer only the chunks the remote is missing.
How Oak differs from Fossil
Fossil SCM is the closest prior art to Oak in philosophy: both use a single local database file instead of a loose-object store, and both treat the repository as a first-class artifact you can back up with cp. Oak local repos use the same SQLite-backed approach β the manifests, blobs, and commits live in a single local database file.
Where they diverge:
- Large-file handling. Fossil stores file content inline in SQLite, which caps out on large binaries. Oak uses content-defined chunking (FastCDC) and spills large chunks to external storage keyed by BLAKE3 hash β large binary assets are a first-class citizen, not a workaround.
- Object model. Fossil's artifact format is a custom text encoding. Oak uses flat manifests (path β blob hash), closer to Mercurial's design. Simpler to reason about; no need to parse a specialized artifact grammar to understand repo state.
- Branching. Fossil uses a timeline model where branching is a tag on a commit. Oak has explicit named branches with a parent-branch relationship β branches are first-class, not timeline annotations.
- Scope. Fossil bundles a wiki, bug tracker, and forum into the repository. Oak is VCS-only β use the tools you already have for issue tracking and documentation.
- Agent workflows. Oak is designed for branch-per-agent patterns:
oak clone, commit on a personal branch,oak push, open a PR. Fossil's model predates this use case.
Fossil's biggest win is its self-contained philosophy β Oak borrows that. The rest is a different set of trade-offs aimed at a different problem.
Built with HTMX, running in Oregon
The web UI is server-rendered HTML powered by HTMX. Most interactions are partial-page swaps: a button click sends a request, the server returns a fragment, HTMX swaps it into the DOM. No client-side framework, no build step, no JavaScript bundle. The server is a single Rust binary (Axum) deployed on a Linux host in Oregon.
Questions about Oak's internals, or interested in working on it? Email [email protected] or reach out on Discord.