Skip to content

feat(subagents): single-file markdown format + contextual prompt + project-scoped discovery #647

@Aaronontheweb

Description

@Aaronontheweb

Context

Netclaw currently stores sub-agent definitions as pairs of ~/.netclaw/agents/<name>.json + <name>.md files, parsed by FileSubAgentDefinitionLoader. Claude Code, OpenCode, and the public SKILL.md format that Netclaw already adopted for skills (#150) all use a single markdown file per unit with YAML frontmatter. Sub-agents are the only artifact in Netclaw's install that still uses a two-file JSON+MD pattern.

Two consequences:

  1. Sub-agents authored for Claude Code or OpenCode can't drop into ~/.netclaw/agents/ as-is. Users with an existing local-ai-preferences/agents/*.md library would be directly portable if Netclaw adopted the same shape.
  2. Authoring a new Netclaw sub-agent requires keeping two files in sync.

Additionally, the current spawn_agent tool lets the frontline LLM pass only a free-form task string — there's no way to specialize a sub-agent for a single invocation (focus on this competitor, use this workspace, limit scope to that repo) without authoring a whole new agent file.

And there's no way to scope a sub-agent to a specific project/workspace. Every agent is global.

Zero-migration observation: ~/.netclaw/agents/ on a fresh install holds only the three stock defaults seeded by netclaw init. There is no user-authored content to preserve. The format change is free.

Proposal

1. Single-file markdown format with YAML frontmatter

Replace the JSON+MD sidecar with one .md file per agent:

---
name: research-assistant
description: >
  Use when the user needs deep web research, multiple sources, or citation.
  Spawn with a focused task. Returns a cited summary, not a transcript.
tools: [web_search, web_fetch, file_read, attach_file]
modelRole: Compaction
timeoutSeconds: 120
visibility: user-facing
emitStructuredFindings: false
---

You are a research assistant. Your job is to help the user by searching
the web, gathering information from multiple sources, and synthesizing
findings into clear, well-organized summaries.

## Guidelines
...

Frontmatter fields map 1:1 to the existing SubAgentProfile record at src/Netclaw.Configuration/SubAgentProfile.cs — no new runtime types.

2. Folder discovery and project overlay

Scanner walks ~/.netclaw/agents/*.md at daemon startup and also <workspace>/.netclaw/agents/*.md when a session has a workspace. Project-scoped agents layer on top of home-dir agents (by name). The home-dir scan is authoritative when no workspace is active.

This lets repos ship sub-agents the same way they ship .github/workflows/*.yml and pairs cleanly with #595 (session CWD + workspace context).

3. Enriched [available-subagents] context layer

Today ToolIndexUpdater.UpdateSubAgentDiscovery() at src/Netclaw.Daemon/Mcp/ToolIndexUpdater.cs:95-117 emits:

- research-assistant: Deep web research with search and citation (timeout: 120s)

Enriched form includes the full description, tool list, and an example call using the new Context parameter so the frontline LLM sees both what each agent does and how to specialize it per call.

4. Optional Context parameter on spawn_agent

Extend SpawnAgentTool.Params at src/Netclaw.Actors/SubAgents/SpawnAgentTool.cs:22-26 with one optional field:

public record Params(
    string Agent,
    string Task,
    string? Context = null);

The tool description on the Context field teaches the frontline LLM when to use it ("workspace details, the user's broader goal, facts the subagent would otherwise have to rediscover — do not duplicate the agent's built-in instructions").

Context is prefixed onto the subagent's first user message, not injected into the system prompt. The agent's identity (body of the .md file) stays verbatim from disk for reproducibility. If Context is null the message is just the Task — identical to today's behavior, so backward compatible.

Initial subagent history becomes:

[system]  <frontmatter body verbatim>
[user]    Context:
          <Context, if provided>

          Task:
          <Task>

No length cap. The runtime decides if a context is too large — forcing a cap in policy would be premature.

5. Regenerate the three init-wizard seeds

IdentityStepViewModel.cs:307+ currently writes three JSON+MD pairs. Rewrite to emit three .md files in the new format.

6. Fail loudly on invalid files

Malformed frontmatter, unknown tool names (anything not in the ToolRegistry and not in SubAgentToolPolicy.GetAllowedUserFacingTools()), duplicate name across home+project scans — all raise a loud warning at scan time and the agent is skipped. No silent degradation. Per CLAUDE.md "no silent fallbacks" rule.

Security posture preserved

SubAgentToolPolicy.IsAllowedForUserFacing(toolName) still filters the tools: array at spawn time. User-facing agents stay restricted to the 4-tool allowlist (web_search, web_fetch, file_read, attach_file). If users drop in a Claude Code agent declaring tools: [Task, Bash, Read], those names either get translated (future work) or fail loudly with an error telling the user which tools they need to rename.

Critical files

  • src/Netclaw.Configuration/FileSubAgentDefinitionLoader.cs — rewrite for MD+frontmatter parsing (replace JSON reader with a YAML frontmatter extractor)
  • src/Netclaw.Configuration/SubAgentProfile.cs — existing shape is already right; only the file loader changes
  • src/Netclaw.Configuration/SubAgentDefinitionRegistry.cs — add project-overlay scan path
  • src/Netclaw.Configuration/SubAgentDiscoveryContextLayer.cs — unchanged (registry feeds it)
  • src/Netclaw.Daemon/Mcp/ToolIndexUpdater.cs:95-117 — enrich UpdateSubAgentDiscovery() with description + tools + example
  • src/Netclaw.Actors/SubAgents/SpawnAgentTool.cs:22-26 — add Context param with descriptive tool metadata
  • src/Netclaw.Actors/SubAgents/SubAgentActor.cs:109-110 — compose initial user message with optional Context prefix
  • src/Netclaw.Cli/Tui/Wizard/Steps/IdentityStepViewModel.cs:307+ — seed defaults in new format
  • src/Netclaw.Configuration/SubAgentToolPolicy.cs — unchanged; still the allowlist gate

Tests

  • Parser: frontmatter extraction, type coercion, missing required fields → fail loud
  • Scanner: home-only, home + project overlay, duplicate name across scopes → fail loud
  • Tool allowlist: unknown names → loud warning, agent skipped
  • Context injection: null → original user-message shape preserved; populated → Context: prefix added
  • SubAgentSpawner integration: full spawn with Context propagates to SubAgentActor._history
  • Seed parity: the three regenerated defaults parse and spawn identically to their JSON predecessors

Out of scope for this issue

Tracked in follow-on issues or existing open items:

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestsubagentsspawn_agent, SubAgentActor, definition loader, discovery context layer, and related features

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions