Git-backed workspace

June 21, 2026 · View on GitHub

Every write to the workspace — saving a worker, editing a context, updating workspace instructions — creates a git commit. Version history is git log. Rollback is git checkout. There is no separate versioning system.

Workspace layout

{WORKEROS_WORKSPACE_DIR}/          ← git root (WORKERS_DIR parent by default)
  workers/{worker_id}/
    worker.yml                     ← manifest (name, description, inputs, triggers, …)
    run.py                         ← entrypoint
    SKILL.md                       ← system prompt
    requirements.txt               ← optional Python deps
  contexts/{context_name}/
    {files…}                       ← arbitrary context files
  workspace.md                     ← workspace instructions (live)
  workspace.base.md                ← editable base persona
  workspace-tools.yml              ← MCP tool registrations (serialised)
  .floom/issues/
    ISSUE-0001.md                  ← workspace issue (YAML frontmatter + body)
    ISSUE-0001.comments.ndjson     ← append-only comment log
  .gitignore                       ← excludes *.env, workeros.db, .venv, __pycache__

Not tracked: .secrets.enc, *.env, workeros.db*, __pycache__. Secrets have their own encrypted store.

WORKEROS_WORKSPACE_DIR defaults to WORKERS_DIR.parent (one level above the workers directory). Override with the WORKEROS_WORKSPACE_DIR env var.

How commits happen

git_ops.commit_paths(workspace_dir, rel_paths, message) is the single write path. The engine calls it from main.py after every mutation:

  • Worker save → commit_paths(…, ["workers/{id}"], "feat(worker): …")
  • Context write → commit_paths(…, ["contexts/{name}"], "feat(context): …")
  • Workspace instructions update → commit_paths(…, ["workspace.md"], "…")

commit_paths does git add -- {rel_paths} then git commit. If nothing changed (identical content), it skips the commit and returns the current HEAD sha. Returns the 7-char short sha of the resulting commit.

Rollback

git_ops.checkout_path(workspace_dir, sha, rel_path) restores rel_path to its state at sha via git checkout {sha} -- {rel_path}. This modifies the working tree only — the caller is responsible for calling commit_paths afterwards to record the rollback as a new commit in history.

get_file_at_sha(workspace_dir, sha, rel_path) reads file content at a given sha without touching the working tree. Used by the versions API.

list_files_at_sha(workspace_dir, sha, prefix) lists all paths under prefix at a given sha. Used by rollback to know which files to restore.

Version history

git_ops.get_log(workspace_dir, rel_path, limit) returns commits that touched rel_path, newest first. Each entry: {id, sha, message, author, timestamp, asset_type, asset_id}. Both id and sha are 7-char short hashes.

Remote sync (optional)

If WORKEROS_GIT_REMOTE is set, the engine calls configure_remote(workspace_dir, remote_url) on startup and push_background(workspace_dir) after every commit. push_background runs in a daemon thread — a transient push failure is logged at DEBUG and never surfaces to the user.

clone_or_init(workspace_dir, remote_url) is used on a fresh install when a remote is already configured: it clones the remote so the full history arrives intact rather than starting blank.

Host hook: workspace_id resolver

In self-hosted single-tenant mode the git root is a single directory shared by the one user. A downstream multi-tenant host can give each workspace its own isolated git root.

set_workspace_id_resolver(fn) registers a callable that returns the active workspace_id for the current request. When set, the host uses the returned value to scope the git root to the right per-workspace directory. In self-hosted mode this resolver is never registered and all functions receive the workspace dir directly as a parameter.

A downstream host can register this at startup:

import git_ops
git_ops.set_workspace_id_resolver(get_active_workspace_id)

Initialisation

ensure_repo(workspace_dir) initialises the git repo if one does not exist. Creates the default .gitignore, sets user.email = workeros@local, user.name = Floom, and makes an initial commit of any pre-existing files. Returns True if it initialised, False if already a repo.

Call ensure_repo once at startup before any commit_paths call. The API server does this in its startup sequence.