From 09aa2cbb66dea0efde758c1ce4ad97597fc5bf4d Mon Sep 17 00:00:00 2001 From: Kyle Mistele Date: Mon, 17 Aug 2026 16:28:48 -0700 Subject: [PATCH 1/3] limit codelayer subagent nesting HumanLayer-Session: https://app.dev.codelayer.gg/sessions/01a0120a-722c-783a-a7c1-5f8d44f2106f --- agents/codelayer/src/agent.ts | 10 ++++++ agents/codelayer/src/coding-subagent-tool.ts | 12 +++++++ agents/codelayer/test/agent.test.ts | 8 +++++ .../test/coding-subagent-tool.test.ts | 7 +++++ packages/agentlayer-core/src/agent.ts | 13 +++++++- packages/agentlayer-core/src/index.ts | 1 + .../agentlayer-core/src/tools/subagent.ts | 15 +++++++-- .../test/subagent-tool.test.ts | 31 +++++++++++++++++++ 8 files changed, 93 insertions(+), 4 deletions(-) diff --git a/agents/codelayer/src/agent.ts b/agents/codelayer/src/agent.ts index b815623..5550e11 100644 --- a/agents/codelayer/src/agent.ts +++ b/agents/codelayer/src/agent.ts @@ -354,6 +354,14 @@ function mergeHooks(base: ReturnType, hooks?: } } +function createForkConfig(tools: Record>) { + const { agent: _agentTool, ...nonDelegatingTools } = tools + return { + tools, + fork: { tools: nonDelegatingTools }, + } +} + export async function createCodelayerAgent(opts: CodelayerAgentOptions): Promise { const { model, @@ -468,6 +476,7 @@ export async function createCodelayerAgent(opts: CodelayerAgentOptions): Promise stopWhen: [doomLoop(3)], providerOptions, promptCacheKey, + fork: createForkConfig(tools), }) } @@ -515,6 +524,7 @@ export async function createCodelayerAgent(opts: CodelayerAgentOptions): Promise stopWhen: [doomLoop(3)], providerOptions, promptCacheKey, + fork: createForkConfig(tools), }) } diff --git a/agents/codelayer/src/coding-subagent-tool.ts b/agents/codelayer/src/coding-subagent-tool.ts index aefff38..eb08df3 100644 --- a/agents/codelayer/src/coding-subagent-tool.ts +++ b/agents/codelayer/src/coding-subagent-tool.ts @@ -50,6 +50,17 @@ const DEFAULT_CODE_SEARCH_TIMEOUT_MS = 30_000 const EXA_CONTEXT_ENDPOINT = 'https://api.exa.ai/context' const CONTEXT7_BASE_URL = 'https://context7.com' const CODELAYER_READ_TOOL_MODALITIES = ['text', 'image', 'pdf'] as const +const SUBAGENT_USAGE_INSTRUCTIONS = `Do not spawn subagents unless the user or applicable AGENTS.md or skill instructions explicitly ask for subagents, delegation, or parallel agent work. Requests for depth, thoroughness, research, investigation, or detailed codebase analysis do not count as permission to spawn. + +Only call this tool for a concrete, bounded, yet non-trivial subtask. + +When not to use subagents: +- If you want to read a specific file path. +- If you are searching for code within a specific file or set of two or three files. +- If no available agent is a good fit for the task; use other tools directly. +- Do not use rpi: subagents for trivial research tasks. + +When using subagents, use multiple subagents in parallel to parallelize discrete, bounded tasks.` export interface CreateCodingSubagentToolOptions extends CreateAgentFilesystemHooksOptions, @@ -443,6 +454,7 @@ export async function createCodingSubagentTool(opts: CreateCodingSubagentToolOpt const tool = createForkingSubagentsTool({ agents: configuredAgents, onChildEvent: opts.onChildEvent, + instructions: SUBAGENT_USAGE_INSTRUCTIONS, }) return Object.assign(tool, { subagents: configuredAgents }) } diff --git a/agents/codelayer/test/agent.test.ts b/agents/codelayer/test/agent.test.ts index 0bac8a3..41f31e9 100644 --- a/agents/codelayer/test/agent.test.ts +++ b/agents/codelayer/test/agent.test.ts @@ -111,6 +111,10 @@ function getAgentConfig(agent: object) { return agent as { model?: LanguageModel tools?: Record + forkConfig?: { + tools: Record + fork?: { tools: Record } + } system?: string | string[] hooks?: AgentConfig['hooks'] providerOptions?: Record @@ -686,6 +690,8 @@ describe('createCodelayerAgent', () => { expect(config.tools?.write).toBeDefined() expect(config.tools?.web_fetch).toBeDefined() expect(config.tools?.agent).toBeDefined() + expect(config.forkConfig?.tools.agent).toBe(config.tools?.agent) + expect(config.forkConfig?.fork?.tools.agent).toBeUndefined() expect(config.system?.length).toBeGreaterThan(0) // glob, grep, list removed - agent uses bash for file discovery expect(config.tools?.list).toBeUndefined() @@ -852,6 +858,8 @@ describe('createCodelayerAgent', () => { expect(config.tools?.grep).toBeUndefined() expect(config.tools?.glob).toBeUndefined() expect(config.tools?.agent).toBeDefined() + expect(config.forkConfig?.tools.agent).toBe(config.tools?.agent) + expect(config.forkConfig?.fork?.tools.agent).toBeUndefined() expect(config.tools?.web_fetch).toBeDefined() }) diff --git a/agents/codelayer/test/coding-subagent-tool.test.ts b/agents/codelayer/test/coding-subagent-tool.test.ts index 67cfa2b..e7e5d15 100644 --- a/agents/codelayer/test/coding-subagent-tool.test.ts +++ b/agents/codelayer/test/coding-subagent-tool.test.ts @@ -68,6 +68,13 @@ describe('createCodingSubagentTool', () => { expect(tool.description).toContain('Set subagent_type to start a fresh registered specialist') expect(tool.description).toContain('Every terminal result returns an agent_id') expect(tool.description).toContain('completion, error, or interruption') + expect(tool.description).toContain('Do not spawn subagents unless the user') + expect(tool.description).toContain('Requests for depth, thoroughness, research, investigation') + expect(tool.description).toContain('Only call this tool for a concrete, bounded, yet non-trivial subtask') + expect(tool.description).toContain('When not to use subagents:') + expect(tool.description).toContain('Do not use rpi: subagents for trivial research tasks') + expect(tool.description).toContain('use multiple subagents in parallel') + expect(tool.description).toContain('ability to spawn its own subagents') expect(tool.subagents.every((agent) => agent.resumable === true)).toBe(true) }) diff --git a/packages/agentlayer-core/src/agent.ts b/packages/agentlayer-core/src/agent.ts index d436681..3766508 100644 --- a/packages/agentlayer-core/src/agent.ts +++ b/packages/agentlayer-core/src/agent.ts @@ -48,6 +48,11 @@ export type ProviderOptions = Parameters[0]['providerOptions' export type ProviderOptionsFactory = (ctx: { runId: string; promptCacheKey?: string }) => ProviderOptions type StreamPart = TextStreamPart +export interface ForkAgentConfig { + tools: Record> + fork?: ForkAgentConfig +} + function isReasoningOnlyAssistantMessage(message: ModelMessage): boolean { if (message.role !== 'assistant' || !Array.isArray(message.content)) return false let hasReasoning = false @@ -95,6 +100,8 @@ export interface AgentConfig> = Rec contextWindowLimit?: number /** Provider-neutral compaction policy. Omitted means enabled with defaults. */ autoCompact?: AutoCompactConfig + /** Runtime configuration for fork children. Nested values configure later descendants. */ + fork?: ForkAgentConfig /** Called when an approval is requested. Fires before the event is pushed to the iterator. Observe-only, errors swallowed. */ onApprovalRequested?: ( approval: ApprovalRequest, @@ -297,6 +304,7 @@ export class Agent> = Record> = Record> = Record> = Record void + instructions?: string } /** @@ -192,7 +201,7 @@ function createSubagentsToolImplementation(opts: CreateSubagentsToolOptions, sup .map((agent) => `- ${agent.name}: ${agent.description}${agent.resumable ? ' (resumable)' : ''}`) .join('\n') const description = supportsForking - ? expandedDescription(agentList) + ? expandedDescription(agentList, opts.instructions) : SUBAGENT_DESCRIPTION_TEMPLATE.replace('{agents}', agentList) const inputSchema = supportsForking ? forkingSubagentInput diff --git a/packages/agentlayer-core/test/subagent-tool.test.ts b/packages/agentlayer-core/test/subagent-tool.test.ts index 105ab8a..b199ad1 100644 --- a/packages/agentlayer-core/test/subagent-tool.test.ts +++ b/packages/agentlayer-core/test/subagent-tool.test.ts @@ -971,6 +971,37 @@ describe('forking subagent tool', () => { }) }) + test('uses the configured fork chain to remove delegation from grandchildren', async () => { + const subagent = createForkingSubagentsTool({ agents: [] }) + const delegatingTools = { agent: subagent } + const root = new Agent({ + model: mockModel([ + assistantWithToolCall('agent', { prompt: 'delegate to a child' }), + assistantWithToolCall('agent', { prompt: 'delegate to a grandchild' }), + assistantWithToolCall('agent', { prompt: 'attempt a fourth level' }), + assistantText('child completed after the grandchild error'), + assistantText('root completed'), + ]), + tools: delegatingTools, + fork: { + tools: delegatingTools, + fork: { tools: {} }, + }, + }) + + const result = await root.run({ state: startState([userMessage('start')]) }).result + + expect(result.finishReason).toBe('complete') + const child = Object.values(result.state.terminalChildren ?? {})[0] + const grandchild = Object.values(child?.state.terminalChildren ?? {})[0] + expect(grandchild?.lastOutcome).toBe('error') + expect(grandchild?.state.messages.at(-1)).toMatchObject({ + role: 'assistant', + content: [{ type: 'tool-call', toolName: 'agent' }], + }) + expect(grandchild?.state.terminalChildren).toBeUndefined() + }) + test('does not recursively redispatch when the triggering parent instruction asks for a subagent', async () => { const triggeringInstruction = 'call a subagent to investigate this problem' const delegatedPrompt = 'Inspect the fork request and report what you find.' From 8a9d45aa9f67e1b68fbab987758fe2527455663a Mon Sep 17 00:00:00 2001 From: Kyle Mistele Date: Mon, 17 Aug 2026 17:04:32 -0700 Subject: [PATCH 2/3] refactor(core): centralize subagent usage guidance HumanLayer-Session: https://app.dev.codelayer.gg/sessions/01a0120a-722c-783a-a7c1-5f8d44f2106f --- agents/codelayer/src/coding-subagent-tool.ts | 12 ---------- .../agentlayer-core/src/tools/subagent.ts | 23 +++++++++++-------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/agents/codelayer/src/coding-subagent-tool.ts b/agents/codelayer/src/coding-subagent-tool.ts index eb08df3..aefff38 100644 --- a/agents/codelayer/src/coding-subagent-tool.ts +++ b/agents/codelayer/src/coding-subagent-tool.ts @@ -50,17 +50,6 @@ const DEFAULT_CODE_SEARCH_TIMEOUT_MS = 30_000 const EXA_CONTEXT_ENDPOINT = 'https://api.exa.ai/context' const CONTEXT7_BASE_URL = 'https://context7.com' const CODELAYER_READ_TOOL_MODALITIES = ['text', 'image', 'pdf'] as const -const SUBAGENT_USAGE_INSTRUCTIONS = `Do not spawn subagents unless the user or applicable AGENTS.md or skill instructions explicitly ask for subagents, delegation, or parallel agent work. Requests for depth, thoroughness, research, investigation, or detailed codebase analysis do not count as permission to spawn. - -Only call this tool for a concrete, bounded, yet non-trivial subtask. - -When not to use subagents: -- If you want to read a specific file path. -- If you are searching for code within a specific file or set of two or three files. -- If no available agent is a good fit for the task; use other tools directly. -- Do not use rpi: subagents for trivial research tasks. - -When using subagents, use multiple subagents in parallel to parallelize discrete, bounded tasks.` export interface CreateCodingSubagentToolOptions extends CreateAgentFilesystemHooksOptions, @@ -454,7 +443,6 @@ export async function createCodingSubagentTool(opts: CreateCodingSubagentToolOpt const tool = createForkingSubagentsTool({ agents: configuredAgents, onChildEvent: opts.onChildEvent, - instructions: SUBAGENT_USAGE_INSTRUCTIONS, }) return Object.assign(tool, { subagents: configuredAgents }) } diff --git a/packages/agentlayer-core/src/tools/subagent.ts b/packages/agentlayer-core/src/tools/subagent.ts index e4a130b..8040feb 100644 --- a/packages/agentlayer-core/src/tools/subagent.ts +++ b/packages/agentlayer-core/src/tools/subagent.ts @@ -154,16 +154,22 @@ export async function deriveChildPromptCacheKey(parentKey: string, toolCallId: s return `${safeParent}${suffix}` } -function expandedDescription(agentList: string, instructions?: string): string { +function expandedDescription(agentList: string): string { return `Launch an isolated subagent. -${ - instructions - ? `${instructions} +Do not spawn subagents unless the user or applicable AGENTS.md or skill instructions explicitly ask for subagents, delegation, or parallel agent work. Requests for depth, thoroughness, research, investigation, or detailed codebase analysis do not count as permission to spawn. -` - : '' -}Omit agent_id, fork_turns, and subagent_type to fork all eligible calling-agent conversation into a new child. Set fork_turns to "all", "none", or a positive integer string to control inherited conversation. Set subagent_type to start a fresh registered specialist with no inherited conversation. Every terminal result returns an agent_id; pass it with a follow-up prompt to continue that exact child after completion, error, or interruption. +Only call this tool for a concrete, bounded, yet non-trivial subtask. + +When not to use subagents: +- If you want to read a specific file path. +- If you are searching for code within a specific file or set of two or three files. +- If no available agent is a good fit for the task; use other tools directly. +- Do not use rpi: subagents for trivial research tasks. + +When using subagents, use multiple subagents in parallel to parallelize discrete, bounded tasks. + +Omit agent_id, fork_turns, and subagent_type to fork all eligible calling-agent conversation into a new child. Set fork_turns to "all", "none", or a positive integer string to control inherited conversation. Set subagent_type to start a fresh registered specialist with no inherited conversation. Every terminal result returns an agent_id; pass it with a follow-up prompt to continue that exact child after completion, error, or interruption. Using fork_turns or omitting subagent_type spawns a subagent with the same tools as you and the ability to spawn its own subagents. @@ -174,7 +180,6 @@ ${agentList}` interface CreateSubagentsToolOptions { agents: SubAgentConfig[] onChildEvent?: (event: AgentEvent) => void - instructions?: string } /** @@ -201,7 +206,7 @@ function createSubagentsToolImplementation(opts: CreateSubagentsToolOptions, sup .map((agent) => `- ${agent.name}: ${agent.description}${agent.resumable ? ' (resumable)' : ''}`) .join('\n') const description = supportsForking - ? expandedDescription(agentList, opts.instructions) + ? expandedDescription(agentList) : SUBAGENT_DESCRIPTION_TEMPLATE.replace('{agents}', agentList) const inputSchema = supportsForking ? forkingSubagentInput From 9d5c5ba5d18e5d3e8237dc260bb379618109b4e2 Mon Sep 17 00:00:00 2001 From: Kyle Mistele Date: Mon, 17 Aug 2026 17:08:38 -0700 Subject: [PATCH 3/3] test: remove tool description assertions HumanLayer-Session: https://app.dev.codelayer.gg/sessions/01a0120a-722c-783a-a7c1-5f8d44f2106f --- agents/codelayer/test/agent.test.ts | 16 +++++----- .../test/coding-subagent-tool.test.ts | 29 +------------------ .../test/structured-output-tool.test.ts | 13 --------- .../test/subagent-tool.test.ts | 19 ------------ .../test/tool-interfaces.test.ts | 23 --------------- .../test/coding-agent.test.ts | 29 ------------------- .../test/glob-tool.test.ts | 4 --- .../test/grep-tool.test.ts | 4 --- .../test/list-tool.test.ts | 4 --- .../test/read-tool.test.ts | 4 --- .../agentlayer-filesystem/test/skill.test.ts | 25 ---------------- .../test/codex-ws-adapter.test.ts | 21 -------------- 12 files changed, 9 insertions(+), 182 deletions(-) diff --git a/agents/codelayer/test/agent.test.ts b/agents/codelayer/test/agent.test.ts index 41f31e9..afe6996 100644 --- a/agents/codelayer/test/agent.test.ts +++ b/agents/codelayer/test/agent.test.ts @@ -837,10 +837,10 @@ describe('createCodelayerAgent', () => { context7ApiKey: 'context7-test-key', }) const config = getAgentConfig(agent) - const subagent = config.tools?.agent as { description?: string } | undefined + const subagents = getSubagents(config.tools?.agent) - expect(subagent?.description).toContain('library-researcher') - expect(subagent?.description).toContain('rpi:implementer-agent') + expect(subagents.some((subagent) => subagent.name === 'library-researcher')).toBe(true) + expect(subagents.some((subagent) => subagent.name === 'rpi:implementer-agent')).toBe(true) }) test('creates an rlm codex agent without bash and with apply_patch', async () => { @@ -1221,7 +1221,6 @@ describe('createCodingSubagentTool', () => { const tool = getAgentConfig(agent).tools?.agent as Tool | undefined expect(tool?.input.safeParse({ prompt: 'inherit and inspect' }).success).toBe(true) - expect(tool?.description).toContain('fork all eligible calling-agent conversation') }) test('creates the standard subagent tool wrapper', async () => { @@ -1231,9 +1230,10 @@ describe('createCodingSubagentTool', () => { }) expect(tool.name).toBe('subagent') - expect(tool.description).toContain('general-purpose') - expect(tool.description).toContain('rpi:implementer-agent') - expect(tool.description).toContain('rpi:codebase-locator') + const subagents = getSubagents(tool) + expect(subagents.some((subagent) => subagent.name === 'general-purpose')).toBe(true) + expect(subagents.some((subagent) => subagent.name === 'rpi:implementer-agent')).toBe(true) + expect(subagents.some((subagent) => subagent.name === 'rpi:codebase-locator')).toBe(true) }) test('hard-codes multimodal read for coding subagents', async () => { @@ -1289,7 +1289,7 @@ describe('createCodingSubagentTool', () => { context7ApiKey: 'context7-test-key', }) - expect(tool.description).toContain('library-researcher') + expect(getSubagents(tool).some((subagent) => subagent.name === 'library-researcher')).toBe(true) }) }) diff --git a/agents/codelayer/test/coding-subagent-tool.test.ts b/agents/codelayer/test/coding-subagent-tool.test.ts index e7e5d15..a81ae2e 100644 --- a/agents/codelayer/test/coding-subagent-tool.test.ts +++ b/agents/codelayer/test/coding-subagent-tool.test.ts @@ -38,7 +38,7 @@ describe('createCodingSubagentTool', () => { system: 'test system prompt', }) const input = tool.input as any - const shape = input.shape as Record + const shape = input.shape as Record expect(Object.keys(shape)).toEqual([ 'description', @@ -50,31 +50,6 @@ describe('createCodingSubagentTool', () => { ]) expect(input.safeParse({ prompt: 'delegate this' }).success).toBe(true) expect(input.safeParse({ prompt: 'delegate this', unknown: true }).success).toBe(false) - expect(shape.description?.description).toBe('Short description of the subagent task.') - expect(shape.prompt?.description).toBe( - 'Task for the subagent. Custom-role tasks must be self-contained because they do not inherit the conversation.', - ) - expect(shape.agent_id?.description).toBe( - 'Continue an existing subagent using an ID from an earlier result. Do not combine with fork_turns or subagent_type.', - ) - expect(shape.fork_turns?.description).toBe( - 'Conversation to inherit: "all", "none", or a positive integer string such as "3". Omitted means "all". Do not combine with agent_id or subagent_type.', - ) - expect(shape.subagent_type?.description).toBe( - 'Start a registered specialist without inheriting the calling agent conversation. Do not combine with agent_id or fork_turns.', - ) - expect(shape.skill?.description).toBe('Optional skill to preload into the subagent.') - expect(tool.description).toContain('Omit agent_id, fork_turns, and subagent_type') - expect(tool.description).toContain('Set subagent_type to start a fresh registered specialist') - expect(tool.description).toContain('Every terminal result returns an agent_id') - expect(tool.description).toContain('completion, error, or interruption') - expect(tool.description).toContain('Do not spawn subagents unless the user') - expect(tool.description).toContain('Requests for depth, thoroughness, research, investigation') - expect(tool.description).toContain('Only call this tool for a concrete, bounded, yet non-trivial subtask') - expect(tool.description).toContain('When not to use subagents:') - expect(tool.description).toContain('Do not use rpi: subagents for trivial research tasks') - expect(tool.description).toContain('use multiple subagents in parallel') - expect(tool.description).toContain('ability to spawn its own subagents') expect(tool.subagents.every((agent) => agent.resumable === true)).toBe(true) }) @@ -90,7 +65,6 @@ describe('createCodingSubagentTool', () => { tool.input.safeParse({ description: 'small task', prompt: 'work', subagent_type: 'general-purpose' }).success, ).toBe(true) expect(tool.input.safeParse({ prompt: 'continue', agent_id: 'prior-child' }).success).toBe(true) - expect(tool.description).toContain('Omit agent_id, fork_turns, and subagent_type') expect(tool.subagents.every((agent) => agent.resumable === true)).toBe(true) }) @@ -105,7 +79,6 @@ describe('createCodingSubagentTool', () => { expect(OUTLINE_IMPLEMENTER_AGENT_NAME).toBe('rpi:outline-implementer-agent') expect(subagents).toHaveLength(1) - expect(subagents[0]?.description).toContain('Implements structure outlines') }) test('shares the configured skill tool with every sub-agent', async () => { diff --git a/packages/agentlayer-core/test/structured-output-tool.test.ts b/packages/agentlayer-core/test/structured-output-tool.test.ts index d8b2035..576c8eb 100644 --- a/packages/agentlayer-core/test/structured-output-tool.test.ts +++ b/packages/agentlayer-core/test/structured-output-tool.test.ts @@ -40,19 +40,6 @@ describe('createStructuredOutputTool', () => { expect(tool.name).toBe('structured_output') }) - test('includes generated JSON schema in the description', () => { - const tool = createStructuredOutputTool( - z.object({ - name: z.string(), - age: z.number(), - }), - ) - - expect(tool.description).toContain('JSON Schema') - expect(tool.description).toContain('"name"') - expect(tool.description).toContain('"age"') - }) - test('serializes typed data to JSON', async () => { const tool = createStructuredOutputTool(z.object({ answer: z.number() })) const result = await tool.execute({ data: { answer: 42 } }, makeToolContext()) diff --git a/packages/agentlayer-core/test/subagent-tool.test.ts b/packages/agentlayer-core/test/subagent-tool.test.ts index b199ad1..25b8bd8 100644 --- a/packages/agentlayer-core/test/subagent-tool.test.ts +++ b/packages/agentlayer-core/test/subagent-tool.test.ts @@ -94,25 +94,6 @@ function createLocalReadTool(cwd: string) { // ── Tests ──────────────────────────────────────────────────────────────────── describe('createSubagentsTool', () => { - test('description includes all registered agent names', () => { - const childAgent = new Agent({ - model: mockModel([assistantText('hi')]), - tools: { echo: echoTool }, - }) - - const tool = createSubagentsTool({ - agents: [ - { name: 'researcher', description: 'Deep codebase research', agent: childAgent }, - { name: 'implementer', description: 'Implement from a plan', agent: childAgent }, - ], - }) - - expect(tool.description).toContain('researcher') - expect(tool.description).toContain('Deep codebase research') - expect(tool.description).toContain('implementer') - expect(tool.description).toContain('Implement from a plan') - }) - test('invalid subagent_type returns error in tool result', async () => { const childAgent = new Agent({ model: mockModel([assistantText('hi')]), diff --git a/packages/agentlayer-core/test/tool-interfaces.test.ts b/packages/agentlayer-core/test/tool-interfaces.test.ts index 297e84a..ca33e5e 100644 --- a/packages/agentlayer-core/test/tool-interfaces.test.ts +++ b/packages/agentlayer-core/test/tool-interfaces.test.ts @@ -749,26 +749,3 @@ describe('normalizeEscapes', () => { expect(normalizeEscapes('\\n\\n\\t')).toBe('\n\n\t') }) }) - -// ─── define() with description override ─────────────────────────────────────── - -describe('define() with description override', () => { - test('EditTool.define() accepts custom description', () => { - const tool = EditTool.define(async () => ({ content: '', matchCount: 0 }), { - description: 'Custom edit description', - }) - expect(tool.description).toBe('Custom edit description') - }) - - test('WriteTool.define() uses default description when no override', () => { - const tool = WriteTool.define(async () => 'ok') - expect(tool.description).toBe('Write content to a file, creating it if it does not exist') - }) - - test('ApplyPatchTool.define() accepts custom description', () => { - const tool = ApplyPatchTool.define(async () => 'ok', { - description: 'Apply Codex patches', - }) - expect(tool.description).toBe('Apply Codex patches') - }) -}) diff --git a/packages/agentlayer-filesystem/test/coding-agent.test.ts b/packages/agentlayer-filesystem/test/coding-agent.test.ts index b70bc0b..a70aba1 100644 --- a/packages/agentlayer-filesystem/test/coding-agent.test.ts +++ b/packages/agentlayer-filesystem/test/coding-agent.test.ts @@ -14,7 +14,6 @@ import { createClaudeCodingAgentToolset, createCodexAgentFilesystemToolset, createCodexCodingAgentToolset, - createSkillToolFromRepoDirs, } from '../src' import { makeToolContext } from './mocks' @@ -58,25 +57,6 @@ async function withHomeEnvironment(home: string, run: () => Promise): Prom } } -async function withSkillResolutionFixture(run: (nestedCwd: string) => Promise): Promise { - return withTemporaryDirectory('agentlayer-skill-repo-', async (repoDir) => - withTemporaryDirectory('agentlayer-other-cwd-', async (unrelatedDir) => { - await initGitRepo(repoDir) - await mkdir(join(repoDir, '.claude', 'skills'), { recursive: true }) - await writeFile(join(repoDir, '.claude', 'skills', 'plan.md'), '# Plan\n\nDo the plan.') - const nestedCwd = join(repoDir, 'packages', 'app') - await mkdir(nestedCwd, { recursive: true }) - const originalCwd = process.cwd() - process.chdir(unrelatedDir) - try { - return await run(nestedCwd) - } finally { - process.chdir(originalCwd) - } - }), - ) -} - async function buildPublicPromptWithMixedInstructionSources() { const originalHome = process.env.HOME return withTemporaryDirectory('agentlayer-system-prompt-', async (fixtureRoot) => { @@ -147,15 +127,6 @@ async function withEmptyPromptFixture(run: (repoDir: string) => Promise): ) } -describe('createSkillToolFromRepoDirs', () => { - test('when the process cwd is unrelated, the provided cwd determines the repository whose skills are loaded', async () => { - await withSkillResolutionFixture(async (nestedCwd) => { - const skillTool = await createSkillToolFromRepoDirs({ cwd: nestedCwd }) - expect(skillTool.description).toContain('plan') - }) - }) -}) - describe('createAgentSystemPrompt', () => { test('the public prompt builder derives HOME from the environment and renders user, root, then cwd instructions', async () => { const result = await buildPublicPromptWithMixedInstructionSources() diff --git a/packages/agentlayer-filesystem/test/glob-tool.test.ts b/packages/agentlayer-filesystem/test/glob-tool.test.ts index 7b04388..8f4b9c1 100644 --- a/packages/agentlayer-filesystem/test/glob-tool.test.ts +++ b/packages/agentlayer-filesystem/test/glob-tool.test.ts @@ -13,10 +13,6 @@ describe('GlobTool interface', () => { expect(GlobTool.name).toBe('glob') }) - test('has non-empty description', () => { - expect(GlobTool.description.length).toBeGreaterThan(0) - }) - test('define() returns a tool with name "glob"', () => { const tool = GlobTool.define(async () => []) expect(tool.name).toBe('glob') diff --git a/packages/agentlayer-filesystem/test/grep-tool.test.ts b/packages/agentlayer-filesystem/test/grep-tool.test.ts index 50af444..f0ab076 100644 --- a/packages/agentlayer-filesystem/test/grep-tool.test.ts +++ b/packages/agentlayer-filesystem/test/grep-tool.test.ts @@ -13,10 +13,6 @@ describe('GrepTool interface', () => { expect(GrepTool.name).toBe('grep') }) - test('has non-empty description', () => { - expect(GrepTool.description.length).toBeGreaterThan(0) - }) - test('define() returns a tool with name "grep"', () => { const tool = GrepTool.define(async () => []) expect(tool.name).toBe('grep') diff --git a/packages/agentlayer-filesystem/test/list-tool.test.ts b/packages/agentlayer-filesystem/test/list-tool.test.ts index 6101c4b..4830fd5 100644 --- a/packages/agentlayer-filesystem/test/list-tool.test.ts +++ b/packages/agentlayer-filesystem/test/list-tool.test.ts @@ -13,10 +13,6 @@ describe('ListTool interface', () => { expect(ListTool.name).toBe('list') }) - test('has non-empty description', () => { - expect(ListTool.description.length).toBeGreaterThan(0) - }) - test('define() returns a tool with name "list"', () => { const tool = ListTool.define(async () => []) expect(tool.name).toBe('list') diff --git a/packages/agentlayer-filesystem/test/read-tool.test.ts b/packages/agentlayer-filesystem/test/read-tool.test.ts index d7ff42b..59e1bf8 100644 --- a/packages/agentlayer-filesystem/test/read-tool.test.ts +++ b/packages/agentlayer-filesystem/test/read-tool.test.ts @@ -23,10 +23,6 @@ describe('ReadTool interface', () => { expect(ReadTool.name).toBe('read') }) - test('has non-empty description', () => { - expect(ReadTool.description.length).toBeGreaterThan(0) - }) - test('define() returns a tool with the correct name', () => { const tool = ReadTool.define(async () => 'content') expect(tool.name).toBe('read') diff --git a/packages/agentlayer-filesystem/test/skill.test.ts b/packages/agentlayer-filesystem/test/skill.test.ts index 130535a..8f59ce2 100644 --- a/packages/agentlayer-filesystem/test/skill.test.ts +++ b/packages/agentlayer-filesystem/test/skill.test.ts @@ -136,20 +136,6 @@ describe('createSkillTool', () => { }) describe('createSkillToolFromDirs', () => { - test('reads flat .md skills from a directory', async () => { - const dir = await mkdtemp(join(tmpdir(), 'skill-test-')) - try { - await writeFile(join(dir, 'my-skill.md'), '# My Skill\n\nThis is my skill content.') - await writeFile(join(dir, 'another.md'), '# Another\n\nAnother skill.') - - const skillTool = await createSkillToolFromDirs({ dirs: dir }) - expect(skillTool.description).toContain('my-skill') - expect(skillTool.description).toContain('another') - } finally { - await rm(dir, { recursive: true }) - } - }) - test('SKILL.md convention sets baseDir to the skill directory', async () => { const dir = await mkdtemp(join(tmpdir(), 'skill-test-')) try { @@ -169,15 +155,4 @@ describe('createSkillToolFromDirs', () => { await rm(dir, { recursive: true }) } }) - - test('namespace prefixes loaded skill names', async () => { - const dir = await mkdtemp(join(tmpdir(), 'skill-test-')) - try { - await writeFile(join(dir, 'plan.md'), '# Plan\n\nPlan instructions.') - const skillTool = await createSkillToolFromDirs({ dirs: [{ path: dir, namespace: 'rpi' }] }) - expect(skillTool.description).toContain('rpi:plan') - } finally { - await rm(dir, { recursive: true }) - } - }) }) diff --git a/packages/agentlayer-provider-openai-codex/test/codex-ws-adapter.test.ts b/packages/agentlayer-provider-openai-codex/test/codex-ws-adapter.test.ts index 22077a0..1bee7b7 100644 --- a/packages/agentlayer-provider-openai-codex/test/codex-ws-adapter.test.ts +++ b/packages/agentlayer-provider-openai-codex/test/codex-ws-adapter.test.ts @@ -441,14 +441,8 @@ describe('convertTools', () => { expect(result).toHaveLength(1) expect(result[0]!.name).toBe('subagent') - expect(result[0]!.description).toBe('Dispatch a subagent') expect(result[0]!.inputSchema.required).toEqual(['prompt']) expect(result[0]!.inputSchema.additionalProperties).toBe(false) - const props = result[0]!.inputSchema.properties as Record> - expect(props.prompt!.description).toBe('Task or follow-up for the subagent.') - expect(props.agent_id!.description).toBe('Continue an existing subagent.') - expect(props.fork_turns!.description).toBe('Conversation to inherit.') - expect(props.subagent_type!.description).toBe('Start a registered specialist.') }) test('returns empty array for undefined tools', () => { @@ -469,21 +463,6 @@ describe('convertTools', () => { expect(result).toHaveLength(0) }) - - test('handles tools without description', () => { - const tools: LanguageModelV3CallOptions['tools'] = [ - { - type: 'function', - name: 'noop', - inputSchema: { type: 'object', properties: {} }, - }, - ] - - const result = convertTools(tools) - - expect(result).toHaveLength(1) - expect(result[0]!.description).toBe('') - }) }) // ---------------------------------------------------------------------------