Skip to content

feat(agents): named agent definitions with per-agent tool restrictions#90

Merged
ezynda3 merged 3 commits into
masterfrom
feat/84-named-agents
Jul 8, 2026
Merged

feat(agents): named agent definitions with per-agent tool restrictions#90
ezynda3 merged 3 commits into
masterfrom
feat/84-named-agents

Conversation

@ezynda3

@ezynda3 ezynda3 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Description

Adds named agents: reusable subagent presets discovered from markdown files, advertised to the LLM through the subagent tool, with per-agent tool allowlists.

Previously every subagent was an ad-hoc prompt — callers re-specified system prompts, models, and tool sets on every spawn, and the LLM had no way to know specialist agents existed. Now a definition file like .agents/agents/code-reviewer.md (frontmatter: description, model, tools, temperature, timeout, hidden, disabled; body = system prompt) becomes a preset the LLM can invoke by name:

subagent(task: "Review the auth changes", agent: "code-reviewer")

and the SDK can use directly:

result, err := k.Subagent(ctx, kit.SubagentConfig{
    Prompt: "Map out the session persistence flow",
    Agent:  "explore", // preset prompt + read-only tool allowlist
})

Three connected pieces, matching the issue's proposal:

  1. Discovery (internal/agents/): definitions load from <project>/.agents/agents/, <project>/.kit/agents/, and ~/.config/kit/agents/ (XDG-aware), with project-over-user-over-builtin precedence. Two built-ins ship with Kit: general (full subagent tool set) and explore (read-only read, grep, find, ls). disabled: true removes an agent and anything it shadows; hidden: true keeps it resolvable but unadvertised.
  2. Advertisement (internal/core/subagent.go): the subagent tool description is generated at registration time, listing each non-hidden agent with its description and tool access. A new optional agent parameter resolves presets; explicit model / system_prompt / timeout_seconds arguments still override.
  3. Tool allowlists: a tools: frontmatter list restricts the subagent to exactly those tools. Restricted agents also suppress child MCP server loading so MCP tools cannot bypass the allowlist. The existing recursion guard (no subagent tool in children) is preserved.

Fixes #84

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes (go test -race ./..., golangci-lint run clean)

Additional Information

New files

  • internal/agents/agents.go (+ tests) — definition type, frontmatter parsing, discovery, precedence merge, built-ins
  • pkg/kit/agents.go (+ tests) — SDK surface: AgentDefinition, LoadAgentDefinitions(), Kit.GetAgents() / GetAgent(), preset resolution, allowlist filtering
  • internal/core/subagent_named_test.go — dynamic description + agent arg pass-through tests

Modified

  • internal/core/subagent.goagent param, dynamic description, SubagentSpawnFunc refactored to a struct-based SubagentSpawnRequest
  • internal/core/tools.go, internal/agent/{agent,factory}.go, internal/kitsetup/setup.goNamedAgents threading via WithNamedAgents ToolOption
  • pkg/kit/kit.goSubagentConfig.Agent / .Temperature, Options.NoAgents, discovery wiring in New(), resolution in Subagent()
  • Docs: root README.md, pkg/kit/README.md, www/pages/ (subagents, sdk/overview, sdk/options, configuration, index), skills/kit-sdk/SKILL.md — also fixes a pre-existing doc bug (Task:Prompt in the www SDK example)

Backward compatibility

  • Fully additive: existing subagent tool calls and Kit.Subagent() call sites work unchanged; without any definition files only the two built-ins are advertised
  • Discovery can be disabled via no-agents config key, KIT_NO_AGENTS=true, or Options.NoAgents
  • Internal-only signature change: core.SubagentSpawnFunc now takes SubagentSpawnRequest (not part of the public SDK)

Verified interactively (tmux + live Anthropic model): agent advertisement in tool description, preset system prompt application, allowlist enforcement (write/bash correctly unavailable to a read-only agent), unknown-agent error with available-agent listing, and built-in explore end-to-end.

