standards
EnvironmentsAgents

Environments

Pick the harness that runs a conversation, a routine or an agent, and the machine it runs on — prepared once by a setup script, used in a copy every time.


An environment is a reference machine for one harness: Standards (our agent), Claude Code, Codex or Cursor. Picking an environment picks the harness. A custom environment adds the tools a setup script — and, if you want, a few minutes in a terminal — installed on top of that harness's base machine, saved as a snapshot. Anywhere you start work — a plain conversation, an agent, a routine — you pick an environment, and every use runs in a copy of it: nothing a run writes goes back to the environment.

Default environments

Four environments exist in every workspace without being created. They have no id and cannot be edited:

DefaultRunsAvailable
StandardsOur agent, in its usual sandbox.Always.
Claude CodeClaude Code on a clean machine.When the Secrets hold ANTHROPIC_API_KEY.
CodexCodex on a clean machine.When the Secrets hold OPENAI_API_KEY.
CursorCursor's agent CLI on a clean machine.When the Secrets hold CURSOR_API_KEY.

A harness without its key is listed as Missing key, with Add a key in its row: the key is typed there and saved to the workspace Secrets without leaving the page. A definition whose own Variables hold the key may use the default even when the workspace has none, and so may you when the key is one of your private secrets: it counts for the runs that act for you, not for anyone else's. Only a variable counts: a login named like the key does not. On an instance where harnesses are not enabled, only the Standards default is listed.

Customize on a default creates a custom environment of that harness.

Build an environment

Environments live under Settings → Execution → Environments: the harnesses, each ready or naming the key it misses, then the environments — yours, then the workspace's — each with its state ("v3" or "Never saved") and what uses it. New environment opens a dialog asking for a name, a harness and a visibility; it needs environments:create. Secrets have their own page, Settings → Execution → Secrets: yours first, then the workspace's. The harness cannot change afterwards.

The editor has three parts:

  1. The setup script. A shell script, up to 64,000 characters, that prepares the machine: install a CLI, clone a repository, write a config file. Build saves the script, then rebuilds the machine from the harness's base, never from the previous version, and runs the script with bash -euo pipefail in the working directory. The script is the reproducible part of the environment.
  2. The build log. The build runs in the background: the panel shows "Building…" with the log refreshing, then "Built" or "Failed — see the log". A failed build ends with ENVIRONMENT_BUILD_FAILED and the script's exit code on the log's last line; the machine stays as the script left it, for inspection, and the published version is untouched. A build that stops making progress for 35 minutes, after a restart, reads as failed and a new one may start.
  3. The terminal, closed by default: "Terminal closed — to adjust the machine by hand". Open it to work on the machine directly, in the same working directory as the script. What you do there is part of the next save, not of the script.
HarnessBase machineWorking directory
StandardsOur agent's sandbox: LibreOffice, Poppler, fonts, Python/workspace
Claude Code, Codex, CursorNode 24, Python 3.14, the harness installed/vercel/sandbox/work

While a build runs, opening the terminal, saving and discarding answer 409 with SCHEMA_CONFLICT: one operation at a time per environment.

Save

Save · vN publishes the machine as it stands as the next version:

  1. Captures the shell environment. A login shell is opened in the working directory and its PATH is recorded, so anything your ~/.bashrc adds to it (nvm, bun, corepack shims) reaches every copy. The other variables are recorded encrypted, except any whose name looks secret: names ending in TOKEN, KEY, SECRET, PASSWORD, CREDENTIAL, AUTH, DSN or URL, or starting with TOKEN, KEY or SECRET, are left out. The API returns the variable names, never their values.
  2. Snapshots the machine and publishes it. Two saves at the same time: one publishes, the other answers 409 with SCHEMA_CONFLICT.

Discard brings the machine back to the last saved version. A new version applies to runs and conversations started after it; a conversation already started keeps the version it started from.

Everything on the machine is part of the snapshot and readable by every run: an SSH key, a ~/.npmrc, a gh login, a .env file, anything set up in the terminal. Prefer the Variables for tokens such as GITHUB_TOKEN.

