Agent definitions

An agent definition says which model an agent runs, what its instructions are and which tools it may use. It is a named, versioned bundle in the bundle/1 format. You publish it as an inline JSON spec with POST /v1/bundles/{name}/versions, and Forgeling compiles the spec into a bundle. Definitions belong to your app, not to a workspace, so runs in every workspace can start from them. Each run names the definition and the exact version it starts from.

Uploading a bundle archive comes in a later release; for now every version is an inline spec.

Publish a version

curl https://api.forgeling.io/v1/bundles/support-triage/versions \
  -H "Authorization: Bearer $FORGELING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "version": "1.0.0",
    "spec": {
      "model": "claude-sonnet-4-5",
      "instructions": "You triage one support ticket. Read $RUN_INPUTS/ticket.md, look the customer up in the tracker, and call submit_result with a priority and a one-paragraph summary.",
      "effort": "medium",
      "tools": {"allow": ["Read", "Grep", "mcp__tracker__*"]},
      "mcp_servers": [{
        "name": "tracker",
        "type": "http",
        "url": "https://mcp.example.com/mcp",
        "headers": {"Authorization": "Bearer ${TRACKER_TOKEN}"}
      }],
      "secrets": ["TRACKER_TOKEN"]
    }
  }'

The answer is 202 with the version and "status": "validating". Forgeling checks the spec and compiles it, and a few seconds later the version is ready, with the bundle.version_ready event, or rejected, with the bundle.version_rejected event and errors, each a path into the spec and a message. GET /v1/bundles/{name}/versions/{version} reads it back. A run can start only from a ready version.

A bundle's name is lower-case letters, digits and -, at most 48 characters, starting with a letter or a digit. A version is a semantic version such as 1.0.0 or 1.1.0-rc.1, without build metadata (no +…).

The inline spec

Field What it holds
model Required. The Anthropic model id, such as claude-sonnet-4-5: lower-case letters, digits, . and -, at most 100 characters.
instructions Required. The agent's instructions, 1 to 65,536 characters.
tools allow and deny, lists of tool rules, with at most 50 rules each, each rule at most 256 characters.
mcp_servers The remote MCP servers the agent may use (MCP servers).
variables Named values a run passes in its definition as variables. Each may have a default and a description; one without a default must be given. Names are letters, digits and _, at most 64 characters, not starting with a digit.
secrets The names of the run secrets the agent uses. For runs this only documents them: a run secret works without being declared.
effort The model's effort level: low, medium, high, xhigh or max.
prompt_profile task, the default and for now the only value: a prompt for one job, which states the run root's paths.

There is no permission-mode field: every agent runs in Claude Code's default permission mode. tools.allow pre-approves tools, and the run's approvals mode decides every other tool call.

MCP servers

An agent reaches remote MCP servers only, at most 10 MCP servers per definition. Each entry has:

Tool rules

tools.allow and tools.deny hold Claude Code permission rules, as strings. Forgeling supports this subset:

The rules are Claude Code's own syntax, read by the Claude Code CLI at the version pinned in the run image, and Forgeling hands them to the CLI unchanged. The matching semantics belong to Anthropic's Claude Code CLI: Anthropic's documentation of permission rules is the reference for them, and Runtime pinning says how a change to the CLI reaches your runs. Paths in rules are relative to the agent's working directory, which holds the run root as run/, so a rule for the files of a coding run's repository starts with run/repo/.

tools.deny always wins over tools.allow. This definition lets the agent change the code but not the database migrations:

{
  "tools": {
    "allow": ["Bash", "Read", "Edit", "Write", "Glob", "Grep"],
    "deny": ["Edit(run/repo/migrations/**)"]
  }
}

Deny rules are not a security boundary when Bash is allowed. A shell can edit any file a deny rule protects. Use deny rules to steer the agent, and protect what matters outside the sandbox, as a coding run's protected default branch does.

There is no web_search field. An agent that needs search allows WebSearch, which depends on Anthropic's availability, or brings a search MCP server with its own key, passed as a run secret.

Approvals

A tool call outside tools.allow goes to the approvals mode of the run, which you set when you start it, as "approvals": {"mode": "deny"} or "approvals": {"mode": "ask"}:

curl https://api.forgeling.io/v1/approvals/$APPROVAL/decision \
  -H "Authorization: Bearer $FORGELING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"decision": "allow", "note": "Checked by the on-call engineer."}'

decision is allow or deny, and note is optional. GET /v1/approvals?status=pending lists the approvals that wait for an answer, approval.resolved reports each outcome, and a decision on an approval that no longer waits is 409 approval_not_pending.

tools.deny wins in both modes. submit_result, the tool through which the agent returns a turn's output_schema result, is always allowed, whatever the mode.

Runtime pinning

Runs pin the Claude Code CLI through their run image. Each run image has one pinned CLI version, and a run keeps the image it started with for its whole life (the run's image_digest names it), so its tool rules match the same way from its first turn to its last.

A new run image whose CLI changes how tool rules match is announced at least 30 days before the switch, in the changelog and by email to your app's owners. Until the switch you can try it: start a run with runtime set to "next" and the header Forgeling-Beta: runtime-next. After the switch, a run started with runtime set to "previous" gets the image from before it, for 90 days. A run started without runtime gets "current", the latest image.

Versions

A version is immutable. Publishing a version that exists again answers 409 bundle_version_exists, so to change an agent, publish a new version and start new runs from it. GET /v1/bundles lists your definitions, and GET /v1/bundles/{name}/versions the versions of one.

To withdraw a version, yank it:

curl -X POST https://api.forgeling.io/v1/bundles/support-triage/versions/1.0.0/yank \
  -H "Authorization: Bearer $FORGELING_API_KEY"

A yanked version starts no new runs: starting one answers 422 bundle_version_unavailable, as for a version that is not ready. Runs that already started from it go on.