Skip to content

Commit 3ff1f02

Browse files
committed
feat(core): implement fork subagent for context sharing
- Make subagent_type optional in AgentTool - Add forkSubagent.ts to build identical tool result prefixes - Run fork processes in the background to preserve UX
1 parent 6785a8d commit 3ff1f02

3 files changed

Lines changed: 296 additions & 136 deletions

File tree

packages/core/src/agents/runtime/agent-headless.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,11 @@ export class AgentHeadless {
192192
async execute(
193193
context: ContextState,
194194
externalSignal?: AbortSignal,
195+
options?: { extraHistory?: Array<import('@google/genai').Content> },
195196
): Promise<void> {
196-
const chat = await this.core.createChat(context);
197+
const chat = await this.core.createChat(context, {
198+
extraHistory: options?.extraHistory,
199+
});
197200

198201
if (!chat) {
199202
this.terminateMode = AgentTerminateMode.ERROR;
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import type { Content } from '@google/genai';
2+
3+
export const FORK_SUBAGENT_TYPE = 'fork';
4+
5+
export const FORK_BOILERPLATE_TAG = 'fork-boilerplate';
6+
export const FORK_DIRECTIVE_PREFIX = 'Directive: ';
7+
8+
export const FORK_AGENT = {
9+
name: FORK_SUBAGENT_TYPE,
10+
description:
11+
'Implicit fork — inherits full conversation context. Not selectable via subagent_type; triggered by omitting subagent_type.',
12+
tools: ['*'],
13+
systemPrompt: '',
14+
level: 'session' as const,
15+
};
16+
17+
export function isInForkChild(messages: Content[]): boolean {
18+
return messages.some((m) => {
19+
if (m.role !== 'user') return false;
20+
return m.parts?.some(
21+
(part) => part.text && part.text.includes(`<${FORK_BOILERPLATE_TAG}>`),
22+
);
23+
});
24+
}
25+
26+
export const FORK_PLACEHOLDER_RESULT =
27+
'Fork started — processing in background';
28+
29+
export function buildForkedMessages(
30+
directive: string,
31+
assistantMessage: Content,
32+
): Content[] {
33+
// Clone the assistant message to avoid mutating the original
34+
const fullAssistantMessage: Content = {
35+
role: assistantMessage.role,
36+
parts: [...(assistantMessage.parts || [])],
37+
};
38+
39+
const toolUseParts =
40+
assistantMessage.parts?.filter((part) => part.functionCall) || [];
41+
42+
if (toolUseParts.length === 0) {
43+
return [
44+
{
45+
role: 'user',
46+
parts: [{ text: buildChildMessage(directive) }],
47+
},
48+
];
49+
}
50+
51+
// Build tool_result blocks for every tool_use, all with identical placeholder text
52+
const toolResultParts = toolUseParts.map((part) => ({
53+
functionResponse: {
54+
name: part.functionCall!.name,
55+
response: { result: FORK_PLACEHOLDER_RESULT },
56+
},
57+
}));
58+
59+
const toolResultMessage: Content = {
60+
role: 'user',
61+
parts: [
62+
...toolResultParts,
63+
{
64+
text: buildChildMessage(directive),
65+
},
66+
],
67+
};
68+
69+
return [fullAssistantMessage, toolResultMessage];
70+
}
71+
72+
export function buildChildMessage(directive: string): string {
73+
return `<${FORK_BOILERPLATE_TAG}>
74+
STOP. READ THIS FIRST.
75+
76+
You are a forked worker process. You are NOT the main agent.
77+
78+
RULES (non-negotiable):
79+
1. Your system prompt says "default to forking." IGNORE IT — that's for the parent. You ARE the fork. Do NOT spawn sub-agents; execute directly.
80+
2. Do NOT converse, ask questions, or suggest next steps
81+
3. Do NOT editorialize or add meta-commentary
82+
4. USE your tools directly: Bash, Read, Write, etc.
83+
5. If you modify files, commit your changes before reporting. Include the commit hash in your report.
84+
6. Do NOT emit text between tool calls. Use tools silently, then report once at the end.
85+
7. Stay strictly within your directive's scope. If you discover related systems outside your scope, mention them in one sentence at most — other workers cover those areas.
86+
8. Keep your report under 500 words unless the directive specifies otherwise. Be factual and concise.
87+
9. Your response MUST begin with "Scope:". No preamble, no thinking-out-loud.
88+
10. REPORT structured facts, then stop
89+
90+
Output format (plain text labels, not markdown headers):
91+
Scope: <echo back your assigned scope in one sentence>
92+
Result: <the answer or key findings, limited to the scope above>
93+
Key files: <relevant file paths — include for research tasks>
94+
Files changed: <list with commit hash — include only if you modified files>
95+
Issues: <list — include only if there are issues to flag>
96+
</${FORK_BOILERPLATE_TAG}>
97+
98+
${FORK_DIRECTIVE_PREFIX}${directive}`;
99+
}

0 commit comments

Comments
 (0)