Summary by CodeRabbit

  • New Features
    • Added “Named Agents” presets discoverable from project, user, and built-in locations, including per-agent tool allowlists for delegation.
    • Subagents can now use an agent preset to inherit prompt/model/tools/timeout/temperature defaults while explicit fields override and tool access only narrows.
    • Added SDK/Kit APIs to load and retrieve named agent definitions, plus NoAgents and --no-agents to disable discovery.
  • Behavior Change
    • Subagent timeouts are now treated as “unset” unless provided.
  • Documentation
    • Updated website, SDK, and CLI/config docs with discovery precedence, override rules, and examples.
  • Tests
    • Added comprehensive coverage for discovery, merging, and subagent named-agent behavior.

#84)

Add reusable subagent presets discovered from markdown files and
advertised to the LLM through the subagent tool.

- new internal/agents package: discovery from .agents/agents/,
  .kit/agents/, and ~/.config/kit/agents/ with project-over-user
  precedence; YAML frontmatter (description, model, tools, temperature,
  timeout, hidden, disabled); body becomes the system prompt
- built-in agents: general (full tool set) and explore (read-only)
- subagent tool: new optional agent parameter; tool description now
  lists discovered agents and their tool access at registration time
- per-agent tool allowlists map tools: frontmatter to the subagent's
  tool set; restricted agents also skip child MCP loading so servers
  cannot bypass the allowlist; recursion guard preserved
- SDK: SubagentConfig.Agent/.Temperature, Kit.GetAgents/GetAgent,
  LoadAgentDefinitions, Options.NoAgents (KIT_NO_AGENTS / no-agents)
- docs: README, www (subagents, sdk, configuration, index), pkg/kit
  README, kit-sdk skill

Fixes #84
@mark-iii-labs-huly

Copy link
Copy Markdown

Connected to Huly®: KIT-91

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds named agent presets discovered from markdown files, advertises them through subagent tooling, resolves them into subagent defaults and tool allowlists, wires the data through Kit creation and spawning, and documents discovery and disable controls.

Changes

Named Agents Feature

Layer / File(s) Summary
Agent discovery and frontmatter parsing
internal/agents/agents.go, internal/agents/agents_test.go
Adds markdown/YAML named-agent loading, project/user/builtin discovery, precedence and disable merging, and tests for parsing and merge behavior.
Subagent tool contract and description
internal/core/subagent.go, internal/core/subagent_test.go, internal/core/subagent_named_test.go, internal/core/tools.go
Adds request-based subagent spawning, named-agent metadata, dynamic tool descriptions, timeout handling, and the tool option for advertising named agents.
Agent factory and setup wiring
internal/agent/agent.go, internal/agent/factory.go, internal/kitsetup/setup.go
Threads named-agent specs through agent creation and setup into core tool construction.
Kit SDK agent resolution
pkg/kit/agents.go, pkg/kit/agents_test.go
Adds public agent-definition accessors, preset application, allowlist filtering, hidden-agent handling, and tests for resolution and copy semantics.
Kit runtime wiring
pkg/kit/kit.go
Stores discovered agents on Kit, adds disable controls, resolves named presets during subagent spawn, applies temperature overrides, and routes spawn requests through the new request type.
Documentation updates
README.md, skills/kit-sdk/SKILL.md, pkg/kit/README.md, www/pages/advanced/subagents.md, www/pages/configuration.md, www/pages/index.md, www/pages/sdk/options.md, www/pages/sdk/overview.md, www/pages/cli/flags.md
Documents named agents, discovery locations, disable flags/options, and SDK usage examples.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LLM
  participant SubagentTool
  participant Kit
  participant NamedAgents
  LLM->>SubagentTool: subagent(task, agent="explore")
  SubagentTool->>Kit: SubagentSpawnRequest
  Kit->>NamedAgents: resolveAgentDefinition(cfg)
  NamedAgents-->>Kit: preset values and allowlist
  Kit->>Kit: Subagent(cfg)
  Kit-->>SubagentTool: SubagentSpawnResult
  SubagentTool-->>LLM: result
Loading

Possibly related PRs

  • mark3labs/kit#57: Also changes how subagents are constructed and exposed, which overlaps with this PR’s subagent tool wiring.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements the requested named-agent discovery, tool advertising, presets, allowlists, built-ins, and SDK resolution.
