Factory (factory)
Set up this tool
Factory Droid reads the root AGENTS.md and custom droids in .factory/droids/. Factory has no per-rule directory, so rule bodies go into the ## Rules block of AGENTS.md.
First rule
Select factory in agnostic-ai.yaml, then follow First rule to add the shared commit-prefix rule and sync it. With the default paths, it reaches AGENTS.md.
Custom droids come from agent specs. A read-only droid also needs its MCP access restricted; sync supplies an empty server list unless you set one.
For existing files, check what import factory reads before syncing.
Verify
Run agnostic-ai sync --check and inspect AGENTS.md for the commit-prefix rule. Launch droid from the project and ask for a commit subject for a small fix; it should use fix:.
Next, add a skill.
Output
AGENTS.md # pointer body + rules (shared path)
.factory/droids/<name>.md # one custom-droid profile per agent
.agents/skills/<name>/SKILL.md # one folder per skill (shared tree)
.factory/commands/<name>.md # one Markdown slash command per command spec
.factory/hooks.json # when hook entries exist
.factory/mcp.json # when MCP entries exist
.factory/settings.json # when a settings entry carries a model, a shell-command rule, or an x-factory key
Native mappings
Field mappings, discovery, and compatibility
Agents
Each <name>.md carries name, description, and optional model and tools. Any x-factory key passes through.
- An agent with an empty body is skipped with a sync note, because Droid CLI requires a system prompt.
- A portable
mcpServerslist is written as is.mcpServers: []excludes every MCP server, even global ones, so list the ones you want. SeemcpServers. - A portable
effortbecomesreasoningEffort, which accepts onlylow,medium, andhigh.xhigh,max, and integer budgets drop with a sync note. Factory ignores the field undermodel: inherit. See per-targetmodelandeffort. readonly: truewritestools: read-only, Factory's read-only tool category. It replaces a portabletoolslist instead of narrowing it, with a sync note that points tox-factory.toolsfor a custom list.x-factory.toolswins overreadonly.readonly: falsedoes nothing.- A droid gets its MCP servers' tools, so a read-only droid with no
mcpServerslist getsmcpServers: []. A list you set is kept.
Tools
The generic tools list maps to Droid CLI's IDs (Read, LS, Grep, Glob, Create, Edit, ApplyPatch, Execute, WebSearch, FetchUrl). One unknown ID makes Factory reject the droid.
BashbecomesExecute,WritebecomesCreate, andWebFetchbecomesFetchUrl, as in Factory's Claude Code importer. The rest already match.TodoWriteandSkilldrop silently, because every droid gets them.ExitSpecModeandGenerateDroiddrop because custom droids cannot enable them.tools: allis never written, because an omitted key allows every tool.- Any other name drops with a sync note. The rest are still written.
x-factory.toolswrites Factory's own names and wins over the translation. Only it can set a category (read-only,edit,execute,web,mcp) or an MCP tool ID. Seetoolssupport by target.
Skills
Written once to the tree that codex, amp, zed, and crush share. Factory also reads .agent/skills/**/SKILL.md, which this adapter does not write.
- Scoped skills go to
<scope>/.factory/skills/<name>/SKILL.mdwith bundled files. Unscoped skills keep.agents/skills/. outputs.factory.skills-dirreplaces the directory at the root and in each scope.
Commands
.factory/commands/<name>.md takes description and argument-hint. $ARGUMENTS is kept. Factory prefers skills for new workflows but still loads commands.
Hooks
.factory/hooks.json is the committed project file (hooks docs). sync keeps the entries it did not write and replaces only its own. With builtins: [memory], a SessionStart hook adds the shared memory index to the session.
- Nine events:
PreToolUse,PostToolUse,UserPromptSubmit,Notification,Stop,SubagentStop,PreCompact,SessionStart,SessionEnd. - The file is keyed by event name,
{"<Event>": [{matcher, hooks: [...]}]}, with nohookswrapper (unlike Claude Code, Codex, Gemini, and Qoder). - Per entry:
type(always"command"),command, and optionaltimeoutin seconds (default 60). Factory'scommandRegexhas no spec field and is not written. matcheris a regex over Factory's tool names, which match Claude's exceptBash,Write, andWebFetch. A matcher that uses those is written as is, matches nothing, and raises a sync note.
MCP
.factory/mcp.json under mcpServers, the Claude Code and Cursor shape (stdio: command, args, env, no type; remote: type, url, headers).
syncrewrites the file whole. Factory's MCP docs say to remove project servers by editing this file, but sync would undo that edit, so remove the MCP spec instead.disabled: truebecomes Factory's per-server boolean. Seedisabledsupport by target.- Both transports keep
disabledTools,timeout, andconnectTimeout(milliseconds), zeros included. Remote HTTP and SSE servers also acceptoauth: falseor an OAuth object withscopes,resource,authorizationServerIssuer,clientId,clientSecret,clientMetadataUrl,tokenEndpointAuthMethod, andcallbackPort. - Set
x-factory.enabledToolsto load only the named tools. An empty list adds no restriction.disabledToolsexcludes named tools, including a name present in both lists. Sync preserves both lists, including explicit[], without removing overlapping names. See Factory tool filtering. x-factoryoverrides each top-level option. Atype: wsspec writes no server and raises a sync note, because Factory supports only stdio, HTTP, and SSE.
Settings
A portable model merges into <git-root>/.factory/settings.json, the project level of Factory's hierarchical settings.
- A portable
effortmerges as top-levelreasoningEffort(CLI settings). Values arenone,dynamic,off,minimal,low,medium,high,xhigh, ormax, and each model accepts a subset. Any other value raises a sync note. The managedsessionDefaultSettings.reasoningEffortis not written. - Sync merges. It sets only
model,reasoningEffort, the command lists, andx-factorykeys, sodisabledSkillsand other keys stay. x-factorysets keys that have no portable field, mainlysandbox(file and process limits enforced by the operating system). Its command-list entries add to the translated ones:x-factory.commandBlocklist: ["author-only"]besidedeny: ["Bash(rm:*)"]writes both.- Portable
allow,deny, andaskbecome Factory's shell-command lists.Bash(npm:*)becomes"npm *"andBash(curl)becomes"curl".
| portable | Factory key | why |
|---|---|---|
allow | commandAllowlist | Always runs without approval. |
ask | commandDenylist | Always asks; you can approve. |
deny | commandBlocklist | Never runs. No approval. |
- Factory's denylist only prompts, so portable
denymaps to the blocklist. The denylist beats the allowlist, and the blocklist beats both. - Rules scoping a path, URL, or MCP tool (such as
Read(src/**)) have no shell form and raise a sync note. - A list is written only when a rule translates into it. A list you keep by hand stays if no rule feeds it. If a rule does, sync replaces that key.
- Factory deprecates the three lists in favor of
permissionRulesbut still reads them (LLM safety and agent controls). A permission rule needs a stableid, amatch.prefixtoken list, andtests.matchandtests.noMatchexamples, which portable lists lack. Put them underx-factory.permissionRules, which is copied into.factory/settings.jsonas written.
Handoff from a linked worktree
With handoff or handoff-hook on, sync adds the main checkout's handoff store to sandbox.filesystem.allowWrite in .factory/settings.local.json, so Factory's sandbox lets handoff saves and the automatic snapshot reach it. The file stays out of Git, and sync never turns the sandbox on. See Write from a linked worktree.
Config keys
| Key | Default |
|---|---|
outputs.factory.agents-dir | .factory/droids |
outputs.factory.skills-dir | .agents/skills |
outputs.factory.commands-dir | .factory/commands |
outputs.factory.hooks-file | .factory/hooks.json |
outputs.factory.mcp-file | .factory/mcp.json |
outputs.factory.conf-file | .factory/settings.json |
Import
agnostic-ai import factory reads these back:
| Source | Becomes |
|---|---|
AGENTS.md embedded ## Rules block (### <name> children) | <rules>/<name>.md per rule |
.factory/droids/<name>.md | <agents>/<name>.md |
.agents/skills/<name>/SKILL.md and .factory/skills/<name>/SKILL.md | <skills>/<name>/SKILL.md |
<scope>/.factory/skills/<name>/SKILL.md | <skills>/<scope>/<name>/SKILL.md |
.factory/commands/<name>.md | <commands>/<name>.md |
.factory/mcp.json (mcpServers.<name>) | <mcps>/<name>.yaml |
.factory/hooks.json, else the legacy .factory/hooks/hooks.json | one hook spec per matcher group |
.factory/settings.json model, reasoningEffort, and command lists | <settings>/factory.yaml; reasoningEffort becomes effort, or x-factory.reasoningEffort when another settings spec sets a different effort |
AGENTS.md | .agnostic-ai/AGNOSTIC_AI.md |
- Droids:
toolsnames map back (ExecutetoBash,CreatetoWrite,FetchUrltoWebFetch). A list with a category (read-only) or an MCP tool ID goes underx-factory.toolsas written.reasoningEffortbecomeseffort. Other droid keys go underx-factory. - MCP servers:
enabledToolsimports underx-factory.enabledTools. Empty lists and names shared withdisabledToolsstay intact. - Command lists: these read back as
Bash(...)rules:commandAllowlisttoallow,commandDenylisttoask,commandBlocklisttodeny. A pattern ending in*becomesBash(x:*). Othersettings.jsonkeys stay in the file.
Read-only droids on import
A tools: read-only that came from a portable readonly: true also goes under x-factory.tools, because import does not turn it back into readonly. Syncing again writes the same droid file.
Protected paths
Not enforced. Sync cannot write an edit-blocking rule or hook for this tool, so it prints a protected-path coverage note.