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:
name: lower-case letters, digits,_and-, at most 32 characters, starting with a letter or a digit. The server's tools aremcp__<name>__<tool>in tool rules.type:httporsse.url: anhttpsURL, at most 2,048 characters.headers: sent with every request to the server, each value at most 4,096 characters. Write${NAME}in a value to send the run secretNAMEthere, so the secret itself stays out of the definition. Pass that secret when you start the run.
Tool rules
tools.allow and tools.deny hold Claude Code permission rules, as strings. Forgeling supports this
subset:
Bash: run shell commands. Inside a run sandbox,Bashmay be allowed without a scope.Read: read files.Edit(path/**): edit files under a path;Editalone edits any file.Write: create or overwrite files.GlobandGrep: find files and search their contents.WebFetch: fetch a URL.WebSearch: search the web, where Anthropic makes it available.Agent: start subagents.mcp__<server>__<tool|*>: one tool of a server inmcp_servers, or with*every tool it has.
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"}:
"deny", the default: the call is refused at once. The agent is told that the tool is not available in this run, and carries on. Nothing waits."ask": the call waits for your app. Forgeling emitsapproval.requestedwith the approval's id, the tool and a summary of its input, and you answer withPOST /v1/approvals/{apr}/decision. A call that gets no answer within 900 seconds is denied.
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.