一键导入
create-agent-connect-graph
// Create a custom KohakuTerrarium creature folder, then spawn and connect it into the current terrarium graph with group tools. Use when asked to make a new agent/creature/worker/specialist and wire it into a team.
// Create a custom KohakuTerrarium creature folder, then spawn and connect it into the current terrarium graph with group tools. Use when asked to make a new agent/creature/worker/specialist and wire it into a team.
Safe conventional-commit workflow for an agent asked to commit changes. Run tests before staging, stage deliberately, write a Conventional Commits-style subject + body, never amend or force-push without explicit user approval.
Merge, split, or reorder PDF files. Use when the user mentions combining PDFs, extracting pages, or any "glue these PDFs together" request. Activates automatically in directories containing .pdf files.
Maintain a simple todo.md / plan.md workspace for multi-step tasks. Use when the user says "make a plan", "track this as a task list", or whenever a non-trivial multi-step task would benefit from being checkpointed to disk between agent turns.
| name | create-agent-connect-graph |
| description | Create a custom KohakuTerrarium creature folder, then spawn and connect it into the current terrarium graph with group tools. Use when asked to make a new agent/creature/worker/specialist and wire it into a team. |
| license | KohakuTerrarium License 1.0 |
| paths | ["creatures/**/config.yaml","creatures/**/prompts/*.md","creatures/**/*.py","terrariums/**/*.yaml","kohaku.yaml"] |
Use this skill when the user wants a new custom agent (a KohakuTerrarium creature) and wants it connected to the current terrarium graph/team.
A creature is a folder with a config file plus prompt files. A terrarium graph
is changed at runtime only through privileged group_* tools. Do not confuse
this with sub-agents: sub-agents are private vertical delegation inside one
creature; spawned creatures are horizontal peers connected by channels/wires.
Ask only if the role or connection pattern is genuinely unknown. Otherwise pick safe defaults:
@kt-biome/creatures/general for general work,
@kt-biome/creatures/swe for coding work, or another package ref if the
user names one;Use a normal sub-agent instead of a graph creature when the work is just a one-shot nested computation that does not need its own tools, model, cwd, memory, or asynchronous channel loop.
Pick a repository-local folder such as creatures/<slug>/ or
.kt/creatures/<slug>/. Keep the folder self-contained:
creatures/<slug>/
config.yaml
prompts/
system.md
memory/ # optional
tools/ # optional custom Python tools
subagents/ # optional custom sub-agent modules/configs
plugins/ # optional lifecycle/prompt plugins
triggers/ # optional custom triggers
inputs/ # optional custom input modules
outputs/ # optional custom output modules
Minimal inherited creature:
# creatures/<slug>/config.yaml
name: <slug>
version: "1.0"
base_config: "@kt-biome/creatures/general"
system_prompt_file: prompts/system.md
For a coding specialist, inherit from SWE instead:
base_config: "@kt-biome/creatures/swe"
Add only fields the new role truly needs. Useful overrides:
controller:
llm: <profile-name> # optional model override
reasoning_effort: high # optional, provider-dependent
# Drop inherited defaults only when the role must be tightly scoped.
# no_inherit: [tools, subagents, plugins]
tools:
- read
- grep
- name: my_tool
type: custom
module: ./tools/my_tool.py
class: MyTool
memory:
folder: memory/
A custom agent often needs code, not just a prompt. Put module files under the
creature folder and reference them with module: ./relative/path.py. The path
is resolved relative to creatures/<slug>/, not the repository root. Use the
YAML key class for tools, plugins, inputs, outputs, and triggers; custom
sub-agent config modules use config: <EXPORTED_CONFIG_NAME>.
Use a tool when the LLM should explicitly call a capability by name.
# creatures/<slug>/tools/project_lookup.py
from kohakuterrarium.modules.tool.base import BaseTool, ToolContext, ToolResult
class ProjectLookupTool(BaseTool):
needs_context = True
@property
def tool_name(self) -> str:
return "project_lookup"
@property
def description(self) -> str:
return "Look up project-specific facts for this worker."
async def _execute(
self,
args: dict,
*,
context: ToolContext | None = None,
) -> ToolResult:
key = args.get("key", "")
# context.working_dir, context.session, context.environment, etc.
return ToolResult(output=f"No project fact registered for {key!r}.")
# creatures/<slug>/config.yaml
tools:
- name: project_lookup
type: custom
module: ./tools/project_lookup.py
class: ProjectLookupTool
Use a sub-agent when the new creature needs private vertical delegation. This
is not a graph peer and does not need group_* wiring.
# creatures/<slug>/subagents/domain_review.py
from kohakuterrarium.modules.subagent.config import SubAgentConfig
DOMAIN_REVIEW = SubAgentConfig(
name="domain_review",
description="Private domain review helper.",
system_prompt="Review the input for domain-specific issues. Return bullets.",
tools=["read", "grep"],
interactive=False,
can_modify=False,
)
subagents:
- name: domain_review
type: custom
module: ./subagents/domain_review.py
config: DOMAIN_REVIEW
Use a plugin when the behavior should run around LLM/tool/sub-agent calls instead of being invoked as a tool.
# creatures/<slug>/plugins/task_header.py
from kohakuterrarium.modules.plugin.base import BasePlugin
class TaskHeaderPlugin(BasePlugin):
name = "task_header"
def __init__(self, header: str = ""):
super().__init__()
self.header = header
def get_content(self, context) -> str | None:
if not self.header:
return None
return f"## Worker routing note\n\n{self.header}"
plugins:
- name: task_header
type: custom
module: ./plugins/task_header.py
class: TaskHeaderPlugin
options:
header: "Use the review-results channel for final replies."
Only add these when the creature has its own event source or output sink. Most runtime graph workers should communicate through terrarium channels instead.
triggers:
- name: domain_timer
type: custom
module: ./triggers/domain_timer.py
class: DomainTimerTrigger
options: { interval: 300 }
prompt: "Check for stale assigned work."
input:
type: custom
module: ./inputs/queue_input.py
class: QueueInput
options: { path: ./tasks.jsonl }
output:
type: custom
module: ./outputs/report_file.py
class: ReportFileOutput
options: { path: ./worker-output.md }
Custom module checklist:
kohakuterrarium.modules.* protocols; do not edit framework
source;options: instead of hardcoding secrets;is_concurrency_safe = False;sys.path hacks.Put role, responsibilities, routing contract, and safety guidelines in
prompts/system.md. Do not paste tool lists, tool-call syntax, or full tool
docs there; KohakuTerrarium generates those automatically.
Good prompt skeleton:
# <Role name>
You are <role>, a specialist in <domain>.
## Mission
- Do <primary responsibility>.
- Return concise status/results on the configured result channel.
- Ask for clarification only when blocked by missing requirements.
## Collaboration contract
- Treat messages on `<task-channel>` as work requests.
- Send progress or final results to `<result-channel>`.
- Do not assume private sub-agent state is visible to peers; communicate via
channels when another creature needs to know something.
## Output style
- Prefer short, actionable updates.
- Include file paths, commands, or evidence when relevant.
If the new worker should react to specific channels, put that in the prompt;
the actual channel permissions are still configured with group_channel.
Before spawning, verify the files exist and paths are relative to the creature folder:
cat creatures/<slug>/config.yaml
cat creatures/<slug>/prompts/system.md
Common mistakes:
system_prompt_file points at prompts/system.md, not an absolute path.base_config uses a package ref like @kt-biome/creatures/general or a
path that exists from the current working directory.module: paths are relative to the creature folder.From a privileged/root creature, inspect the current team first:
group_status(include_spawnable=true, include_history=false)
Use the snapshot to avoid duplicate creature names/channels and to confirm the
caller is privileged. If group_status is unavailable, you are not in a
privileged graph-editing context; tell the user to run from the root/privileged
node or use kt terrarium run / Studio to manage topology.
Create the node with the folder path (or package ref). The new creature is non-privileged and joins the caller's graph, but it still needs channel/wire routing before useful work reaches it.
group_add_node(
config_path="creatures/<slug>",
name="<display-name>",
pwd="<working-directory>" # optional; defaults to caller cwd
)
Record the returned creature_id or name. Prefer the returned id for later
wiring if names may collide.
Channels are broadcast: every listener receives every message. A creature's own messages are filtered out, so do not rely on self-loop iteration.
Typical task/result channels:
group_channel(action="create", channel="<slug>-tasks",
description="Tasks for <display-name>")
group_channel(action="create", channel="<slug>-results",
description="Results from <display-name>")
If the channels already exist, skip creation and only wire missing edges.
group_channel(action="wire", ...) changes the target creature's edge:
direction="listen" lets the target receive from the channel;
direction="send" lets it publish to the channel.
Typical root-dispatch pattern:
# Worker receives tasks.
group_channel(action="wire", channel="<slug>-tasks",
creature_id="<worker-id>", direction="listen")
# Worker publishes results.
group_channel(action="wire", channel="<slug>-results",
creature_id="<worker-id>", direction="send")
# Optional: if the privileged/root caller itself needs explicit edges,
# wire it too (often cross-graph wiring pairs the caller automatically,
# but same-graph updates may need explicit permissions depending on the
# existing topology/prompt contract).
group_channel(action="wire", channel="<slug>-tasks",
creature_id="<root-id>", direction="send")
group_channel(action="wire", channel="<slug>-results",
creature_id="<root-id>", direction="listen")
For direct one-to-one messages without channels, use group_send(to, message).
For a deterministic pipeline hand-off after each final turn, optionally add an
output wire:
group_wire(action="add", from="<worker-id>", to="<next-id>",
with_content=true)
Use channels for dispatch and conversation; use output wires for pipeline handoff.
Run another snapshot:
group_status(include_spawnable=false, include_history=false)
Confirm:
idle or not_started;listen on task and send on results;Then send the first task:
send_channel(channel="<slug>-tasks", message="<clear task payload>")
If you need to wake only the new worker and not every listener, use:
group_send(to="<worker-id>", message="<clear task payload>")
To pause without deleting session state:
group_stop_node(creature_id="<worker-id>")
group_start_node(creature_id="<worker-id>")
To remove cleanly, first delete/unwire edges touching the worker, then remove it:
group_channel(action="unwire", channel="<slug>-tasks",
creature_id="<worker-id>", direction="listen")
group_channel(action="unwire", channel="<slug>-results",
creature_id="<worker-id>", direction="send")
group_remove_node(creature_id="<worker-id>")
# Files created:
# creatures/code-reviewer/config.yaml
# creatures/code-reviewer/prompts/system.md
group_status(include_spawnable=true)
group_add_node(config_path="creatures/code-reviewer", name="code-reviewer")
group_channel(action="create", channel="review-tasks",
description="Code review requests")
group_channel(action="create", channel="review-results",
description="Code review findings")
group_channel(action="wire", channel="review-tasks",
creature_id="code-reviewer", direction="listen")
group_channel(action="wire", channel="review-results",
creature_id="code-reviewer", direction="send")
group_status(include_spawnable=false)
send_channel(channel="review-tasks",
message="Review src/auth.py for correctness and security. Return findings on review-results.")