The owner renames or archives an environment from the ⋯ menu. Archive stops its machine and closes the conversations using it. While a routine or an agent still uses it, the archive is refused with 409 ENVIRONMENT_IN_USE, naming them: switch them to another environment first.

Visibility and the shared machine

An environment is private (only its owner sees and uses it) or workspace (every member who may use environments sees and uses it). Only the owner edits, builds, saves or archives it, in both cases.

A copy of a workspace environment is a whole machine: every member who uses it can read its files with a shell, including the owner's own logins made in the terminal. Sharing therefore asks first — "Members will be able to use and read everything this machine holds, including what was set up in the terminal (gh, CLIs, SSH keys…)." — and the API requires confirmSharedMachine: true with visibility: "workspace", on create and on update; without it the request answers 400 with SCHEMA_VALIDATION_FAILED. A workspace environment reads Shared machine in the list.

A workspace routine or agent may only use a workspace environment or a default one. A workspace environment cannot go back to private while a workspace definition, or another member's, still uses it (400 with SCHEMA_VALIDATION_FAILED).

If the owner leaves the workspace, their private environments are archived and the triggers and schedule of the definitions using them are switched off; their workspace environments stay.

Model access

Standards never bills a harness turn: the harness reaches its model with your access, in one of two ways.

  • A key in the Variables — ANTHROPIC_API_KEY for Claude Code, OPENAI_API_KEY for Codex, a Cursor user API key as CURSOR_API_KEY for Cursor. The definition's Variables are read first, then your private secrets (those of the person the run acts for), then the workspace's. The key is handed to the harness without ever entering the machine: env in a copy does not show it. The Harnesses card asks for these keys when they are missing.
  • Otherwise, what the machine itself holds: whatever was configured inside a custom environment, in its script or its terminal, and saved with it.

With neither, the turn fails with ENVIRONMENT_MODEL_ACCESS_MISSING: "No model access: add a key in the Variables, or set it up again in the environment's terminal." A harness default with no key is refused before any machine starts, and so is a conversation created on it (400).

The editor's Machine card shows Model access: the key found ("ANTHROPIC_API_KEY from the workspace"), or "No key" with Add a key.

Variables other than the harness's own key are set as environment variables in the copy, over the captured shell environment. In a harness conversation they are fixed when its copy starts: a Variable deleted or changed later reaches a new conversation, not one already running. On the Standards harness, every command carries the current Variables and the logins exposed to sandbox scripts. A harness copy never receives a login, even an exposed one: its 2FA code is computed for each command of the Standards sandbox, which a copy never runs.

The model is chosen on the conversation

The environment does not carry a model. A definition's model, or a conversation's model selector, picks one among the harness's models:

HarnessModels (provider / model)Default
Claude Codeanthropic / sonnet, opus, haiku — each the latest model of that familysonnet
Codexopenai / gpt-6.1-sol, gpt-6-luna, gpt-6-astragpt-6.1-sol
Cursorcursor / auto (Cursor picks the model), composer-2.5auto

These are the models every workspace is offered. The model menu shows more when it can: with the harness's key in the Variables (the agent's, else the workspace's), it lists the models that key's account can run, read from the provider (Anthropic, OpenAI, Cursor) and refreshed hourly; without a key — or on a custom environment signed in on its machine — it shows the list Standards curates at models.standards.new, else the table above. Only members who may read the Variables (env-vars:read) see the list read with a key; the others see the curated one. GET /environments/harnesses/:harness/models?definitionId=<id> answers { source, models }, where source is live, served or compiled. With auto, Standards sends Cursor no model at all and Cursor uses the one configured for your account. A model under another provider, or an empty model id, answers 400 with SCHEMA_VALIDATION_FAILED; a model id the harness does not know fails at the turn. A conversation takes its agent's model, else the harness default; PATCH /agents/sessions/:id/inference-overrides with { "model": { "provider": "anthropic", "model": "opus" } } changes it from the next turn.

Use it in a routine or an agent

On a routine's or an agent's page, the With rows include Environment; the model is picked in the prompt's footer, among the chosen harness's models. Manage environments… opens the settings section. Choosing a harness environment clears, after a confirmation, the settings a harness cannot honour:

SettingWith a harness
Prompt, triggers, schedule, webhookUnchanged. The trigger's input reaches the harness inside a <user_data> block.
Budget per runDisabled: billed by your model provider, not by Standards.
Delegation, depth, critic, auto-decideDisabled: the harness orchestrates itself.
ToolsThe harness uses its own built-in tools, plus the Standards workspace tools (standards_search, standards_query, standards_read, standards_write, standards_organize, standards_delete): search, read, create and update records, documents and files, with the data permissions of the person the run acts for. Each call follows the same approval rules as our agent, with nobody to ask: a call that would need a person's approval — deleting or archiving, sending outside, a write while the conversation is in Manual — is refused with HARNESS_APPROVAL_REQUIRED and changes nothing. A harness never reaches your paired machines or the cloud browser through Standards: it has its own shell. Only memory, GitHub, Slack and trusted MCP servers (mcp:<id>) can be granted; anything else is refused with AGENT_TOOL_GROUP_HARNESS. Every Standards tool is mounted as standards_<verb>, because a harness already has built-in tools called read and write; in the instructions and skills a harness run reads, read, write and delete mean standards_read, standards_write and standards_delete. Every kind the agent is granted is available from the first step, since a harness cannot load a group later, and there is no Standards sandbox, so copy is not offered. send is not offered either: its approval card has nowhere to show.
MCP serversThe servers granted to the definition (mcp:<id>) reach the harness as tools, called with the identity of the person the run acts for. A server that is not trusted is left out, since nobody can approve its calls there: trust it again under Settings → Integrations to use it.
MemoryWhen the agent or routine is granted memory (see Memory), the harness receives the memory profiles after the agent's instructions — the workspace profile, the profile of the person the run acts for, and the agent's own — and gets the memory kind on standards_search and standards_read, plus standards_propose_memory, called by the agent on behalf of that person. A conversation reads its profiles once, when it starts. A routine has no instructions of its own but still receives the profiles. A conversation without an agent always has memory.

A custom Standards environment keeps every Standards setting and tool: our agent runs as usual, on a copy of your machine, with exec_command and its file tools working in /workspace.

Through the API, a definition carries harness and environmentId:

harnessenvironmentIdRuns on
absentabsentThe Standards default.
"claude-code", "codex" or "cursor"absentThat harness's default.
anyan environment's idThat environment; harness is taken from it, and a contradicting one answers 400.

Send null to clear either on PATCH. A definition with a harness that carries a disabled setting answers 400 with SCHEMA_VALIDATION_FAILED, naming the field; a grant other than memory or a trusted MCP server answers 400 with AGENT_TOOL_GROUP_HARNESS. Changing the environment counts as changing what the routine does: you become the author of its triggers and the armer of its schedule.

Use it in a chat

On a new conversation, a compact environment chip sits above the composer when there is a real choice — a harness key in the Variables or a custom environment you can see: a default shows its logo alone, a custom environment its logo and name. Otherwise the chip is absent and Environment… in the composer's + menu opens the same menu: Default, My environments, Workspace and Manage environments…. Pick one before the first message; after it, the environment is fixed for the conversation and shown, read-only, in the conversation's header (nothing for Standards). Through the API, POST /agents/sessions accepts harness and environmentId with the same meaning as on a definition; a conversation with an agent takes the agent's. Both are fixed at creation. A harness conversation cannot have a past message edited (409 AGENT_INVALID_STATE): send a new message instead.

What a run looks like

  • A routine run starts a fresh copy, runs until the harness ends, then destroys the copy.
  • A conversation gets one copy of its own, stopped between messages and resumed on the next one with its files and the harness's own history. Archiving the conversation destroys its copy.
  • No maximum duration. The only ceiling is technical: 24 hours.
  • Queueing. Each person runs up to fifteen routine runs at once on one environment; the others wait their turn, none is dropped. A chat message never waits behind routine runs.
  • No automatic retry. A harness crash, an interrupted run, a harness waiting for input or a conversation that lost its history fails with ENVIRONMENT_RUN_FAILED: send the message again.

