From 98d5f55102c27c73ad4d44f2c7d5f7c7a27a6d9f Mon Sep 17 00:00:00 2001 From: Egbert Eich Date: Tue, 9 Jun 2026 09:41:14 +0200 Subject: [PATCH 1/4] feat: when core tools are disabled still add subagent tool Core tools may be disabled to use kit as a 'chat-only' or as an agent with only external (MCP) tools. The subagent tool is different as it doesn't access the 'outside' world but spawns simultaniously running sub-agents only. Thus it may still be useful even when only chatting to parallelize tasks or when using external (MCP) tools. Signed-off-by: Egbert Eich --- internal/agent/agent.go | 4 ++-- internal/core/tools.go | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 2c80d718..58eb2260 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -291,8 +291,8 @@ func NewAgent(ctx context.Context, agentConfig *AgentConfig) (*Agent, error) { // DisableCoreTools allows explicitly having zero tools (for chat-only mode). var coreTools []fantasy.AgentTool if agentConfig.DisableCoreTools && len(agentConfig.CoreTools) == 0 { - // Explicitly zero tools - chat-only mode - coreTools = nil + // Explicitly subagent tool only - chat-only mode + coreTools = core.SubagentTool() } else if len(agentConfig.CoreTools) > 0 { // Custom tools provided - use them coreTools = agentConfig.CoreTools diff --git a/internal/core/tools.go b/internal/core/tools.go index b4712921..6ba8e199 100644 --- a/internal/core/tools.go +++ b/internal/core/tools.go @@ -100,6 +100,12 @@ func SubagentTools(opts ...ToolOption) []fantasy.AgentTool { } } +func SubagentTool(opts ...ToolOption) []fantasy.AgentTool { + return []fantasy.AgentTool{ + NewSubagentTool(opts...), + } +} + // AllTools returns all available core tools. func AllTools(opts ...ToolOption) []fantasy.AgentTool { return append(SubagentTools(opts...), NewSubagentTool(opts...)) From 864353307cf726d80a0a799221c87a63db5e3088 Mon Sep 17 00:00:00 2001 From: Egbert Eich Date: Sun, 14 Jun 2026 08:04:06 +0200 Subject: [PATCH 2/4] docs: update documentation on `DisableCoreTools` Mention that the 'subagent' tool is still added when core tools are disabled. - README.md pkg/kit/README.md skills/kit-sdk/SKILL.md www/pages/sdk/options.md: Update documentation - internal/agent/agent.go internal/agent/factory.go internal/ksetup/setup.go: Update data structure description Signed-off-by: Egbert Eich --- README.md | 5 +++-- internal/agent/agent.go | 8 +++++--- internal/agent/factory.go | 5 +++-- internal/kitsetup/setup.go | 5 +++-- pkg/kit/README.md | 4 ++-- skills/kit-sdk/SKILL.md | 2 +- www/pages/sdk/options.md | 4 ++-- 7 files changed, 19 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 84c7f186..6c449ebe 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ mcpServers: # Extensions and tools --extension, -e Load additional extension file(s) (repeatable) --no-extensions Disable all extensions ---no-core-tools Disable all built-in core tools (bash, read, write, edit, grep, find, ls, subagent) +--no-core-tools Disable built-in core tools except subagent: (bash, read, write, edit, grep, find, ls) --prompt-template Load a specific prompt template by name --no-prompt-templates Disable prompt template loading @@ -602,7 +602,8 @@ host, err := kit.New(ctx, &kit.Options{ // Tool options Tools: []kit.Tool{...}, // Replace default tool set entirely ExtraTools: []kit.Tool{...}, // Add tools alongside defaults - DisableCoreTools: true, // Disable all built-in core tools; also controllable via + DisableCoreTools: true, // Disable all built-in core tools except for the + // subagent tool; also controllable via // --no-core-tools flag, KIT_NO_CORE_TOOLS env var, // or no-core-tools: true in .kit.yml diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 58eb2260..dd74feff 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -42,9 +42,10 @@ type AgentConfig struct { // CodingTools or tools with a custom WorkDir). CoreTools []fantasy.AgentTool - // DisableCoreTools, when true, prevents loading any core tools. + // DisableCoreTools, when true, prevents loading any core tools except for + // the subagent tool. // If both DisableCoreTools is true and CoreTools is empty, the agent - // will have no tools (useful for simple chat completions). + // will only have the subagent tool (useful for simple chat completions). DisableCoreTools bool // ToolWrapper is an optional function that wraps the combined tool list @@ -288,7 +289,8 @@ func NewAgent(ctx context.Context, agentConfig *AgentConfig) (*Agent, error) { // Register core tools (direct AgentTool implementations, no MCP overhead). // Use caller-provided tools if set, otherwise default to all core tools. - // DisableCoreTools allows explicitly having zero tools (for chat-only mode). + // DisableCoreTools allows explicitly having zero tools except for the + // subagent tool (for chat-only mode). var coreTools []fantasy.AgentTool if agentConfig.DisableCoreTools && len(agentConfig.CoreTools) == 0 { // Explicitly subagent tool only - chat-only mode diff --git a/internal/agent/factory.go b/internal/agent/factory.go index cb3d692f..c4595518 100644 --- a/internal/agent/factory.go +++ b/internal/agent/factory.go @@ -45,9 +45,10 @@ type AgentCreationOptions struct { // CoreTools overrides the default core tool set. If empty, core.AllTools() // is used. CoreTools []fantasy.AgentTool - // DisableCoreTools, when true, prevents loading any core tools. + // DisableCoreTools, when true, prevents loading any core tools except for + // the subagent tool. // If both DisableCoreTools is true and CoreTools is empty, the agent - // will have no tools (useful for simple chat completions). + // will only have the subagent tool (useful for simple chat completions). DisableCoreTools bool // ToolWrapper wraps the combined tool list before agent creation. ToolWrapper func([]fantasy.AgentTool) []fantasy.AgentTool diff --git a/internal/kitsetup/setup.go b/internal/kitsetup/setup.go index a02010dd..b3f01921 100644 --- a/internal/kitsetup/setup.go +++ b/internal/kitsetup/setup.go @@ -33,9 +33,10 @@ type AgentSetupOptions struct { // CoreTools overrides the default core tool set. If empty, core.AllTools() // is used. Allows SDK users to pass custom tools (e.g. with WithWorkDir). CoreTools []fantasy.AgentTool - // DisableCoreTools, when true, prevents loading any core tools. + // DisableCoreTools, when true, prevents loading any core tools except for + // the subagent tool. // If both DisableCoreTools is true and CoreTools is empty, the agent - // will have no tools (useful for simple chat completions). + // will only have the subagent tool (useful for simple chat completions). DisableCoreTools bool // ExtraTools are additional tools added alongside core, MCP, and extension // tools. They do not replace the defaults — they extend them. diff --git a/pkg/kit/README.md b/pkg/kit/README.md index fcaafb4a..5fbed387 100644 --- a/pkg/kit/README.md +++ b/pkg/kit/README.md @@ -100,7 +100,7 @@ host, err := kit.New(ctx, &kit.Options{ // Tool options Tools: []kit.Tool{kit.NewBashTool()}, // Replace default tool set ExtraTools: []kit.Tool{myTool}, // Add alongside defaults - DisableCoreTools: true, // Use no core tools (0 tools) + DisableCoreTools: true, // Use no core tools except for the subagent tool // Configuration SkipConfig: true, // Skip .kit.yml files (viper defaults + env vars still apply) @@ -398,7 +398,7 @@ Key `Options` fields for SDK usage: | `SkipConfig` | Skip `.kit.yml` loading (defaults + env vars still apply) | | `Tools` | Replace core tools with custom set | | `ExtraTools` | Add tools alongside defaults | -| `DisableCoreTools` | Use no core tools (0 tools, for chat-only) | +| `DisableCoreTools` | Use no core tools (except subagent tool - for chat-only) | | `NoSession` | Ephemeral mode (no session persistence) | | `SessionPath` | Open specific session file | | `Continue` | Resume most recent session | diff --git a/skills/kit-sdk/SKILL.md b/skills/kit-sdk/SKILL.md index e578fbcd..0aaa7a8f 100644 --- a/skills/kit-sdk/SKILL.md +++ b/skills/kit-sdk/SKILL.md @@ -107,7 +107,7 @@ host, err := kit.New(ctx, &kit.Options{ // Tools Tools: []kit.Tool{kit.NewBashTool()}, // REPLACES entire default tool set ExtraTools: []kit.Tool{myTool}, // ADDS alongside core/MCP/extension tools - DisableCoreTools: true, // Use no core tools (0 tools, for chat-only) + DisableCoreTools: true, // Use no core tools except for the subagent tool (for chat-only) // Configuration SkipConfig: true, // Skip .kit.yml files (viper defaults + env vars still apply) diff --git a/www/pages/sdk/options.md b/www/pages/sdk/options.md index 424c1ce4..bc0be82e 100644 --- a/www/pages/sdk/options.md +++ b/www/pages/sdk/options.md @@ -55,7 +55,7 @@ host, err := kit.New(ctx, &kit.Options{ // Tools Tools: []kit.Tool{...}, // Replace default tool set entirely ExtraTools: []kit.Tool{...}, // Add tools alongside defaults - DisableCoreTools: true, // Use no core tools (0 tools, for chat-only) + DisableCoreTools: true, // Use no core tools except for the subagent tool (for chat-only) // Configuration SkipConfig: true, // Skip .kit.yml files (viper defaults + env vars still apply) @@ -158,7 +158,7 @@ when embedding Kit as a library. |-------|------|---------|-------------| | `Tools` | `[]Tool` | — | Replace the entire default tool set | | `ExtraTools` | `[]Tool` | — | Additional tools alongside core/MCP/extension tools | -| `DisableCoreTools` | `bool` | `false` | Use no core tools (0 tools, for chat-only) | +| `DisableCoreTools` | `bool` | `false` | Use no core tools except for the subagent tool (for chat-only) | | `NoExtensions` | `bool` | `false` | Disable Yaegi extension loading | | `NoContextFiles` | `bool` | `false` | Disable automatic AGENTS.md loading | From 3fac8786ca6d47e4ed7096ebfddce7b48d6d10f1 Mon Sep 17 00:00:00 2001 From: Egbert Eich Date: Sun, 14 Jun 2026 08:09:52 +0200 Subject: [PATCH 3/4] test: add test to verify 'subagent' tool is added when DisableCoreTools true pkg/kit/kit_test.go: - Add TestDisableCoreTools() to make sure 'subagent' is the only tool added when `DisableCoreTools` is true. Signed-off-by: Egbert Eich --- pkg/kit/kit_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pkg/kit/kit_test.go b/pkg/kit/kit_test.go index 81256549..d990f7aa 100644 --- a/pkg/kit/kit_test.go +++ b/pkg/kit/kit_test.go @@ -472,3 +472,32 @@ func TestNewSystemPromptInline(t *testing.T) { t.Errorf("composed system-prompt missing inline content; got %q", composed) } } + +// TestDisableCoreTools verifies that setting Options.DisableCoreTools to true +// limits the available tools to only the 'subagent' tool. +func TestDisableCoreTools(t *testing.T) { + if os.Getenv("ANTHROPIC_API_KEY") == "" { + t.Skip("Skipping test: ANTHROPIC_API_KEY not set") + } + defer resetViper() + + ctx := context.Background() + host, err := kit.New(ctx, &kit.Options{ + Model: "anthropic/claude-sonnet-4-5-20250929", + Quiet: true, + NoSession: true, + NoExtensions: true, + DisableCoreTools: true, + }) + if err != nil { + t.Fatalf("Failed to create Kit with DisableCoreTools: %v", err) + } + defer func() { _ = host.Close() }() + + tools := host.GetToolNames() + if len(tools) != 1 { + t.Errorf("Expected 1 tool when DisableCoreTools is true, got %d: %v", len(tools), tools) + } else if len(tools) > 0 && tools[0] != "subagent" { + t.Errorf("Expected only 'subagent' tool, got %q", tools[0]) + } +} From a08f9ab52e2c8dc444b6d2c43e6872d784be8060 Mon Sep 17 00:00:00 2001 From: Egbert Eich Date: Sun, 14 Jun 2026 08:31:02 +0200 Subject: [PATCH 4/4] fix: reset Viper state in each subtest to prevent cross-test leakage Add defer `resetViper()` in each subtest in`TestNewWithSkillsOptions()` to avoid order-dependent test failures. Suggested-by: coderabbitai --- pkg/kit/kit_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/kit/kit_test.go b/pkg/kit/kit_test.go index d990f7aa..32445bd1 100644 --- a/pkg/kit/kit_test.go +++ b/pkg/kit/kit_test.go @@ -375,6 +375,7 @@ func TestNewWithSkillsOptions(t *testing.T) { ctx := context.Background() t.Run("NoSkills disables skill loading", func(t *testing.T) { + defer resetViper() host, err := kit.New(ctx, &kit.Options{ Model: "anthropic/claude-sonnet-4-5-20250929", Quiet: true, @@ -392,6 +393,7 @@ func TestNewWithSkillsOptions(t *testing.T) { }) t.Run("SkillsDir propagates", func(t *testing.T) { + defer resetViper() // Use a non-existent dir — no skills will load but the option must be // accepted without error and result in zero skills. dir := t.TempDir() @@ -411,6 +413,7 @@ func TestNewWithSkillsOptions(t *testing.T) { }) t.Run("explicit Skills paths load correctly", func(t *testing.T) { + defer resetViper() // Write a minimal skill file to a temp dir. dir := t.TempDir() skillFile := dir + "/my-skill.md"