← Facundo

June 20, 2026

Designing the Agent's Environment: Workspace, Runtime, and Directories


This is the first in a series of documents where I work through the architecture of an agent-orchestration library I'm building. This first one is about the environment in which the agent runs.

The problem with one workspace is that it forces very different kinds of things to live in the same place: the code the agent works on, the artifacts it produces that I want to look at, and the secrets I hand it.

For this I defined three directories:

.artifacts/
	agents/
		<agent-id>/
	common/
.secrets/
	agents/
		<agent-id>/
	common/
.workspaces/
	<repo>--<agent-id>/

With this split each kind of thing gets treated the way it actually needs to be, and the workspace is free to be whatever the agent needs without dragging secrets and artifacts along with it. Secrets can be made read only, so the agent can use them but not modify or delete them. Artifacts live on their own, separate from the workspace, so I can track them, diff them, or look at just what the agent produced without wading through every edit it made to get there.

Having a separate secrets directory makes credential handling simple. Secrets now have a known home, getting a credential to an agent is just a matter of putting the file in the right place, and the runtime knows where to look for it.

In practice it works like this:

  1. An init --copy-credentials subcommand reads the credentials I already have on my machine, the ones each tool writes to its own spot in my home directory (~/.claude/.credentials.json, ~/.codex/auth.json, ~/.config/gh/hosts.yml, and so on), and stages copies in the common secrets directory so they're shared across agents.
  2. When an agent starts in a container, the image looks for credentials first in /secrets/agent, then in /secrets/common, and copies whatever it finds back to the path the tool expects. So authentication just happens, without me logging in inside every container I spawn.

There's also a doctor subcommand that checks for this and tells me when something's missing, like a provider with no auth file or no GitHub PAT.

The same mechanism handles environment variables. Drop an .env file in the secrets directory and the runtime reads it and exports the values into the agent's session.

Agent Identifiers

I wanted a quick way to tell whether a branch or workspace is owned by an agent at all, and if so, which one.

  • constant aiagent-: A branch or directory that starts with aiagent- is an agent's. I can spot it at a glance, filter for it (git branch | grep aiagent-), or clean up agent workspaces by the prefix alone, without it ever being confused with a branch a person made.
  • name <agent-name>: Names are readable and you choose them, which makes them the part you actually recognize when scanning a list. But I wanted something more, like an id.
  • letter <agent-letter>: unique and auto-incremented, starting from a, and never reused. Once you pass z it rolls over to aa, ab, and so on. The letter does two things the name can't:
    • It is guaranteed unique, so it disambiguates two agents that happen to share a name. It is short, which matters once it shows up in branch names, directory names, and everywhere else an agent gets referenced.
    • Letters are handed out in order, so a higher letter means a more recently created agent.

Put together, the id is:

aiagent-<agent-name>-<agent-letter>

The same three parts name the agent's branch, with / instead of -, since that's how git namespaces branches:

aiagent/<agent-name>/<agent-letter>

So every agent branch lives under aiagent/, which is what makes the "is this branch an agent's?" check trivial: it's any branch under that prefix.

Inside the agent directories the prefix is dropped, since everything there is already an agent's:

workspace:  .agents/.workspaces/<agent-name>-<agent-letter>/
artifacts:  .agents/.artifacts/agents/<agent-name>-<agent-letter>/
secrets:    .agents/.secrets/agents/<agent-name>-<agent-letter>/

The prefix only earns its place where agent work sits next to human work, like git branches. In a directory that already holds nothing but agents, aiagent- would just be noise.

Agent Environment

  • workspace: where the code lies
  • runtime: where the agent lies

Workspace

The workspace is the part that makes sure there is somewhere for the agent to work. In practice that means a few steps:

  1. Directories: create the workspace and wire up the artifacts and secrets directories from before.
  2. Git: clone or set up a worktree, create the branch, and so on.
  3. Skills wiring: skills are just files, so they get linked into the agent's environment. I'll go into this in a later document.
  4. on_provision: a hook for any custom commands the user wants to run when the workspace is set up.

Step 1: Artifacts and Secrets

The artifacts and secrets live outside the workspace, in the shared .artifacts and .secrets trees from before. The agent needs to reach them from inside its workspace, and the way it reaches them can't depend on where it's running.

On a container I can mount the directories, so the agent sees them at a path like /artifacts and /secrets. On the host there's no mount. The agent would have to climb out of its own workspace with something like ../../.artifacts/..., which is ugly, and it broke during testing. Worse, those are two different paths, so the agent would need different instructions depending on which runtime it's in.

The fix is to give the agent one fixed path that works everywhere, and put the runtime difference underneath it. During provisioning the workspace creates four symlinks at a fixed location inside the workspace:

<workspace>/.agents/
    .artifacts/
        agent/   → this agent's artifacts
        common/  → shared artifacts
    .secrets/
        agent/   → this agent's secrets
        common/  → shared secrets

Each symlink resolves differently depending on the runtime. On the host it points up to the real shared tree (<repoRoot>/.agents/.artifacts/agents/<agent-name>-<agent-letter>/). In a container it points to the mount (/artifacts/agent). The agent only sees the fixed path, which it gets through environment variables:

AGENTS_ARTIFACTS_DIR        = <workspace>/.agents/.artifacts/agent
AGENTS_COMMON_ARTIFACTS_DIR = <workspace>/.agents/.artifacts/common
AGENTS_SECRETS_DIR          = <workspace>/.agents/.secrets/agent
AGENTS_COMMON_SECRETS_DIR   = <workspace>/.agents/.secrets/common

So the agent reads AGENTS_ARTIFACTS_DIR and writes there, the same way in every environment.

Step 2: Git Modes

The git step changes depending on the mode. There are four:

  • clone: a full clone of the repo.
  • worktree: a git worktree sharing the main repo, on its own branch.
  • self: no separate workspace, the agent works in the current repo as is.
  • none: a managed directory with no git repo at all, for agents that don't need a code repository.

The environment decides where the workspace physically lives. There are two:

  • filesystem: the workspace is a directory on the host
  • docker: the code lives inside a docker volume instead.

Not every mode is valid in every environment:

env \ modeworktreecloneselfnone
filesystem
docker✗ (host-only)✗ (no "self" in a container)

Runtime

The runtime is the environment the agent process actually runs in. There are two we care to support:

  • host: the agent runs as a subprocess in the main environment.
  • container (docker): the agent runs inside a docker container.

This covers the environment: the directories that keep the agent's different concerns apart, the identifiers that make agent-owned work recognizable, and the workspace and runtime that get an agent ready to run.

The next thing is how you actually drive all this, the config that declares the agents and the commands that bring them up (agents init, then agents provision atlas, agents start atlas). That's the next document.