Thanks for your interest in contributing! This guide will help you get started.
Prerequisites: Node.js 22+, pnpm 9+
# Clone the repo
git clone https://github.com/renseiai/agentfactory.git
cd agentfactory
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Run type checking
pnpm typecheck
# Run tests
pnpm testpackages/
core/ @renseiai/agentfactory — orchestrator, providers, crash recovery
linear/ @renseiai/plugin-linear — Linear issue tracker integration
server/ @renseiai/agentfactory-server — Redis queues, session storage, worker pool
cli/ @renseiai/agentfactory-cli — CLI tools (orchestrator, worker, fleet)
nextjs/ @renseiai/agentfactory-nextjs — Next.js route handlers, webhook, middleware
dashboard/ @renseiai/agentfactory-dashboard — Fleet management dashboard UI
mcp-server/ @renseiai/agentfactory-mcp-server — MCP server for external clients
code-intelligence/ @renseiai/agentfactory-code-intelligence — Tree-sitter AST parsing, BM25 search
create-app/ @renseiai/create-agentfactory-app — Project scaffolding tool
docs/ Documentation
examples/ Working code samples
linear (no internal deps)
└── core (depends on linear)
├── server (depends on core + linear)
├── cli (depends on core + linear + server)
└── nextjs (depends on core + linear + server)
create-app (no runtime deps — generates code that uses the other packages)
Turborepo handles building packages in the correct order.
- Fork the repo and create a branch from
main - Make your changes in the appropriate package
- Run
pnpm build && pnpm typecheckto verify everything compiles - Run
pnpm testto run the test suite - Open a pull request against
main
# Build just one package (and its dependencies)
pnpm --filter @renseiai/agentfactory-nextjs build
# Typecheck one package
pnpm --filter @renseiai/agentfactory-server typecheck
# Watch mode for tests
pnpm --filter @renseiai/agentfactory test:watchAll packages use ESM with .js extensions in relative imports:
// Correct — .js extension in source .ts files
import { foo } from './bar.js'
// Incorrect — no extension
import { foo } from './bar'This ensures emitted JS has proper extensions for Node.js ESM compatibility.
- Keep PRs focused — one feature or fix per PR
- Include a clear description of what changed and why
- Add tests for new functionality
- Update the relevant package's README.md if the public API changes
- Make sure CI passes (typecheck, build, secret scan)
To add support for a new coding agent:
- Create
packages/core/src/providers/my-provider.ts - Implement the
AgentProviderinterface - Map native SDK events to
AgentEventtypes - Add to the provider resolution in
packages/core/src/providers/index.ts - Add tests and documentation
See packages/core/src/providers/ for existing implementations.
Tool plugins expose CLI functionality as typed, in-process tools for Claude agents. This avoids subprocess overhead and gives agents Zod-validated parameters instead of CLI arg strings.
Claude Code has a fixed set of built-in tools (Read, Write, Bash, etc.) — you can't add new ones directly. The only extension mechanism is MCP (Model Context Protocol) servers. The Claude Agent SDK provides createSdkMcpServer() which creates an MCP server in the same process — no IPC, no child process, no network. The SDK discovers the tools and adds them to the model's tool palette alongside the built-ins.
- Create
packages/core/src/tools/plugins/my-plugin.ts - Implement the
ToolPlugininterface:
import { z } from 'zod'
import { tool, type SdkMcpToolDefinition } from '@anthropic-ai/claude-agent-sdk'
import type { ToolPlugin, ToolPluginContext } from '../types.js'
export const myPlugin: ToolPlugin = {
name: 'af-my-plugin', // MCP server name — tools appear as mcp__af-my-plugin__*
description: 'My custom tools',
createTools(context: ToolPluginContext): SdkMcpToolDefinition<any>[] {
const apiKey = context.env.MY_API_KEY
if (!apiKey) return [] // Return empty if prerequisites missing
return [
tool(
'af_my_action',
'Does something useful',
{ param: z.string().describe('The input parameter') },
async (args) => {
// Your logic here — runs in-process
const result = await doSomething(args.param, apiKey)
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
}
}
),
]
},
}- Register in
packages/core/src/orchestrator/orchestrator.ts:
import { myPlugin } from '../tools/plugins/my-plugin.js'
// In constructor:
this.toolRegistry.register(myPlugin)- Export from
packages/core/src/tools/index.ts - Add tests in
packages/core/src/tools/__tests__/
- Tool names:
af_{plugin}_{action}withsnake_case(e.g.,af_linear_get_issue) - Plugin
namefield becomes the MCP server name — keep it short, useaf-prefix - Return
[]fromcreateTools()when required env vars (API keys) are missing - Wrap errors as
{ isError: true, content: [{ type: 'text', text: 'Error: ...' }] } - Non-Claude providers ignore plugins — they continue using Bash-based CLI
Use GitHub Issues to report bugs or request features. Include:
- Steps to reproduce (for bugs)
- Expected vs actual behavior
- Node.js and pnpm versions
- Which package is affected
By contributing, you agree that your contributions will be licensed under the MIT License.