Skip to content

Static subagents: persistent agents with own identity, config, and crons #34

Description

@ludusrusso

Problem Statement

WildGecu currently supports a single agent identity per installation. Users who want multiple specialized agents — a research agent, a coding assistant, a daily planner — must run entirely separate WildGecu instances with different --home directories. There is no way to define, manage, or address multiple named agents from a single installation, and no way for the main agent to delegate to a persistent agent with its own expertise and memory.

Solution

Introduce static subagents — persistent, named agents that live under the main agent's home directory. Each static agent has its own identity (SOUL.md), memory (MEMORY.md), configuration (config.yaml), and cron jobs. They are managed via CLI commands and can be addressed by name from both the user (via --agent flag) and the main agent (via delegate_to_agent and create_agent tools).

Static agents are independent peers of the main agent: they have their own personality, model preference, and scheduled tasks, but they share the same daemon process and global configuration (providers, API keys).

User Stories

  1. As a user, I want to create a named agent with its own identity (e.g., wildgecu agent add researcher), so that I can have specialized agents for different domains.
  2. As a user, I want to list all my static agents (wildgecu agent list), so that I can see what agents are available.
  3. As a user, I want to remove a static agent (wildgecu agent remove researcher), so that I can clean up agents I no longer need.
  4. As a user, I want to chat with a specific agent (wildgecu chat --agent=researcher), so that I get responses from the right identity and memory.
  5. As a user, I want to use code mode with a specific agent (wildgecu code --agent=researcher), so that a specialized coding agent works on my repo.
  6. As a user, I want each agent to have its own config.yaml specifying its preferred model, so that I can assign cheaper models to simple agents and expensive models to complex ones.
  7. As a user, I want each agent to have its own SOUL.md created through an interactive bootstrap interview, so that each agent has a distinct personality and expertise.
  8. As a user, I want each agent to maintain its own MEMORY.md, so that memory from one agent doesn't leak into another.
  9. As a user, I want to add cron jobs to specific agents (wildgecu cron add --agent=researcher), so that scheduled tasks run with the right agent identity.
  10. As a user, I want to list cron jobs for a specific agent (wildgecu cron ls --agent=researcher), so that I can manage each agent's schedule independently.
  11. As an agent (main), I want to delegate a task to a static agent by name (delegate_to_agent), so that I can leverage a specialist's identity and memory for the task.
  12. As an agent (main), I want to create a new static agent during conversation (create_agent), so that the user can ask me to set up a new specialist without leaving the chat.
  13. As a developer, I want static agents to reuse the existing Home abstraction, so that SOUL/MEMORY/crons/skills work identically for main and sub agents.
  14. As a developer, I want one daemon to serve all agents, so that there's no process management overhead per agent.
  15. As a developer, I want the daemon to load all agents' cron jobs at startup, so that all scheduled work runs from a single scheduler.
  16. As a static agent, I want to run my cron jobs with my own SOUL and model, so that my scheduled outputs reflect my identity.
  17. As a static agent, I want my own memory curation after each session, so that I build up context relevant to my domain.

Implementation Decisions

Directory structure

Each static agent lives at ~/.wildgecu/agents/<name>/ with a mirrored structure:

~/.wildgecu/
├── SOUL.md                    # main agent identity
├── MEMORY.md                  # main agent memory
├── wildgecu.yaml              # global config (providers, API keys)
├── crons/                     # main agent cron jobs
├── cron-results/              # main agent cron results
├── agents/
│   ├── researcher/
│   │   ├── config.yaml        # agent-specific config (model)
│   │   ├── SOUL.md            # agent identity
│   │   ├── MEMORY.md          # agent memory
│   │   ├── crons/             # agent cron jobs
│   │   └── cron-results/      # agent cron results
│   └── planner/
│       ├── config.yaml
│       ├── SOUL.md
│       ├── MEMORY.md
│       ├── crons/
│       └── cron-results/

Agent config.yaml

Minimal for v1:

model: gemini/gemini-2.0-flash

The model field is a provider/model reference (or alias) resolved against the global wildgecu.yaml providers map. If omitted, the agent inherits the global default_model.

Home abstraction extension

The existing Home type gets new methods:

  • AgentsDir() string — returns the path to the agents/ directory
  • Agent(name string) (*Home, error) — returns a Home instance rooted at agents/<name>/, creating the directory if needed
  • ListAgents() ([]string, error) — lists all agent names by scanning agents/

This means a static agent's Home gives access to .Soul(), .Memory(), .CronsDir(), .CronResultsDir() etc. — all scoped to that agent's directory. No new abstraction needed.

Agent config parsing

A new config type for parsing config.yaml:

  • Lives alongside agent setup code (e.g., pkg/agent/agentcfg/ or directly in pkg/agent/)
  • Parses the YAML file and resolves the model reference against the global config
  • Returns a model string that the provider Container can use

CLI commands

New command group: wildgecu agent

  • wildgecu agent add <name> — Creates the agent directory, runs a bootstrap interview (reusing the existing bootstrap mechanism from cmd/init.go), writes SOUL.md and config.yaml. Asks for model preference during setup.
  • wildgecu agent list — Scans agents/ and prints each agent's name, model, and whether it has a SOUL.
  • wildgecu agent remove <name> — Deletes the agent's directory after confirmation.

Modified commands with --agent flag:

  • wildgecu chat --agent=<name> — Connects to the daemon and creates a session scoped to the named agent's identity and model.
  • wildgecu code --agent=<name> — Same, but in code mode.
  • wildgecu cron add --agent=<name> — Adds a cron job under the named agent's crons/ directory.
  • wildgecu cron ls --agent=<name> — Lists cron jobs for the named agent.
  • wildgecu cron rm --agent=<name> <job> — Removes a cron job from the named agent.