Out of Scope Changes check ✅ Passed The code and docs changes stay focused on named agents, subagent integration, and related tests without clear unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: named agent definitions with per-agent tool restrictions.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/84-named-agents

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
internal/agents/agents.go (1)

162-166: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Aggregated errors lose wrapping (%w).

Both LoadFromDir and LoadAgents collect per-file/per-scope errors as strings and rejoin them with fmt.Errorf("...: %s", strings.Join(errs, "; ")), which discards the error chain. As per coding guidelines, **/*.go: "Always check errors and wrap them with fmt.Errorf("context: %w", err)". Consider collecting error values and using errors.Join so callers can still errors.Is/errors.As into individual causes.

♻️ Suggested refactor
-	var loaded []*Agent
-	var errs []string
+	var loaded []*Agent
+	var errs []error
 	for _, entry := range entries {
 		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".md") {
 			continue
 		}
 		a, err := Load(filepath.Join(dir, entry.Name()))
 		if err != nil {
-			errs = append(errs, err.Error())
+			errs = append(errs, err)
 			continue
 		}
 		loaded = append(loaded, a)
 	}
 
 	if len(errs) > 0 {
-		return loaded, fmt.Errorf("some agents failed to load: %s", strings.Join(errs, "; "))
+		return loaded, fmt.Errorf("some agents failed to load: %w", errors.Join(errs...))
 	}

Also applies to: 234-238

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/agents/agents.go` around lines 162 - 166, The aggregated-load path
in LoadFromDir and LoadAgents is flattening underlying errors into strings, so
callers lose error chaining and cannot use errors.Is/errors.As. Update the error
collection in these loading functions to keep error values instead of strings,
then return a wrapped aggregated error using errors.Join or fmt.Errorf with %w
where appropriate, preserving the individual causes while still adding context.

Source: Coding guidelines

pkg/kit/kit.go (1)

1571-1577: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider structured logging instead of fmt.Fprintf(os.Stderr, ...) for the agent-load warning.

As per coding guidelines, **/*.go: "Use github.com/charmbracelet/log for structured logging." This new warning path writes directly to os.Stderr via fmt.Fprintf rather than going through the project's logger (e.g. log.Warn("failed to load some agent definitions", "err", agErr)), which would also integrate with any configured log level/format. If similar warnings elsewhere in this file already use fmt.Fprintf(os.Stderr, ...) for consistency, this may be intentional — worth confirming which convention is authoritative here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/kit/kit.go` around lines 1571 - 1577, The agent-load warning in the
`LoadAgentDefinitions` path currently writes directly to stderr with
`fmt.Fprintf`, bypassing the project logger. Replace that warning in `kit.go`
with structured logging via `github.com/charmbracelet/log` (for example, use the
existing logger path around `LoadAgentDefinitions` and log the `agErr` as a
field) so it respects configured log level and format; keep the same
`NoAgents`/`Quiet` gating and update any related warning site in this block
consistently.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/kit/agents.go`:
- Around line 51-62: `Kit.GetAgents` currently returns a shallow copy of
`m.namedAgents`, so the “snapshot” guarantee in the doc only applies to the
slice container, not the underlying `AgentDefinition` values. Update
`GetAgents()` to return a deep copy of each `*AgentDefinition` (including nested
mutable fields like `Agent`/`Tools` as needed) so callers cannot mutate shared
Kit state through returned pointers, or else adjust the comment to explicitly
document pointer sharing. Keep the behavior for `Options.NoAgents` and preserve
the existing nil/empty semantics.
- Around line 100-116: `resolveAgentDefinition()` currently treats
`SubagentConfig.Tools` as an allowlist by intersecting it with the named agent’s
`Tools`, which conflicts with the documented override behavior. Update the logic
in `Kit.resolveAgentDefinition` and/or `applyAgentDefinition` so explicit
`cfg.Tools` truly replaces the agent’s tool set, or revise the
`SubagentConfig`/`Tools` documentation to clearly state it is restricted by the
agent allowlist. Add a test covering the explicit-`Tools` case to lock in the
intended behavior.

In `@pkg/kit/kit.go`:
- Around line 1571-1577: The agent-discovery guard in the `LoadAgentDefinitions`
path already checks `v.GetBool("no-agents")`, but the matching CLI flag is not
wired up. Add a `--no-agents` flag binding in the root command setup (the
`cmd/root.go` registration that populates viper) and ensure it maps to the same
`no-agents` key used in `kit.go`, so the existing `opts.NoAgents` and
`v.GetBool("no-agents")` check can actually disable agent loading from the CLI.

In `@www/pages/sdk/options.md`:
- Around line 74-78: Update the `NoAgents` example comment in the options docs
so it reflects the full discovery scope handled by the `NoAgents` setting.
Expand the inline note beside `NoAgents` to mention that it disables named agent
discovery from `.agents/agents/`, `.kit/agents`, `~/.config/kit/agents`, and
built-ins, keeping the example consistent with the runtime contract.

---

Nitpick comments:
In `@internal/agents/agents.go`:
- Around line 162-166: The aggregated-load path in LoadFromDir and LoadAgents is
flattening underlying errors into strings, so callers lose error chaining and
cannot use errors.Is/errors.As. Update the error collection in these loading
functions to keep error values instead of strings, then return a wrapped
aggregated error using errors.Join or fmt.Errorf with %w where appropriate,
preserving the individual causes while still adding context.

In `@pkg/kit/kit.go`:
- Around line 1571-1577: The agent-load warning in the `LoadAgentDefinitions`
path currently writes directly to stderr with `fmt.Fprintf`, bypassing the
project logger. Replace that warning in `kit.go` with structured logging via
`github.com/charmbracelet/log` (for example, use the existing logger path around
`LoadAgentDefinitions` and log the `agErr` as a field) so it respects configured
log level and format; keep the same `NoAgents`/`Quiet` gating and update any
related warning site in this block consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8c1a23f6-2c66-4827-9965-1fe937fce4da

📥 Commits

Reviewing files that changed from the base of the PR and between a2b5cc8 and b0afae0.

📒 Files selected for processing (20)
  • README.md
  • internal/agent/agent.go
  • internal/agent/factory.go
  • internal/agents/agents.go
  • internal/agents/agents_test.go
  • internal/core/subagent.go
  • internal/core/subagent_named_test.go
  • internal/core/subagent_test.go
  • internal/core/tools.go
  • internal/kitsetup/setup.go
  • pkg/kit/README.md
  • pkg/kit/agents.go
  • pkg/kit/agents_test.go
  • pkg/kit/kit.go
  • skills/kit-sdk/SKILL.md
  • www/pages/advanced/subagents.md
  • www/pages/configuration.md
  • www/pages/index.md
  • www/pages/sdk/options.md
  • www/pages/sdk/overview.md

Comment thread pkg/kit/agents.go
Comment thread pkg/kit/agents.go
Comment thread pkg/kit/kit.go
Comment thread www/pages/sdk/options.md
ezynda3 added 2 commits July 8, 2026 13:03
…tools contract (#84)

Address review findings on the named agents PR:

- GetAgents/GetAgent now return deep copies (Tools slice, Temperature
  pointer) so callers cannot mutate shared Kit state through the
  returned definitions
- add the missing --no-agents CLI flag bound to the no-agents viper
  key, following the --no-skills pattern; document it in README,
  cli/flags, subagents page, and the kit-sdk skill
- document that SubagentConfig.Tools is intersected with a named
  agent's allowlist (narrows, never widens) rather than overriding it;
  this is deliberate since the agent-loop spawner always passes
  inherited tools and an override would bypass the allowlist
- expand the NoAgents example comment in sdk/options.md to cover the
  full discovery scope
- tests: TestKitGetAgents_DeepCopy,
  TestResolveAgentDefinition_ExplicitToolsIntersectAllowlist
The Agent field doc already stated the intersection semantics, but the
Tools field doc itself still read as a plain override. Add a
cross-reference so both godoc entries describe the same contract.
@ezynda3 ezynda3 merged commit a2272f2 into master Jul 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: named agent definitions with per-agent tool restrictions

1 participant