Projects

Workspaces and hooks

Where each task's files live, how long they last, and how to run your own scripts when a workspace is created, used or removed.

Every task gets its own workspace directory. Agents work only there, and the sandbox mounts only that directory. Hooks let you run shell scripts at four points in a workspace's life, for example to install dependencies or seed a database.

Where workspaces live#

A task's workspace is <workspace.root>/<workspace key>:

  • workspace.root comes from WORKFLOW.md. The wizard writes ~/HarnessWorkspaces/<project>. Without it, each project gets <temp>/symphony_workspaces/<project>.
  • The workspace key is the task identifier with anything outside A–Z a–z 0–9 . _ - replaced by _. When the identifier had to change, a short hash is added so two tasks can never share a folder.

With Cloudflare sandboxes the files live in Cloudflare; the local folder is only a placeholder.

Lifecycle#

EventWhat happens
First dispatchThe workspace is created. With managed git, the project is cloned into it on the task branch. Then after_create runs.
Each attemptbefore_run runs, then the agent, then after_run. The branch is saved after every attempt.
RetriesThe same workspace is reused, so the agent sees its earlier work.
Terminal state (Done, Canceled)The branch is saved, before_remove runs, and the workspace is removed. A workspace whose branch couldn't be saved is kept.
App or server restartWorkspaces of tasks already in a terminal state are removed at startup.

The Workspaces tab lists a project's workspaces with their branch, whether it's saved to the repository, and whether an agent is using it. You can reveal a workspace in Finder, open a terminal or an editor there, or remove it (the branch is saved first; uncommitted changes are lost).

Hooks#

WORKFLOW.md
hooks:
  after_create: |
    npm ci
  before_run: |
    npx prisma migrate reset --force --skip-seed
  after_run: |
    rm -rf .next/cache
  before_remove: |
    echo "removing $SYMPHONY_ISSUE_IDENTIFIER"
  timeout_ms: 120000
HookWhenIf it fails
after_createOnce, right after the workspace is createdThe new directory is removed and the attempt fails
before_runBefore every attemptThe attempt fails (and is retried)
after_runAfter every attemptIgnored (logged)
before_removeBefore the workspace is removedIgnored (logged)

Each hook runs with bash -lc, in the workspace directory, with a timeout of hooks.timeout_ms (default 60 s). Hooks get the merged project and global environment, plus:

VariableValue
SYMPHONY_WORKSPACEThe workspace path (/workspace inside a sandbox)
SYMPHONY_WORKSPACE_KEYThe workspace key
SYMPHONY_WORKFLOW_DIRThe folder containing WORKFLOW.md (host hooks only)
SYMPHONY_HOOKThe hook's name
SYMPHONY_ISSUE_ID, SYMPHONY_ISSUE_IDENTIFIER, SYMPHONY_ISSUE_TITLE, SYMPHONY_ISSUE_STATEThe task
SYMPHONY_ISSUE_BRANCHThe task branch, symphony/<key>

Agent and gateway credentials (ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, CLAUDE_CODE_OAUTH_TOKEN, OPENAI_API_KEY, HARNESS_GATEWAY_API_KEY, LITELLM_API_KEY) and push tokens never reach hooks.

Where hooks run#

Project runs agents inHooks run
Docker (default sandbox.hooks: sandbox)In a disposable container from the agent image, with the same isolation. During an attempt they get the agent's network policy; otherwise no network at all. Only SYMPHONY_* variables, the names in docker.env and git identity variables are passed in.
CloudflareIn the task's Cloudflare sandbox, in /workspace.
Host, or sandbox.hooks: hostOn your machine, as your user, in the workspace directory.
Host hooks and sandboxed agents

With sandbox.hooks: host and Docker on, hooks run outside the sandbox in a directory the agent controls. Anything they run there (git especially, or project scripts like npm lifecycle hooks) reads agent-written files. The inspector shows a warning. Keep hooks in the sandbox unless you're sure.

See also#