When --agent is omitted, all commands operate on the main agent (backward compatible).

Daemon changes

The daemon currently initializes one SessionManager for the main agent. With static agents:

  1. At startup, the daemon scans ~/.wildgecu/agents/ to discover all static agents.
  2. Cron scheduling: For each agent, it loads their crons/ directory and creates cron jobs using the agent's own provider (resolved from their config.yaml model + global providers map). All jobs run in one gocron.Scheduler.
  3. Session creation: The daemon's IPC protocol (ChatRequest) gains an Agent field. When a client sends session.create with an agent name, the daemon creates a session using that agent's Home, SOUL, and model — calling agent.Prepare() with the agent-specific config.
  4. Reload: On daemon restart, the agent list and all cron jobs are reloaded.

delegate_to_agent tool

A new tool available to the main agent (and other static agents):

  • Input: agent_name (required, string), prompt (required, string)
  • Looks up the static agent by name from ~/.wildgecu/agents/<name>/
  • Loads the agent's SOUL, MEMORY, and config
  • Creates a provider from the agent's model config
  • Runs a full agent loop with the agent's system prompt and tools
  • Returns the final text response to the caller
  • The delegated agent's conversation is NOT saved to its memory (it's a one-shot delegation, not a full session)

create_agent tool

A new tool available to the main agent:

  • Input: name (required, string), soul (required, string), model (optional, string)
  • Creates the directory structure at agents/<name>/
  • Writes the provided soul content to SOUL.md
  • Writes config.yaml with the model (or default)
  • Returns confirmation to the caller
  • The main agent crafts the SOUL content based on conversation with the user (no bootstrap interview — the main agent IS the interviewer)

Module changes

  • Modified: pkg/home/home.go — Add AgentsDir(), Agent(name), ListAgents() methods
  • New: pkg/agent/agentcfg.go (or similar) — Agent config.yaml parsing
  • New: cmd/agent.gowildgecu agent command group
  • New: cmd/agent_add.go — Bootstrap interview for new agents
  • New: cmd/agent_list.go — List agents
  • New: cmd/agent_remove.go — Remove agents
  • Modified: cmd/chat.go — Add --agent flag, pass agent name to daemon
  • Modified: cmd/code.go — Add --agent flag
  • Modified: cmd/cron.go — Add --agent flag
  • Modified: cmd/cron_add.go — Add --agent flag
  • Modified: pkg/daemon/daemon.go — Scan agents at startup, multi-agent cron loading
  • Modified: pkg/daemon/sessions.go — Support sessions scoped to specific agents
  • Modified: pkg/daemon/chat_handler.go — Handle Agent field in ChatRequest
  • Modified: pkg/agent/tools/subagent.go — Add delegate_to_agent and create_agent tools

Testing Decisions

Good tests verify external behavior through public interfaces. Use mock providers and temporary directories, not real LLM APIs.

Modules to test

  1. pkg/home/home.go — Agent directory methods: Test AgentsDir(), Agent(name), ListAgents() with temp directories. Verify directory creation, listing, and that sub-Home accessors (Soul, Memory, CronsDir) return correct paths. Prior art: TestNew, TestIdentityFiles, TestDirectoryAccessors in pkg/home/home_test.go.

  2. Agent config parsing: Test YAML parsing, model resolution, and defaults. Prior art: config tests in x/config/load_test.go.

  3. delegate_to_agent tool: Test with a mock provider and a temp agent directory containing a SOUL and config. Verify that the agent's identity is loaded and the prompt is forwarded correctly. Prior art: tool tests in pkg/agent/tools/.

  4. create_agent tool: Test that it creates the correct directory structure and files. Verify config.yaml and SOUL.md contents. No mock provider needed — pure filesystem test.

  5. Daemon multi-agent cron loading: Test that the scheduler discovers and loads cron jobs from multiple agent directories. Prior art: pkg/cron/scheduler_test.go.

What makes a good test here

  • Test public interfaces, not internal helpers
  • Use t.TempDir() for filesystem operations
  • Use subtests (t.Run) per the project's CLAUDE.md convention
  • Mock the Provider interface for tool tests
  • Verify the contract: correct file paths, correct SOUL loading, correct model resolution

Out of Scope

  • Telegram per subagent — Telegram stays bound to the main agent only
  • Hot-reload of agents — Agents are discovered on daemon startup/restart only
  • Workspace-scoped agents — Agents in project .wildgecu/ directories
  • Tool restrictions in config.yaml — All agents get the same tool set for v1
  • Agent-to-agent direct communication — Agents communicate only through the parent's delegation tools
  • Documentation updates — Will be done as a follow-up after implementation

Further Notes

  • The --agent flag follows the same pattern as the existing --model flag: it's a global flag on the root command, available to all subcommands.
  • The delegate_to_agent tool intentionally does NOT persist memory for the delegated session. This keeps delegation fast and side-effect-free. If users want persistent conversations with a subagent, they use wildgecu chat --agent=<name>.
  • The create_agent tool lets the main agent act as an interviewer — the user says "create me a research agent" and the main agent crafts an appropriate SOUL without running the standard bootstrap flow.
  • Agent names should be validated (alphanumeric + hyphens, no spaces, no path traversal).

Metadata

Metadata

Assignees

No one assigned

    Labels

    prdProduct Requirements Document

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions