Factory (factory)
Output
AGENTS.md # canonical entry-point pointer body + inlined rules (written by sync, shared path)
.factory/droids/<name>.md # one custom-droid profile per agent
.agents/skills/<name>/SKILL.md # one folder per skill (shared tree with codex/amp/zed/crush)
.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 Droid reads the root AGENTS.md natively and loads custom droids from .factory/droids/. Each agent emits as one <name>.md profile with name, description, and optional model / tools frontmatter (tools translates onto Droid CLI's own tool IDs, see below); arbitrary x-factory keys pass through.
An agent spec with an empty body skips instead of writing a frontmatter-only file: Droid CLI's own schema says the body "is the system prompt and cannot be empty", so the skip surfaces as a coverage note rather than landing a file the tool rejects. Factory has no per-rule directory, so rule bodies inline into the shared AGENTS.md ## Rules block.
Skills load from .agents/skills/, the same cross-tool tree codex, amp, zed, and crush emit; the render is byte-identical, so the shared tree dedupes into one write. docs.factory.ai/harness/skills also documents a second compatibility path, .agent/skills/**/SKILL.md, which this adapter does not additionally write.
- Tools: a spec's generic
toolslist is translated onto Droid CLI's own tool IDs, not passed through. The vendor's table is the complete set of valid IDs (Read,LS,Grep,Glob,Create,Edit,ApplyPatch,Execute,WebSearch,FetchUrl) and "Unknown IDs cause a validation error", so one unknown name costs the author the whole droid, not just that tool.BashbecomesExecute,WritebecomesCreate, andWebFetchbecomesFetchUrl, the same three renames Factory's own Claude Code importer performs; the rest of agnostic-ai's vocabulary is already valid and carries over.- Three load-time rules shape the rest:
TodoWriteandSkillare "always included for every droid ... You do not list them", so they drop without a note since the droid keeps them anyway;ExitSpecModeandGenerateDroid"cannot be enabled by a custom droid", so they drop like any unknown name; and the literaltools: allis rejected by Droid CLI, so a scalar value never reaches the frontmatter and the omitted key means "allow every tool", which is Factory's own way to spell it. - Any other name drops with a coverage note rather than being written unconfirmed, while the names that do translate still emit.
- Set
x-factory.toolsto bypass the table with Factory's own vocabulary directly, the only way to reach a category name (read-only,edit,execute,web,mcp) or a registered MCP tool ID; it wins outright over the translated form. Seetoolssupport by target.
- Commands: written to
.factory/commands/<name>.mdwithdescriptionandargument-hintfrontmatter. The body stays Markdown and$ARGUMENTSis preserved. Factory recommends Skills for new reusable workflows but continues to load this command surface. - Hooks: written to
.factory/hooks.json: "Project |.factory/hooks.json| Commit to share with teammates." (docs.factory.ai/harness/hooks, #629).- The file is managed:
syncoverwrites it whole, the same as.factory/mcp.jsonbelow. A hand edit is lost on the next sync; change the hook spec instead (target-audit 2026-09-11, #745). - Nine events:
PreToolUse,PostToolUse,UserPromptSubmit,Notification,Stop,SubagentStop,PreCompact,SessionStart,SessionEnd. Unlike Claude Code, Codex, Gemini, and Qoder's shared{"hooks": {...}}wrapper, "Standalonehooks.jsonfiles are keyed directly by event name", so this adapter's document renders{"<Event>": [{matcher, hooks: [...]}]}at the top level with no wrapper, the same divergence Windsurf/Devin CLI's.devin/hooks.v1.jsoncarries. - Per entry:
type(always"command", the vendor's field table documents no other value),command, and optionaltimeout(seconds, vendor default 60 when absent, not milliseconds). matcheris a regex; the vendor's own "Common tool matchers" (Execute,Read,Edit,Create,ApplyPatch,LS,Glob,Grep,Task,FetchUrl,WebSearch) already match Claude's own spelling except three:Bash,Write, andWebFetch, the same three namestools.go's translation table renames toExecute,Create, andFetchUrlfor thetoolsfrontmatter field. A matcher carried over from a Claude spec using one of those three still emits verbatim but folds into one coverage note, since it parses as a valid regex and then matches nothing.- The vendor's other matcher-group field,
commandRegex("Additional regex filter for Execute commands"), has no counterpart on agnostic-ai's generic hook spec and is not emitted.
- The file is managed:
- MCP: written to
.factory/mcp.jsonunder the standardmcpServersmap, the same shape Claude Code and Cursor use (stdio:command/args/env, notype; remote:type+url/headers).- The file is managed:
syncoverwrites it whole, the same as Claude Code, Cursor, Junie, and Kiro. That is worth knowing here, because Factory's own docs send you to hand-edit it: "Project servers cannot be removed withdroid mcp removeor the/mcpmanager. To remove them, edit.factory/mcp.jsondirectly" (docs.factory.ai/harness/mcp). Such an edit is lost on the next sync; remove the MCP spec instead (target-audit 2026-09-11, #737). - Factory's schema documents a working per-server
disabledboolean (defaultfalse), unlike Claude Code, Cursor, and Copilot, so agnostic-ai passes a spec'sdisabled: truestraight through instead of stripping it. - Both transports preserve
disabledTools,timeout, andconnectTimeout(milliseconds), including explicit zero timeouts. - Remote HTTP/SSE servers also accept
oauth: falseor an OAuth object withscopes,resource,authorizationServerIssuer,clientId,clientSecret,clientMetadataUrl,tokenEndpointAuthMethod, andcallbackPort. x-factoryoverrides each top-level option. These fields stay scoped to Factory. Seedisabledsupport by target. Atype: wsspec emits no server and raises a coverage note because Factory documents only stdio, HTTP, and SSE.
- The file is managed:
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 |
Verify
- Install the Factory CLI (subagents docs).
- Check the tree:
ls AGENTS.md .factory/droids/ .agents/skills/ .factory/hooks.json .factory/mcp.jsongrep "Generated by agnostic-ai" .factory/droids/*.mdfor the provenance header (it sits after the frontmatter)python -m json.tool .factory/hooks.json > /dev/nullwhen hook specs existpython -m json.tool .factory/mcp.json > /dev/null
- Launch
droid:- Each
.factory/droids/<name>.mdappears in the droid picker. - Each
.agents/skills/<name>/loads as a skill. - Each
mcpServers.<name>from.factory/mcp.jsonconnects. /hooksshows each entry in.factory/hooks.jsonunder the Project tab.
- Each