Docs/Timebase Components/How Atlas Uses Git
AtlasConcept

How Atlas Uses Git

Timebase Atlas stores your model in a Git repository on the Atlas server's own disk.

Updated 14 Oct 2026Timebase Docs

Overview#

Timebase Atlas stores your model in a Git repository on the Atlas server's own disk. Every domain, every library, every instance map and every branch you see in the Atlas interface is a file, a directory or a Git ref inside that one repository, and every save you make becomes a Git commit. This article explains what is in that repository, where it lives, what Atlas does to it, and what you can and cannot do to it from outside.

It is written for administrators and technically-minded operators: the people who own backups, deployment and change control, and who get asked "is our model in source control?"

The one thing to take away: the repository is real Git and it is entirely local. Atlas has no concept of a remote. It cannot clone, push, fetch or pull, and there is no field in the interface or in settings.config where you would type a repository URL. Everything below describes a repository that lives only on the Atlas server's own disk, which is also why your backup of it is the only copy.

What Atlas keeps in Git#

One Atlas server serves exactly one data repository. That is a design assumption, written into the read path itself: the configured data root is authoritative and the repository path is not something a caller can vary per request.

The tracked contents are your model, and nothing else:

Path in the repository What it holds
domains/<domain>/ontology.jsonld One domain's types: entity types, relationship types, properties, data types, functions
domains/<domain>/instances/<map>.map.json That domain's instance maps
libraries/<library>.jsonld One library of reusable types, including the two that ship with Atlas
.gitignore Written and maintained by Atlas itself (see below)

Everything else in the configuration directory is kept out of Git. Atlas seeds and extends its own .gitignore on every start with these entries:

compiled-worlds.json
compiled-worlds.json.tmp-*
draft-sessions.json
draft-sessions.json.tmp-*
/.worktrees/
/agents/
/skills/
/views/

Four of those matter to you directly:

  • compiled-worlds.json is the record of which (branch, domain) pairs have been compiled. It is runtime bookkeeping, not model content. Committing it would add a junk commit on every restart and would break the way Atlas decides whether a compiled World is out of date.
  • /agents/ and /skills/ hold your Gage agent and skill definitions. They are not versioned. See the warning below.
  • /views/ holds saved views, one file per domain. It is user content kept beside the model but outside its history.
  • /.worktrees/ holds the working copies of open draft sessions.

