KIBZP6K46EIBYQS2XLVG7FKY22BULCBES4YN45BUQPEDO55WPSPAC 1. Call the `pijul_manager` tool with action "spawn" and the feature name.2. Share the workspace path with the user.3. Open a subtask for the feature agent that includes:- Goals, constraints, and acceptance criteria from the user.- The rule: run `pijul_manager` action "sync" before "push".- The rule: record must always be non-interactive (use `-a`, a message, and `--no-prompt`); never open an editor.- The rule: push must always be non-interactive (use `-a` and `--no-prompt`).- The rule: all work stays on trunk in the forked workspace; use the `parent` remote (default remote points to the parent repo) for sync/push.- The rule: resolve conflicts locally and keep trunk clean.
The spawn tool now creates workspaces and subtask sessions together. Do not open subtasks manually. The parent should only coordinate and wait for subtask updates.1. Call the `pijul_manager` tool with action "spawn" and include:- feature + description + prompt + agent (single spawn), or- spawns: an array of { feature, description, prompt, agent, parentFeature?, parentAgentId?, parentSessionId?, workspaceRoot? }2. Share the workspace path(s) and session id(s) returned by the tool.3. Do not open subtasks manually; the tool creates them automatically with the provided prompts.
async function runCommand(command: string, args: string[], cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {const proc = Bun.spawn([command, ...args], {
async function runPijulCommand(args: string[], cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {const proc = Bun.spawn(['pijul', ...args], {
}async function pijulChannel(root: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {return runPijulCommand(['channel'], root);}async function pijulDiff(cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {return runPijulCommand(['diff'], cwd);}async function pijulCloneRepo(source: string, destination: string, cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {return runPijulCommand(['clone', source, destination, '--no-prompt'], cwd);}async function pijulPullFromChannel(cwd: string, channel: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {return runPijulCommand(['pull', '--from-channel', channel, '-a', '--no-prompt'], cwd);}async function pijulAddAll(cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {return runPijulCommand(['add', '--recursive', '--no-prompt', '.'], cwd);}async function pijulRecordAll(cwd: string, message: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {return runPijulCommand(['record', '-a', '-m', message, '--no-prompt'], cwd);
export const AnarchyPlugin: Plugin = async () => {
function resolveSpawnRequests(args: PijulManagerArgs): SpawnRequest[] {if (args.spawns && args.spawns.length > 0) return args.spawns;if (!args.feature) return [];if (!args.description || !args.prompt || !args.agent) return [];return [{feature: args.feature,description: args.description,prompt: args.prompt,agent: args.agent,parentFeature: args.parentFeature,parentAgentId: args.parentAgentId,parentSessionId: args.parentSessionId,workspaceRoot: args.workspaceRoot,},];}function argsContainPijul(value: unknown): boolean {if (!value) return false;if (typeof value === 'string') return /(^|\s)pijul(\s|$)/.test(value);if (Array.isArray(value)) return value.some((entry) => argsContainPijul(entry));if (typeof value === 'object') {return Object.values(value as Record<string, unknown>).some((entry) => argsContainPijul(entry));}return false;}async function createSubtaskSession(input: {client: PluginInput['client'];directory: string;parentSessionId: string;agent: string;title: string;prompt: string;}): Promise<string> {const createResponse = await input.client.session.create({query: { directory: input.directory },body: { parentID: input.parentSessionId, title: input.title },});const createError = (createResponse as { error?: { message?: string } }).error;if (!createResponse || createError) {const error = createError;throw new Error(error?.message ?? 'Failed to create subtask session.');}const session = (createResponse as { data?: { id?: string } }).data;if (!session?.id) throw new Error('Failed to create subtask session.');const promptResponse = await input.client.session.prompt({path: { id: session.id },query: { directory: input.directory },body: {agent: input.agent,parts: [{type: 'text',text: input.prompt,},],},});const promptError = (promptResponse as { error?: { message?: string } }).error;if (!promptResponse || promptError) {const error = promptError;throw new Error(error?.message ?? 'Failed to start subtask session.');}return session.id;}export const AnarchyPlugin: Plugin = async (input) => {
description: z.string().optional(),prompt: z.string().optional(),spawns: z.array(z.object({feature: z.string(),description: z.string(),prompt: z.string(),agent: z.string(),parentFeature: z.string().optional(),parentAgentId: z.string().optional(),parentSessionId: z.string().optional(),workspaceRoot: z.string().optional(),}),).optional(),
if (!args.feature) return 'Missing feature name.';const featureKey = slugify(args.feature);if (!featureKey) return 'Invalid feature name.';const agentId = args.agentId ?? args.agent;const sessionId = args.sessionId;if (!agentId || !sessionId) {return 'Missing agentId/sessionId for feature; provide both for the child agent.';
const spawnRequests = resolveSpawnRequests(args);if (spawnRequests.length === 0) {return 'Missing spawn details. Provide feature + description + prompt + agent, or use spawns[].';
const parentKey = args.parentFeature ? slugify(args.parentFeature) : undefined;let parentFeature: FeatureState | undefined;let depth = DEFAULT_FEATURE_DEPTH;let parentAgentId = args.parentAgentId;let parentSessionId = args.parentSessionId;if (parentKey) {parentFeature = state.features[parentKey];if (!parentFeature) return 'Unknown parent feature; spawn the parent first.';depth = (parentFeature.depth ?? DEFAULT_FEATURE_DEPTH) + 1;if (depth > MAX_FEATURE_DEPTH) {return `Max feature depth ${MAX_FEATURE_DEPTH} exceeded.`;
const results: Array<{ feature: string; workspace: string; sessionId?: string; status: string }> = [];const subtaskJobs: Array<{featureKey: string;workspace: string;agent: string;title: string;prompt: string;parentSessionId: string;}> = [];for (const request of spawnRequests) {const featureKey = slugify(request.feature);if (!featureKey) return 'Invalid feature name.';if (!request.agent || !request.description || !request.prompt) {return `Missing subtask details for ${request.feature}; include agent, description, and prompt.`;
parentAgentId = parentAgentId ?? parentFeature.agentId ?? parentFeature.agent;parentSessionId = parentSessionId ?? parentFeature.sessionId;if (!parentAgentId || !parentSessionId) {return 'Missing parent agentId/sessionId; provide them to spawn a child.';
const parentKey = request.parentFeature ? slugify(request.parentFeature) : undefined;let parentFeature: FeatureState | undefined;let depth = DEFAULT_FEATURE_DEPTH;let parentAgentId = request.parentAgentId ?? args.parentAgentId ?? context.agent;let parentSessionId = request.parentSessionId ?? args.parentSessionId ?? context.sessionID;if (parentKey) {parentFeature = state.features[parentKey];if (!parentFeature) return 'Unknown parent feature; spawn the parent first.';depth = (parentFeature.depth ?? DEFAULT_FEATURE_DEPTH) + 1;if (depth > MAX_FEATURE_DEPTH) {return `Max feature depth ${MAX_FEATURE_DEPTH} exceeded.`;}parentAgentId = parentAgentId ?? parentFeature.agentId ?? parentFeature.agent ?? context.agent;parentSessionId = parentSessionId ?? parentFeature.sessionId ?? context.sessionID;
}const workspaceRoot = args.workspaceRoot? path.resolve(args.workspaceRoot): path.join(os.tmpdir(), 'opencode-pijul-workspaces');const workspace = path.join(workspaceRoot, featureKey);
await ensureDir(workspaceRoot);
const workspaceRoot = request.workspaceRoot? path.resolve(request.workspaceRoot): args.workspaceRoot? path.resolve(args.workspaceRoot): path.join(os.tmpdir(), 'opencode-pijul-workspaces');const workspace = path.join(workspaceRoot, featureKey);await ensureDir(workspaceRoot);
const cloneResult = await runCommand('pijul', ['clone', root, workspace, '--no-prompt'], root);if (cloneResult.exitCode !== 0) {return `Failed to clone repo: ${cloneResult.stderr || cloneResult.stdout}`;
if (!sessionId) {subtaskJobs.push({featureKey,workspace,agent: request.agent,title: request.description,prompt: `${request.prompt.trim()}\n\nWorkspace: ${workspace}\nFeature: ${featureKey}`,parentSessionId: parentSessionId ?? context.sessionID,});}
await ensureDefaultRemote(workspace, root);
const subtaskResults = await Promise.allSettled(subtaskJobs.map((job) =>createSubtaskSession({client: input.client,directory: input.directory,parentSessionId: job.parentSessionId,agent: job.agent,title: job.title,prompt: job.prompt,}).then((sessionId) => ({ featureKey: job.featureKey, sessionId })),),);
state.features[featureKey] = {feature: featureKey,workspace,agent: agentId,agentId,sessionId,parentFeature: parentKey,parentAgentId,parentSessionId,childFeatures: [],depth,status: 'active',};if (parentKey && parentFeature) {const children = new Set(parentFeature.childFeatures ?? []);children.add(featureKey);parentFeature.childFeatures = Array.from(children);
const subtaskErrors: string[] = [];for (const result of subtaskResults) {if (result.status === 'fulfilled') {const featureState = state.features[result.value.featureKey];if (featureState) featureState.sessionId = result.value.sessionId;const entry = results.find((item) => item.feature === result.value.featureKey);if (entry) entry.sessionId = result.value.sessionId;} else {subtaskErrors.push(result.reason instanceof Error ? result.reason.message : String(result.reason));}
return `Spawned ${featureKey} in ${workspace} on trunk.`;
const lines = results.map((item) => {const session = item.sessionId ? ` session=${item.sessionId}` : ' session=pending';return `${item.feature} ${item.status} workspace=${item.workspace}${session}`;});if (subtaskErrors.length > 0) {lines.push(`Subtask errors: ${subtaskErrors.join(' | ')}`);}return lines.join('\n');
const pullResult = await runCommand('pijul',['pull', '--from-channel', state.trunkChannel, '-a', '--no-prompt'],feature.workspace,);
const pullResult = await pijulPullFromChannel(feature.workspace, state.trunkChannel);
const pushResult = await runCommand('pijul',['push', '--to-channel', state.trunkChannel, '-a', '--no-prompt'],feature.workspace,);
const pushResult = await pijulPushToChannel(feature.workspace, state.trunkChannel);
- feature: the requested feature name- agentId: the child agent id- sessionId: the child agent session id- parentFeature: the parent feature name (if this is a child of another feature)- parentAgentId: the parent agent id (if applicable)- parentSessionId: the parent session id (if applicable)2. Share the workspace path with the user.3. Open a subtask for the feature agent that includes:- Goals, constraints, and acceptance criteria from the user.- The workflow: sync → resolve conflicts → record → push → verify trunk clean → report summary.- The rule: all work stays on trunk in the forked workspace; use the `parent` remote (default remote points to the parent repo) for sync/push.- The rule: record must always be non-interactive (use `-a`, a message, and `--no-prompt`); never open an editor.- The rule: push must always be non-interactive (use `-a` and `--no-prompt`).- The rule: communicate with the parent only; escalate to the user through the parent if information is missing.- The depth limit: maximum depth is 5.
- feature + description + prompt + agent (single spawn), or- spawns: an array of { feature, description, prompt, agent, parentFeature?, parentAgentId?, parentSessionId?, workspaceRoot? }2. Share the workspace path(s) and session id(s) returned by the tool.3. Do not open subtasks manually; the tool creates them automatically with the provided prompts.