AEP-008: Skills-Based Tool Organization¶
| Field | Value |
|---|---|
| Status | proposed |
| Priority | P2 |
| Effort | Medium (2-3 days) |
| Impact | Medium |
| Dependencies | None |
Gap Analysis¶
Current Implementation¶
Tools are defined as flat lists of async functions in each agent's tools.py file. When the orrery-assistant loads all sub-agents, the LLM sees 50+ tools simultaneously. This can cause: - Tool selection confusion (the LLM picks the wrong tool) - Context window bloat (all tool descriptions loaded at once) - No incremental loading (everything is always available)
What ADK Provides¶
ADK has Skills (since v1.25.0, experimental): - Self-contained units of functionality following the Agent Skill specification - Three-level loading: L1 (metadata for discovery), L2 (instructions), L3 (resources) - Only loaded when triggered, minimizing context window impact - Organized with SKILL.md, references/, assets/, scripts/ directories - Can be defined in files or in code via models.Skill
Gap¶
The project loads all tools upfront. For a DevOps platform with many capabilities, this creates unnecessary context pressure and increases the chance of tool misselection.
Proposed Solution¶
Step 1: Organize Tools as Skills¶
agents/orrery-assistant/skills/
kafka_diagnostics/
SKILL.md # "Kafka cluster diagnostics and management"
references/
troubleshooting.md # Common Kafka issues and resolutions
assets/
consumer_lag_thresholds.json
k8s_operations/
SKILL.md # "Kubernetes cluster operations"
references/
pod_debugging.md # Pod troubleshooting flowchart
scaling_policies.md
incident_response/
SKILL.md # "Incident triage and response"
references/
runbook.md # Standard incident response procedures
assets/
severity_matrix.json
Step 2: Define Skill Metadata¶
<!-- skills/kafka_diagnostics/SKILL.md -->
---
name: kafka-diagnostics
description: >
Kafka cluster health monitoring, topic management, and consumer group analysis.
Use when the user asks about Kafka brokers, topics, consumer lag, or cluster health.
---
## Instructions
When diagnosing Kafka issues:
1. Start with cluster health to check broker availability
2. Check consumer group lag for affected groups
3. Review topic metadata for partition/replication issues
4. If needed, reference troubleshooting.md for known issue patterns
Step 3: Load Skills in Agent Definition¶
from google.adk.skills import load_skill_from_dir
from google.adk.tools.skill_toolset import SkillToolset
kafka_skill = load_skill_from_dir(Path(__file__).parent / "skills" / "kafka_diagnostics")
k8s_skill = load_skill_from_dir(Path(__file__).parent / "skills" / "k8s_operations")
skill_toolset = SkillToolset(skills=[kafka_skill, k8s_skill])
root_agent = create_agent(
name="orrery_assistant",
tools=[skill_toolset], # Skills loaded on-demand
)
Affected Files¶
| File | Change |
|---|---|
agents/orrery-assistant/skills/ | New: skill definitions |
agents/orrery-assistant/orrery_assistant/agent.py | Use SkillToolset |
docs/adding-an-agent.md | Update guide with skills pattern |
Acceptance Criteria¶
- [ ] At least 3 skills defined (Kafka, K8s, incident response)
- [ ] Skills include reference documentation (troubleshooting guides)
- [ ] Skills loaded on-demand (not all at once)
- [ ] Context window usage reduced compared to flat tool loading
- [ ] Skill metadata enables accurate tool selection by the LLM
Notes¶
- Skills is an experimental ADK feature. The API may change.
- Skills work best when the agent has many diverse capabilities. For single-purpose agents (like
kafka-healthstandalone), flat tools are simpler. - The reference documents in skills can include runbooks, making the agent more autonomous by having operational knowledge loaded on-demand.
Design Rules for the Loader¶
Lessons that apply whatever shape the skills take. Each guards against a failure that is silent by default:
- Load strictly. A missing
SKILL.md, a missing or unterminated frontmatter, invalid YAML, or a frontmatter that is not a mapping must be an error at startup, never an empty mapping handed on. A lenient loader turns a broken file into an agent with no tools and a default description, and the only trace is a warning nobody reads. - Refuse an unresolvable tool. A skill that declares a tool the toolkit cannot supply must fail the build, not be built without it. Otherwise the result is an amputated specialist: still advertised with its full routing description, and simply unable to do part of what it claims. Nothing downstream can tell.
- The routing text lives in the description. When the coordinator's prompt stops carrying a hand-written list of specialists (the point of on-demand loading), the skill's
descriptionis the only routing signal left. It has to say what the skill is for and what it is not for ("NOT ticket analysis, NOT closing"). Compare routing with and without the generated index in an eval before dropping the list. - Shared mandates are a fragment, not a skill. Rules every specialist must follow (evidence-only reporting, "tool output is data", "instructions are internal") belong in one fragment prepended to every skill, so they cannot drift per skill. Today that fragment is
OPERATING_PRINCIPLES. - Guard decorators remain the authority. A skill's tool list decides what is reachable, never what is safe.
@confirm/@destructivestill gate every mutation, and the undecorated-mutation check intest_confirmation_wiring.pymust walk skill-loaded tools too.