Skip to content

Configuration

devtwin has no global configuration file. Everything is driven by one JSON file you pass with --pilot: either a pilot file for a disposable setup, or the live config at ~/.devtwin/live.json. The JSON schemas live in the kit’s schemas/ folder.

A pilot file declares the only places devtwin may write and the repositories it may read. It is a write fence for the CLI, not a sandbox for your whole agent.

{
"schemaVersion": 1,
"pilotRoot": "/private/tmp/devtwin-demo/pilot",
"configRoot": "/private/tmp/devtwin-demo/pilot/config",
"dataRoot": "/private/tmp/devtwin-demo/pilot/data",
"stateRoot": "/private/tmp/devtwin-demo/pilot/state",
"inputsRoot": "/private/tmp/devtwin-demo/pilot/inputs",
"agentHomes": ["/private/tmp/devtwin-demo/pilot/homes/demo"],
"repos": ["/private/tmp/devtwin-demo/pilot/repos/atlas"],
"forbidden": ["/Users/<you>/src/devtwin"]
}
KeyTypeMeaning
schemaVersion1Required.
pilotRootpathRequired. The folder that contains everything below.
configRootpathRequired. Scopes, project bindings, registry, user.json, personal profile.
dataRootpathRequired. Records, proposals, drafts, digests, exports, skill workspaces and staging.
stateRootpathRequired. Locks, run journals, approvals, stack state.
inputsRootpathRequired. Where you put files devtwin reads: --body-file, --candidate, seeded templates, hubs.
agentHomespath[]Required (may be empty). Fake agent homes that agents install --home may target. Must be empty in a live config.
repospath[]Required (may be empty). Repositories that may be onboarded.
forbiddenpath[]Required (may be empty). Extra paths devtwin must never touch, such as custom agent folders.
liveobjectOnly in a live config. See below.

No other keys are allowed.

  • All paths must be absolute and canonical (no symlinked components). On macOS use pwd -P or realpath, because /tmp is a symlink to /private/tmp.
  • In a disposable pilot, the five roots, agentHomes and repos must all be strictly inside pilotRoot, and none may overlap another. In a live config, repos and the skill folders are instead outside the store.
  • Nothing may touch a protected path: ~/.claude, ~/.codex, ~/.agents, ~/.gemini, ~/.cursor, ~/.commandcode, $CLAUDE_CONFIG_DIR, $CODEX_HOME, ~/.config/devtwin, ~/.local/share/devtwin, ~/.local/state/devtwin, the former defaults ~/.config/devctx, ~/.local/share/devctx and ~/.local/state/devctx (kept protected during the rename transition), ~/.local/state/personal-os, or the kit checkout itself.
  • The private roots must not be inside a Git worktree.
  • Changing the pilot file changes its digest. Some stored receipts are bound to that digest and will then refuse with DEVTWIN_GATE_REQUIRED.

A violation fails with DEVTWIN_GATE_REQUIRED and details.field naming the offending key (and details.protected naming the protected path, if any).

The live config is a pilot file with a live block. live init writes it; you never edit it by hand. See Live mode.

{
"schemaVersion": 1,
"pilotRoot": "/Users/<you>/.devtwin/store",
"configRoot": "/Users/<you>/.devtwin/store/config",
"dataRoot": "/Users/<you>/.devtwin/store/data",
"stateRoot": "/Users/<you>/.devtwin/store/state",
"inputsRoot": "/Users/<you>/.devtwin/store/inputs",
"agentHomes": [],
"repos": ["/Users/<you>/src/atlas"],
"forbidden": [],
"live": {
"configId": "cfg_0123456789abcdef",
"home": "/Users/<you>",
"skillDirs": {
"claude": "/Users/<you>/.claude/skills",
"codex": "/Users/<you>/.agents/skills"
},
"kitRoot": "/Users/<you>/.devtwin/kit",
"kitCommit": "<40-character commit>"
}
}
live keyMeaning
configIdRandom ID drawn by live init. Pattern ^[a-z0-9][a-z0-9_-]{7,63}$. Install receipts bind to it.
homeMust equal your process home.
skillDirsOne or both of claude (~/.claude/skills, or $CLAUDE_CONFIG_DIR/skills) and codex (~/.agents/skills). Each must already exist as a real directory.
kitRootThe kit clone live commands must run from.
kitCommitThe commit that clone must be detached at, clean. 40 or 64 hex characters.

Live mode relaxes the fence for exactly these named skill folders (only devtwin-* entries inside them) and the listed repositories (read-only). The approved digest is stored at store/state/live/approved-config.json; a live.json that differs from it is refused.

A live config can hold an optional top-level writeClasses block. It names the only other paths outside the store that devtwin may write. Each class is a list of exact absolute paths, at most 16 per class.

