nextv1.4.0
Repos

Workspaces

Declare a root repository with member submodules in repos.yaml, and what it means for sandboxing and publication.

A workspace is a root repository with other repositories mounted in it as submodules, declared beside the repos: list in repos.yaml.

Entries are named by id:

workspaces:
  product:
    root: product
    root_pointer_default: ignore     # or bump
    members:
      api: { repository: api, path: services/api }
KeyMeaning
workspaces.<name>The workspace's name.
rootThe id of the root repository's repos: entry.
root_pointer_defaultignore (default) or bump: what happens to the root's submodule pointers after members merge. See Publication.
members.<name>.repositoryThe id of the member's repos: entry.
members.<name>.pathWhere the member mounts in the root.

Connecting a repo that has submodules declares its workspace for you, each submodule a member. A submodule you already connected on its own, as a clone elsewhere with the same origin, is that member, settings and all; Kraft adds an entry for the submodule's own checkout only when it finds none. When you file a work item against the root, you choose which members it changes and its root-pointer policy (the workspace's default unless you pick one); both are frozen into the item. The item's worktree holds exactly those members, each on the item's branch, and a task with scope: each_repository runs once per selected repository.

Kraft checks each member out as a worktree of its connected repository, the same way it checks out the root. So the item's branch exists in that repository too. Abandoning or archiving the item deletes the member's worktree and branch there, as long as the member is still connected; if you disconnect or move it first, remove them by hand. A member whose repository isn't connected any more is checked out with git submodule update instead.

Every member mounts directly in the root. repos.yaml fails to load, and filing an item against that workspace is refused, when one member's path is inside another member's (libs/a/vendor/x inside libs/a) or two members share a path. The error names both members. A submodule nested in a member is part of that member's repository, so leave it out of members:.

Each selected repository binds the tasks that run in it with its own policy layer. A task in the assembled worktree, which holds all of them at once, runs under the tightest of their layers: allowlists intersect, deny lists add up, and numbers take their minimum.

Sandboxing

A workspace with members can be sandboxed like a single repository. A sandbox on the root or on any member (sandbox or policy.sandbox), or one that comes from a chain, node or task, wraps the item's whole checkout. The container sees each member as it sees the root: the member's refs go through a ref store of its own, and the files git trusts for it are read-only. Before Kraft runs git in a member, it checks that the member is still the checkout Kraft made from its connected repository. If it isn't, the item stops for a person, naming the member. A member whose repository isn't connected stops a sandboxed item too: connect it, then retry.

A sandboxed worker on a plain repository can still create a git repository of its own inside its worktree, and commit a gitlink to it. That nested repository's config belongs to the worker. Kraft's own git therefore never works inside a nested repository. Its status and diff calls compare only the commit a gitlink records, and its automatic commit of leftover work skips nested repositories. The daemon pins submodule.recurse, fetch.recurseSubmodules, push.recurseSubmodules, diff.submodule, status.submoduleSummary and diff.ignoreSubmodules, whatever your own git config says. If a sandboxed item's worktree holds a nested repository Kraft did not create, the item stops for a person, and the stop names the paths. That includes an untracked one, a populated gitlink, or a gitlink the branch added or moved. Remove them, or git rm --cached the gitlinks, and retry. A submodule your repository already had, left unpopulated, doesn't stop anything.

While one of a sandboxed item's sessions is still running, Kraft runs no git in its worktree at all. When one of those sessions ends, Kraft may move the item's branch in your repository to the commit the worker left, through the ref store, without reading the worktree. The diff view answers that the diff is available once the sandboxed session ends. A task that needs the review package stops for a person, and the sweep of leftover work waits for the last task of the step.

Under a sandbox, the clean check before a merge request no longer looks for uncommitted edits inside a submodule. It still catches a submodule whose commit moved, and each declared workspace member is checked on its own. Leftover work is no longer committed into a submodule's pointer unless that submodule is one of the item's declared members.

Publication

Publication goes members first. A member's merge request merges before the root moves. A root with source changes of its own gets its own merge request, and it stays a draft until every member has merged and the root names their merged revisions. A root whose only change is the members' pointers follows the item's root-pointer policy: ignore leaves it alone, and bump pushes the new pointers to its default branch. If that push is refused, it opens a merge request for them instead. A member that fails to merge stops publication and leaves the root untouched.

A repos.yaml from before workspaces still loads, but an item with submodules needs a declared workspace. Declare it in workspaces:, or reconnect the root, which loses its settings.

Copyright © 2026