Machine state (atlas.state, and the assistant's sessions, threads and exports) lives in the separate Data folder, so it needs no ignore rule. In 2.0.0 the assistant's sessions, threads and exports sat under Config and carried ignore rules; from the next release they live under Data, and the first start after upgrading moves them. The extra .gitignore lines an older install already has are harmless and stay.

Important: agent and skill definitions are held outside version control. Atlas excludes /agents/ and /skills/ on every boot, including untracking them from a repository that had them committed. Back those two directories up separately, so a deleted agent definition can be restored.

Where the repository lives on disk#

The repository root is the Atlas configuration directory. There is no separate "repository path" setting: the configuration directory is the repository.

Windows service install. When the Config path is not set, Atlas falls back to a fixed location under ProgramData:

C:\ProgramData\Flow Software\Timebase\Atlas\Config

So the Git directory itself is C:\ProgramData\Flow Software\Timebase\Atlas\Config\.git, and your domains are in ...\Config\domains\.

Docker. The Compose file in Start Here for Docker sets the configuration path with an environment variable and backs it with a named volume:

Setting Value
Environment variable Config=/atlas/config
Repository path inside the container /atlas/config
Volume mount point /atlas
Named volume atlas, which Docker prefixes with the project name as timebase_atlas

So in a default Docker deployment the whole repository lives in that named volume, under config/. To find it on the host:

docker volume inspect timebase_atlas

To take a backup without stopping Atlas, copy the whole config directory out of the volume, or use docker cp from the running container.

Note: the image's own default is Config=/config, and the Compose file overrides it. If you deploy the image without a Compose file, check which one you are getting before you go looking for the repository. Atlas logs the resolved path at startup as using config path: <path>.

What Atlas does to the repository every time it starts#

In order, on every startup Atlas:

  1. Warns about stray repositories. If any domains/<domain>/.git folder exists, Atlas logs a warning suggesting you remove it. Per-domain repositories are a leftover from a much older layout and are not supported.
  2. Force-syncs the mandatory library. atlas-core ships embedded in the Atlas assembly rather than as a file on disk. Atlas writes it into libraries/ if it is absent, and compares it byte-for-byte and overwrites yours if they differ. It is the engine's own vocabulary, so embedding it means a packaging mistake cannot leave a deployment without it.
  3. Seeds or extends .gitignore with the entries listed above. It only appends what is missing, so your own additional entries survive.
  4. Initialises the repository if there is none. It runs git init, renames the initial branch to main, stages everything and makes one commit with the message Initial import, authored as atlas-api <atlas@atlas.local>.
  5. Untracks agents and skills if the repository is an existing one.
  6. Commits anything uncommitted. If the working tree has changes after the steps above, Atlas stages everything and commits it with the message Auto-commit: seed data, again as atlas-api <atlas@atlas.local>. This is how a seed-library upgrade lands in history.
  7. Pins two repository settings in the repository's local config: gc.auto=0 and core.autocrlf=false. Both matter - see the operating notes below.
  8. Runs a libgit2 smoke test. Atlas reads HEAD and one blob through its native Git library. If that fails, startup is aborted rather than continuing in a degraded state, because the read path depends on it.

Warning: Step 2 means atlas-core is system-owned. Any edit you make to it is reverted at the next restart, and the revert is committed. Put your own types in your own library. atlas-common is not system-owned: it is an example library you import, and yours to edit afterwards.

Only those two are system-owned. Everything else Atlas can supply - isa95-equipment, atlas-functions, the other standards libraries and the example domains - is opt-in: it sits under examples/ and is copied into your repository when you ask for it during commissioning. Once copied it is yours, and no upgrade overwrites it.

How Atlas reads and writes the repository#

Reads and writes take completely different routes through Git, and knowing which is which explains most of the performance and locking behaviour you will observe.

Reads are pinned, worktree-free and lock-free. When Atlas serves a read for a branch, it resolves that branch name to a commit SHA once, then reads file contents straight out of the Git object database in-process, at that pinned commit. No checkout happens, no temporary branch is created, and neither the per-repository lock nor the per-branch lock is taken. Any number of readers therefore run in parallel, and they run in parallel with writers: a reader that pinned a SHA is immune to a writer moving the branch afterwards.

Writes use a short-lived worktree and serialise per branch. Every write follows the same sequence: take the branch lock, create a transient branch named v2-write/<branch>/<random> and a worktree for it under <data-root>/.worktrees/.v2/, load, mutate, save, commit, advance the real branch ref, tear the worktree down, release the lock. One write produces one commit. Two writers targeting the same branch queue behind each other; writers targeting different branches do not.

Commit authorship comes from the signed-in identity. The author name and email are built from the authenticated Pulse principal's sub and name claims, formatted as Name <sub@atlas.local>. When authentication is disabled, or a request arrives unauthenticated, writes are attributed to Anonymous <anonymous@atlas.local>. Background and startup operations use atlas-api <atlas@atlas.local>, and agent-driven writes are attributed to the agent's name.

Note: commit authorship comes from the authenticated caller. Give each pipeline its own Pulse client so its identity appears in the history; a request with no identity lands as Anonymous.

Draft sessions are created only when you need one. A save that validates cleanly is committed straight to the target branch and leaves nothing behind. A save that fails validation is adopted into a private per-person draft session on a v2-draft/... ref with a durable worktree under <data-root>/.worktrees/.v2-draft/, and the target branch does not move. So having a draft session means "I have unpublished work that does not validate". Draft refs are filtered out of the branch list and cannot be renamed or deleted directly.

What you can do with Git from inside Atlas#

Atlas has a Git item at the top of the left-hand navigation, above Domains and Libraries. That page is where a modeller does branch work: the branch rail, New branch, Rename, Delete, Compare and Merge.

The same operations exist on the machine-facing API under /api/v1/branches. Every route below needs a Pulse bearer token whose audience includes Atlas.

Operation Route
List branches GET /api/v1/branches
Create a branch POST /api/v1/branches with {"name": "...", "from": "..."}
Rename a branch PATCH /api/v1/branches/{branch} with {"newName": "..."}
Delete a branch DELETE /api/v1/branches/{branch}?force=true (or force=false)
Commit history GET /api/v1/branches/{branch}/commits?limit=&cursor=
Compare two branches GET /api/v1/branches/{branch}/compare?base={other}
Merge POST /api/v1/branches/{source}/merge with {"target": "..."}
Files a commit changed GET /api/v1/commits/{sha}/files
One file's before and after GET /api/v1/commits/{sha}/diff?path=...

A branch name has to be one Git accepts. Atlas validates against Git's own rules before it touches the repository, and refuses an illegal name with a message naming the specific rule rather than a generic failure. A name cannot:

  • be empty, be @, or contain @{
  • contain .., //, a space, a control character, or any of Git's reserved characters
  • begin or end with /, begin with -, or end with .
  • have any /-separated part beginning with . or ending with .lock

A backslash is refused for the same reason: Git forbids it in a ref name, so there is no name to create.

Four behaviours matter when scripting against these.

The default branch is protected. Atlas resolves one branch as the default: the Git:DefaultBranch configuration value if it is set and that branch exists, otherwise main, otherwise master, otherwise the longest-lived branch, otherwise alphabetically first. The default branch cannot be deleted (403, Branch.CannotDeleteDefault) and cannot be renamed (Branch.CannotRenameDefault), and force=true does not override either. Git:DefaultBranch is bound once at startup, so changing it needs a restart.

A conflicted merge comes back as a success. This is the single most important thing to state loudly on this page. Atlas merges with git merge <source> --no-ff --no-edit. Atlas can resolve two kinds of conflicted file on its own: a .jsonld file, by merging the @graph arrays, and the root commissioning.json marker, by taking the union of both sides' imported examples with the newer timestamp and version. If every conflicted file is one of those two, Atlas resolves them and completes the merge. In every other case it runs git merge --abort and returns:

HTTP/1.1 200 OK

{
  "success": false,
  "hasConflicts": true,
  "conflictFiles": ["domains/brewery/ontology.jsonld", "..."]
}

HTTP 200, "success": false. Request problems - an unknown branch, or merging a branch into itself - produce an error status; a conflict is reported in the body. Check success and conflictFiles there. A stopped merge leaves the target branch untouched, so nothing is half-applied.

Commit history is paged. GET /api/v1/branches/{branch}/commits accepts limit between 1 and 100, and any value outside that range, including omitting it, gives you 10. It is cursor-paged, so follow nextCursor until it comes back null to read the whole history.

Branch changes in the interface need an administrator. The web interface calls /app/branches rather than /api/v1. Reads there are open to any signed-in user; create, rename, delete and merge require an administrator role. Build integrations against the /api/v1 routes, which are the published surface.

Troubleshooting#

  • I edited a shipped library and my change disappeared after a restart. atlas-core is system-owned. Atlas compares your copy against the embedded copy on every start and overwrites yours if it differs, then commits the overwrite as Auto-commit: seed data. Create your own library instead. Libraries you took from examples/ during commissioning, atlas-common and isa95-equipment among them, are yours and are never overwritten, so this does not apply to them.
  • My merge reported success but nothing was merged. Check the response body, not the status code. A conflicted merge returns HTTP 200 with "success": false, "hasConflicts": true and a conflictFiles list. The merge was aborted, so the target branch is untouched and safe. Fix the listed files on the source branch, then merge again.
  • The commit history shows 10 entries. That is the default page size, applied to any limit outside 1..100. Pass a limit up to 100 and follow the nextCursor value until it comes back null.
  • I cannot delete or rename main. The resolved default branch is protected against both, and force=true does not override it. If your trunk is called something else, set Git:DefaultBranch to that name and restart, which moves the protection to the right branch. To retire the current default, make another branch the default first.
  • Atlas will not start and the log mentions a libgit2 native probe failure. The read path depends on the native Git library, so Atlas stops at startup rather than coming up unable to serve reads. This normally means an incomplete deployment - pull the image again or reinstall.
  • An agent or skill definition was deleted and I cannot find it in the history. /agents/ and /skills/ are held outside version control. Restore them from a file-level backup of the configuration directory.
  • I saved a change, compiled, and nothing changed. The save had outstanding violations, so it is held in your private draft session and the branch has not moved. Check the Outstanding items panel on the Overview screen, clear the violations, then publish the draft from the Unpublished changes panel. Compiling from a draft builds your copy and leaves the shared compiled World in place.
  • A branch is showing content I did not expect after someone ran Git commands on the server. Atlas owns the repository exclusively. If an external pull, reset or checkout moved a branch, the compiled World in memory is now out of date. Restart Atlas, which recompiles every recorded branch and domain at boot, and make model changes through Atlas rather than against the repository directly.
  • The repository has grown large and I want to garbage collect it. Stop Atlas first. gc.auto=0 is pinned so that a background repack cannot run underneath a live read. Run git gc against a stopped instance only, and take a copy of the configuration directory before you do.
Article Owns
Timebase Atlas Key Concepts The vocabulary: domain, library, branch, draft session, World, compile
Branch Management Creating, comparing, renaming, deleting and merging branches day to day
Compiling and Publishing a Domain The compile lifecycle, staleness and publishing a draft
Atlas API Overview The /api/v1 surface, authentication, error format and pagination
Start Here for Docker: How to Deploy and Secure Timebase 2.0 Installing Atlas, the data volume and first-run setup
Atlas Configuration Reference settings.config, the environment overlay and configuration precedence
esc
↑↓ navigate · ↵ open · ⌘K anywhere