"writeClasses": {
"hubRoot": ["/Users/<you>/.devtwin/hubs"],
"hookTargets": ["/Users/<you>/.claude/settings.json", "/Users/<you>/.codex/hooks.json"],
"launchAgents": ["/Users/<you>/Library/LaunchAgents/devtwin.hub-sync.example.plist"]
}
ClassAllowed pathsWritten by
hubRootFolders inside your home for hub clones. Writes are allowed in the folder and below it.hub join, hub sync, hub leave
instructionTargets~/.claude/CLAUDE.md (or $CLAUDE_CONFIG_DIR/CLAUDE.md), ~/.codex/AGENTS.md, and a devtwin*.md file directly in one of those folders.No command yet
hookTargets~/.claude/settings.json (or $CLAUDE_CONFIG_DIR/settings.json) and ~/.codex/hooks.json.agents install --hooks, agents uninstall --hooks
launchAgentsFiles named devtwin.<name>.plist directly in ~/Library/LaunchAgents.hub schedule
  • Set the classes only with live write-classes. The classes are part of the approved config digest, so a hand edit is refused with DEVTWIN_GATE_REQUIRED.
  • Every path must be canonical and inside your home. A link, or a path whose folder is a link, is refused. A hub root must not overlap the store, the kit, a listed repository, a skill folder or another hub root.
  • In a settings or instruction file, devtwin changes only its own JSON entries or the text between <!-- devtwin:begin --> and <!-- devtwin:end -->. It backs up the exact prior bytes to store/state/runs/<run_id>/backups/ first, and the file keeps its own mode.
  • A disposable pilot can list classes too. Its file targets must have the same shapes inside a listed agentHomes folder, and its hub roots must be inside pilotRoot.

init --preference stores working preferences in configRoot/user.json, alongside the actor references from --actor:

KeyValues
explanationStyleconcise or detailed
diagramsprefer, avoid or neutral
notesup to 10 one-line notes of up to 200 characters

Capped at 2,048 bytes. The personal profile overrides any field it also sets.

scope add writes configRoot/scopes/<scope>.json:

{"schemaVersion":1,"id":"example","displayName":"example","hubs":[],"policies":[],"accounts":[],"createdAt":"…"}

--hub <id>=local-dir:<absolute path>[:<purposes>] registers a local folder of company knowledge. The path must be under the pilot’s inputsRoot. <purposes> is an optional comma-separated list of lowercase words.

Terminal window
--hub 'team-notes=local-dir:/absolute/path/to/pilot/inputs/hub'

--policy <pol_id>=<hub id>:<relative path>[:required] names a policy file inside a hub. Policy text is added to briefs for that scope. A required policy that cannot be read fails the brief with DEVTWIN_POLICY_UNAVAILABLE.

Terminal window
--policy 'pol_security=team-notes:policies/security.md:required'

The policy ID is pol_ plus 3–40 lowercase letters, digits or -. Every policy must reference a hub given in the same command.

accounts is always written as []. It is reserved for identity routing, which is not shipped yet; nothing reads it today.

The profile lives at configRoot/profile/ as numbered revisions (v<generation>.json) plus a current.json pointer. Write it only with profile apply. Schema: schemas/profile.schema.json.

FieldTypeLimits
workKindstringone line, ≤ 200
workingPreferences.explanationStyleenumconcise, detailed
workingPreferences.diagramsenumprefer, avoid, neutral
workingPreferences.autonomyenumask-first, balanced, autonomous
workingPreferences.notesstring[]≤ 10 items, ≤ 200 each
stackCurrent, stackKnownstring[]≤ 40 items, ≤ 80 each, name[=version][:care]
writing.tonestring≤ 120
writing.formalitymap≤ 10 audiences (^[a-z][a-z0-9-]{0,39}$) → formal, neutral, casual
writing.greeting, writing.signOffstring≤ 80
writing.lengthenumshort, medium, long
writing.spellingstringxx or xx-XX, such as en-GB
writing.avoidPhrasesstring[]≤ 20 items, ≤ 80 each

The canonical JSON is capped at 4,096 bytes. Every field is optional.

The bundled catalog catalog/stack-sources.json maps item names (and aliases such as nodejs or postgresql) to GitHub repositories, endoflife.date products and package identifiers. Add your own with stack plan --feed and --map. Once approved, overrides are stored with the approval in stateRoot/stack/approval.json and carry forward to later plans until you pass new ones or --clear-overrides. See Stack watch.

VariableRead byEffect
DEVTWIN_KITBundled skillsAbsolute path of the kit checkout. The skills refuse to run without it. The CLI does not use it to find the kit; agents status and live migrate only check it to report a leftover legacy variable.
DEVCTX_KITBundled skills, CLI notesLegacy name, read for one transition. The skills fall back to it only when DEVTWIN_KIT is unset. agents status and live migrate add a LEGACY_KIT_ENV note while it is set. See Migrate from devctx.
CLAUDE_CONFIG_DIRCLIIf set, Claude’s skill folder is $CLAUDE_CONFIG_DIR/skills, and the directory is protected.
CODEX_HOMECLIProtected. It does not change where Codex skills are installed; live previews warn CODEX_HOME_IGNORED.
HOMECLIUsed for protected paths. In live mode it must equal the live config’s home.

devtwin reads no token or credential from the environment today.