An agent is a folder that a process step names. The folder holds a manifest, two prompts and, if you want them, skills. Factorial reads the folder at the commit that the ticket pinned.
The agent folder
Each agent lives in .factorial/agents/<name>/. The folder name must equal the name in the manifest. It must also equal the agent: value of the process step that uses it. A human gate needs no agent.
.factorial/
agents/
plan-agent/
agent.yaml the manifest
system-prompt.md who the agent is, and what it must deliver
instructions.md the workspace, the delivery and the revision flow
skills/ skills only this agent uses
speckit-specify/SKILL.md
mcp/ MCP servers only this agent uses
skills/ skills that any agent can share
project-constitution/SKILL.md
mcp/ MCP servers that any agent can share
product-docs.yaml
Every path that the manifest declares must exist. Sync checks both prompts, each skill and each MCP server file. A step whose agent folder is missing, or whose manifest does not parse, fails when it starts. The sync answer lists such agents as agent_warnings. See Snapshot the config.
The agent file
The keys of an agent file use snake_case. A key that the table does not list is an error. The only vendor-specific part is runtime.kind. The process chooses the runtime that runs the agent.
| Key | Required | What it holds |
|---|---|---|
specVersion | yes | factorial/v1. The value adlc/v1 still parses. This key is camelCase, like the same key in the other config files. |
kind | yes | Agent. |
name | yes | The agent name. It equals the folder name and the agent: value of the step. |
description | no | One line for people who read the file. |
prompts | yes | Two paths, system and instructions, relative to the agent folder. See Prompts. |
skills | no | Skills by name, in two lists, local and shared. See Skills. |
mcp | no | MCP servers by name, in the same two lists. Without the key, the agent has no MCP server. See Select it for an agent. |
model | yes | A concrete model id, for example claude-haiku-4-5-20251001. Factorial refuses a size name such as sonnet. It also refuses an id that ends in -latest and an id with no digit. The model API rejects them, and the agent would fail on its first turn. |
limits | yes | You must give max_duration. Write a number and a unit, s, m or h, for example 30m or 1h. max_turns is optional. Write a whole number of 1 or more. |
secrets | no | A list of the keys that the agent needs. Each item has a key and an optional description. See Secrets. |
permissions | no | The GitHub permissions that the agent asks for. See Permissions. |
runtime | yes | kind is claude or codex. It must match the family of the runtime that runs the agent. Sync refuses a mismatch. |
This is the manifest of the plan agent in the sample lane:
specVersion: factorial/v1
kind: Agent
name: plan-agent
description: Turns a raw request or ticket into a reviewable contract: spec, plan, task list
prompts:
system: system-prompt.md # the role and the output
instructions: instructions.md # the workspace, the delivery and the revision flow
skills:
local: # from skills/ in this agent folder
- speckit-specify
- speckit-clarify
- speckit-tasks
shared: # from skills/ in .factorial/
- project-constitution
mcp:
shared:
- product-docs # needs an allowlist entry, see MCP servers
model: claude-haiku-4-5-20251001 # a concrete id, never a size name
limits:
max_duration: 1h # a run gets a one-hour token, so deliver inside the hour
max_turns: 40
secrets:
- key: ANTHROPIC_API_KEY
description: Anthropic API key for the model
- key: GH_TOKEN
description: GitHub App token, made for each run
runtime:
kind: claude
Secrets
The secrets list names the keys that Factorial must supply to the agent. It never holds a value. You store the values in the Secrets section of the project. A declared key with no stored value blocks activation, and the answer names the key. Give the agents their credentials shows how to store them.
Factorial makes GH_TOKEN for each run, so you store no value for it. When the process names a runtime, the model key comes from that runtime alone, and a declaration of it in the agent file does nothing. The list is a statement of what the agent needs. It does not limit what a run can read: Security explains where the boundary is.
Permissions
The optional permissions key asks for GitHub permissions for the token of the run. It maps a scope to read or write. The scopes are contents, issues, pull_requests, metadata, checks and actions. Another scope, workflows included, is an error. Without the key, the agent gets the default profile of the App.
permissions:
contents: write
pull_requests: write
issues: read
A manifest only asks. The operator gives the grant through the permission_ceiling setting of the project. Factorial refuses a request that is above the ceiling and names each scope that is too high. See permission_ceiling.
Prompts
An agent has two prompts. Both are markdown files beside agent.yaml. A prompt is never inline YAML.
prompts.system- Who the agent is, and what it must deliver. Write the role and the output here.
prompts.instructions- The working context: where the agent stands, how it delivers its branch and pull request, and what it does when a reviewer asks for changes.
Keep the prompts small. Factorial already does the bookkeeping. It admits the ticket, sets the labels and assigns people. It moves the ticket to the next step, merges at a gate and checks every postcondition. A prompt that describes any of this duplicates the orchestrator and goes out of date when the config changes. Describe the work and nothing else.
An agent does not read .factorial/ from its checkout. The checkout is the workspace.base branch, where the folder is old or missing. Each run gets the pinned config commit in the variable FACTORIAL_CONFIG_SHA and in its prompt. A prompt that must read the process file uses this command:
git show "$FACTORIAL_CONFIG_SHA":.factorial/processes/process-simple.yaml
Do not put {slug} in workspace.base of the step. When Factorial cannot work out the slug, the base becomes a branch that does not exist. The ticket then blocks on the first try. Use a base that always exists, and let the instructions name the real branch point. The adapter also writes base into the prompt as the branch of the checkout, so write those instructions so that the agent cannot miss them.
Skills
A skill is a folder that holds a SKILL.md file. The file starts with YAML front matter that has a name and a description. The text after it tells the agent how to do one kind of task.
---
name: speckit-tasks
description: Decompose an approved plan into an ordered task list. Use as phase 3 of planning, after specify and clarify.
---
# Tasks
Phase 3 of planning. Produce tasks.md.
The agent file declares a skill by name, in one of two lists:
skills.local- The skill is
agents/<name>/skills/<skill>/SKILL.md. Only this agent uses it. skills.shared- The skill is
.factorial/skills/<skill>/SKILL.md. Any agent can declare it.
A skill name must be unique across both lists of an agent, because the agent finds a skill by name. A declared skill with no SKILL.md is an error.
What an agent must never do
Every prompt keeps one short rule. The agent never merges a pull request, never approves its own pull request, never adds a label and never pushes to main. Do not remove it.
The rule is in the prompt because the agent and Factorial share one GitHub permission profile. Two things enforce the merge: the branch protection that you set and the gate. It cannot enforce the other three rules. The sentence in the prompt is the control. Security explains this accepted cost.
The agent must also report what happened, with no gloss. Factorial does not read the report as proof. It checks the postconditions of the step against GitHub: the branch, the file, the pull request, the CI result. When they do not hold, the step fails, whatever the report says, and Factorial retries it or blocks the ticket. An honest report that says what was not done helps the next try and the person who reads the ticket.
Start from the catalog
You do not write the agent folders by hand. The process builder holds a catalog of agents: plan-agent, build-agent and test-agent. When you press Commit and sync, the commit writes a folder for each catalog agent that your process names. It skips an agent that the repository already has. It never rewrites an agent folder or a shared skill that the repository holds, so you can tune the prompts after the first commit.
Most catalog agents have variants for the shape of the lane. The stacked variant opens the implementation pull request against the spec branch. The base-target variant opens every pull request against the base branch. Only the variant that matches the branches of your lane writes files. The shared project-constitution skill has the same variants, and its naming table uses the branches of your lane.
The explorer in the process builder lists every file that the commit would write. You can read and edit each file before you tick it. An agent that the catalog does not know, or whose files you leave unticked, gets a agent.yaml stub that holds only comments. Fill in the stub, because a step whose agent is still a stub fails when it starts. Fill .factorial/ gives the steps of the builder, and A stale edit blocks the commit explains the stale badge on a file that you edited.