The harness's tool calls appear by family — a command, a file read, an edit with its diff — and an MCP tool call as it does with our agent. A failed run stores its error as <CODE>: <message>, which the routine's status line shows.

API

RoutePermissionPurpose
GET /environmentsauthenticated{ defaults, environments }: the defaults ({ harness, available }, Standards first with harness: null), then the environments you can see: id, name, harness, visibility, ownedBy, ready, version, savedAt, buildState, shellEnvNames. ?include=usedBy adds usedBy.
GET /environments/:idvisibleAdds setupScript and buildLog.
POST /environmentsenvironments:createCreate { name, harness, visibility?, confirmSharedMachine? }; harness: null is a Standards environment.
PATCH /environments/:idownerChange name, visibility or setupScript; harness is refused.
POST /environments/:id/buildowner202: rebuild from the base and run the script in the background. Read buildState (idle, building, failed) and buildLog back.
POST /environments/:id/terminalownerOpen the terminal; returns { url, token }.
POST /environments/:id/saveownerSave (see above).
POST /environments/:id/discardownerBack to the last saved version; 400 ENVIRONMENT_NOT_READY if never saved.
DELETE /environments/:idownerArchive. 409 ENVIRONMENT_IN_USE while a definition uses it.

An environment you cannot see answers 404; one you see but do not own answers 403 on the owner routes. An environment is ready once saved and not archived. Attaching any environment other than the Standards default — on a definition or a conversation — needs environments:read, the use level of the environments capability; member has both levels by default.

The typed client does not cover environments. Call these routes through createTransport:

import { createTransport } from "@standards/client";

const transport = createTransport({
  baseUrl: "https://api.standards.new/v1",
  apiKey: process.env.STANDARDS_API_KEY!,
});

const routineId = "…"; // the routine to move onto Codex

const { defaults } = await transport.get<{
  defaults: Array<{ harness: string | null; available: boolean }>;
}>("/environments");

if (defaults.some((d) => d.harness === "codex" && d.available)) {
  await transport.patch(`/agents/definitions/${routineId}`, {
    harness: "codex",
    environmentId: null,
    model: { provider: "openai", model: "gpt-6.1-sol" },
    maxTreeCostUsd: null,
    toolGroups: ["memory"],
    maxDepth: null,
    critic: null,
    autoDecide: null,
  });
}

Errors

CodeHTTPWhen
ENVIRONMENT_NOT_READY400Never saved, archived, or its snapshot is gone.
ENVIRONMENT_IN_USE409Archiving an environment a definition still uses. The definitions' ids are in the error's details.
ENVIRONMENT_MODEL_ACCESS_MISSING400No key in the Variables and nothing usable on the machine.
ENVIRONMENT_BUILD_FAILED400The setup script exited non-zero. Recorded on the environment (buildState: "failed", last line of buildLog).
ENVIRONMENT_RUN_FAILED400The harness crashed, was interrupted, waited for input, or a conversation lost its history.

When a run or a turn is under way, these surface as its error rather than an HTTP answer. An instance where environments are not enabled answers 503 with CONFIGURATION_REQUIRED. See errors for the shared codes (SCHEMA_CONFLICT, SCHEMA_VALIDATION_FAILED, CONFIGURATION_REQUIRED).

Next steps

Read agents and routines for triggers, schedules and runs, and roles and permissions for the environments capability.

GitHub repositories

An environment can list several repositories, each with an optional branch. Choose repositories from installed GitHub Apps above the setup script. A blank branch follows the repository's default branch.

A build clones them into /workspace/<owner>/<repository> before running the setup script, using the builder's linked GitHub account. A fresh conversation copy refreshes those checkouts. A resumed copy keeps its files and current branch. If a GitHub event concerns another repository, the agent can clone it without resetting repositories already present.

Repository failures appear in the build or run context, with the repository and failure status. They do not silently select another account. GitHub-managed credentials, authenticated remotes and GitHub credential helpers are removed before a snapshot is saved; if cleanup fails, saving fails. Files and credentials you add for other services remain part of the shared machine, so use Variables for secrets.