Skill v1.0.1
currentAutomated scan100/100+4 new
name: ak-build description: > Add tools, agents, and handoffs to an existing Agent Kernel project. This skill guides you through reading the current project, understanding its framework and structure, then making context-aware additions — new tools, new agents, agent-to-agent handoffs, and Module wiring. The workhorse skill for iterative agent development. license: Apache-2.0 metadata: version: "1.0.1" author: yaalalabs category: user
Build: Add Tools & Agents
Use this skill to add new tools, agents, or handoffs to an existing Agent Kernel project.
Instructions for the Agent
Step 1: Read the Existing Project
Before generating any code, inspect the project to determine:
- Framework — Open the main agent file (e.g.,
app.py,demo.py,server.py,lambda.py) and look for:
from agentkernel.openai import OpenAIModule→ OpenAI Agents SDKfrom agentkernel.langgraph import LangGraphModule→ LangGraphfrom agentkernel.crewai import CrewAIModule→ CrewAIfrom agentkernel.adk import GoogleADKModule→ Google ADKfrom agentkernel.smolagents import SmolagentsModule→ Smolagentsfrom agentkernel.pydanticai import PydanticAIModule→ Pydantic AIfrom agentkernel.maf import MAFModule→ Microsoft Agent Framework
- Existing agents — List every agent already defined (names, roles, instructions).
- Existing tools — List every tool function and which agent uses it.
- Entry point — Is it CLI (
demo.py), API (RESTAPI.run()), Lambda (Lambda.handler), or Azure Function?
- Config — Read
config.yamlfor session type, guardrails, tracing, integrations already enabled.
- Dependencies — Read
pyproject.tomlfor the extras already installed (e.g.,[openai,api,redis]).
Report back what you found before proceeding. Example:
Project summary:- Framework: OpenAI Agents SDK- Entry point:app.py(API mode viaRESTAPI.run())- Agents:triage(routes to sub-agents),math(handles math),general(handles everything else)- Tools:calculator(bound tomath),web_search(bound togeneral)- Session: Redis- Extras:[openai,api,redis]
Step 2: Ask What to Add
Ask the user what they want to add:
- A new tool — A Python function that an agent can call
- A new agent — A new specialist agent
- A handoff — Wire an existing agent to delegate to another
- All of the above — Add a new agent with its own tools and wire it into the existing handoff graph
Step 3: Add a Tool
3a. Write the Tool Function
Create the tool function in the project's tool file (usually tool.py, or wherever existing tools live).
Rules:
- Tool functions are plain Python functions (sync or async) with type annotations and a docstring.
- Use
ToolContext.get()inside the function body to access session and runtime. Never pass context as a parameter. - Use
__(double underscore) as the nested delimiter in environment variable names (e.g.,AK_REDIS__URL).
from agentkernel.core import ToolContextdef lookup_order(order_id: str) -> str:"""Look up an order by its ID and return the order details."""context = ToolContext.get()session = context.session# Use session cache for expensive lookupscache = session.get_non_volatile_cache()cached = cache.get(f"order:{order_id}")if cached:return cached# Your lookup logic hereresult = f"Order {order_id}: shipped, arriving tomorrow"cache[f"order:{order_id}"] = resultreturn result
3b. Bind the Tool to an Agent
The binding syntax depends on the framework. Match what the project already uses:
OpenAI Agents SDK:
from agentkernel.openai import OpenAIToolBuildertools = OpenAIToolBuilder.bind([lookup_order, existing_tool_1])agent = Agent(name="support", instructions="...", tools=tools)
LangGraph:
from agentkernel.langgraph import LangGraphToolBuildertools = LangGraphToolBuilder.bind([lookup_order, existing_tool_1])agent = create_react_agent(name="support", tools=tools, model=model, prompt="...")
CrewAI:
from agentkernel.crewai import CrewAIToolBuildertools = CrewAIToolBuilder.bind([lookup_order, existing_tool_1])agent = Agent(role="support", goal="...", backstory="...", tools=tools, verbose=False)
Google ADK:
from agentkernel.adk import GoogleADKToolBuildertools = GoogleADKToolBuilder.bind([lookup_order, existing_tool_1])agent = Agent(name="support", model=LiteLlm(model="openai/gpt-4o-mini"),description="...", instruction="...", tools=tools)
Smolagents:
from agentkernel.smolagents import SmolagentsToolBuildertools = SmolagentsToolBuilder.bind([lookup_order, existing_tool_1])agent = ToolCallingAgent(tools=tools,model=model,name="support",description="...",)
Pydantic AI:
from agentkernel.pydanticai import PydanticAIToolBuildertools = PydanticAIToolBuilder.bind([lookup_order, existing_tool_1])agent = Agent(model="openai:gpt-4o-mini", name="support", description="...", instructions="...", tools=tools)
Microsoft Agent Framework:
from agentkernel.maf import MAFToolBuildertools = MAFToolBuilder.bind([lookup_order, existing_tool_1])agent = Agent(client, name="support", description="...", instructions="...", tools=tools)
Gotcha: Always add the new tool to the existingbind()call for that agent. Don't create a secondbind().
Step 4: Add an Agent
4a. Define the Agent
Match the framework already in use:
OpenAI Agents SDK:
from agents import Agentfrom agentkernel.openai import OpenAIToolBuildersupport_agent = Agent(name="support",handoff_description="Specialist for customer support and order inquiries",instructions="You help customers with order lookups, returns, and general support questions.",tools=OpenAIToolBuilder.bind([lookup_order]),)
LangGraph:
from langchain.chat_models import init_chat_modelfrom langgraph.prebuilt import create_react_agentfrom agentkernel.langgraph import LangGraphToolBuildermodel = init_chat_model("openai:gpt-4o-mini")support_agent = create_react_agent(name="support", # Always pass name= explicitly!tools=LangGraphToolBuilder.bind([lookup_order]),model=model,prompt="You help customers with order lookups, returns, and general support questions.",)
Gotcha (LangGraph): Always passname=tocreate_react_agent(). Without it the agent is unnamed and the supervisor cannot route to it.
CrewAI:
from crewai import Agentfrom agentkernel.crewai import CrewAIToolBuildersupport_agent = Agent(role="support", # CrewAI uses role= as the identifier, NOT name=goal="Specialist for customer support and order inquiries",backstory="You help customers with order lookups, returns, and general support questions.",tools=CrewAIToolBuilder.bind([lookup_order]),verbose=False,)
Gotcha (CrewAI): Userole=as the agent identifier — Agent Kernel mapsagent.roleto the agent name. Settingverbose=Falsekeeps output clean.
Google ADK:
from google.adk.agents import Agentfrom google.adk.models.lite_llm import LiteLlmfrom agentkernel.adk import GoogleADKToolBuildersupport_agent = Agent(name="support",model=LiteLlm(model="openai/gpt-4o-mini"), # Always wrap in LiteLlm()description="Specialist for customer support and order inquiries",instruction="You help customers with order lookups, returns, and general support questions.",tools=GoogleADKToolBuilder.bind([lookup_order]),)
Gotcha (Google ADK): UseLiteLlm(model="openai/gpt-4o-mini")— never pass a bare model string like"gpt-4o-mini".
Smolagents:
from smolagents import LiteLLMModel, ToolCallingAgentfrom agentkernel.smolagents import SmolagentsToolBuildermodel = LiteLLMModel(model_id="openai/gpt-4o")support_agent = ToolCallingAgent(tools=SmolagentsToolBuilder.bind([lookup_order]),model=model,name="support",description="You help customers with order lookups, returns, and general support questions.",)
Pydantic AI:
from pydantic_ai import Agentfrom agentkernel.pydanticai import PydanticAIToolBuildersupport_agent = Agent(model="openai:gpt-4o-mini", # provider-agnostic — swap for "anthropic:...", "google-gla:...", etc.name="support", # Always pass name= explicitly!description="Specialist for customer support and order inquiries",instructions="You help customers with order lookups, returns, and general support questions.",tools=PydanticAIToolBuilder.bind([lookup_order]),)
Gotcha (Pydantic AI): Always passname=(AK registers by name eagerly; Pydantic AI otherwise infers it lazily at first run) anddescription=(Pydantic AI's description is optional but is what AK reports as the agent description / A2A summary). The provider key for the model string (e.g.OPENAI_API_KEY) must be set at import time — Pydantic AI resolves the provider at construction.
Microsoft Agent Framework:
from agent_framework import Agentfrom agent_framework.openai import OpenAIChatClientfrom agentkernel.maf import MAFToolBuildersupport_agent = Agent(OpenAIChatClient(model="gpt-4.1-mini"), # swap the client for another MAF provider (agent-framework-anthropic, ...)name="support", # Always pass name= explicitly!description="Specialist for customer support and order inquiries",instructions="You help customers with order lookups, returns, and general support questions.",tools=MAFToolBuilder.bind([lookup_order]),)
Gotcha (Microsoft Agent Framework): Always passname=—MAFModuleraisesValueErrorfor an unnamed agent because AK registers agents by name immediately.
4b. Register with the Module
Add the new agent to the existing Module constructor call. Do not create a second Module.
# Before:OpenAIModule([triage_agent, math_agent, general_agent])# After:OpenAIModule([triage_agent, math_agent, general_agent, support_agent])
This applies to all frameworks — LangGraphModule, CrewAIModule, GoogleADKModule, SmolagentsModule, PydanticAIModule, MAFModule work the same way.
4c. Structured Output (Optional)
To make an agent return a typed dict instead of plain text, define a Pydantic model and configure it on the agent (or, for CrewAI, on the module). The runner returns an AgentReplyAny whose content is the result as a dict; str(reply) is the JSON serialization, so text-based consumers (CLI, chat integrations) work unchanged. Applies to non-streaming execution only.
from pydantic import BaseModelclass OrderStatus(BaseModel):order_id: strstatus: str
| Framework | How to configure | |
|---|---|---|
| OpenAI Agents SDK | Agent(..., output_type=OrderStatus) | |
| LangGraph | create_react_agent(..., response_format=OrderStatus) | |
| CrewAI | CrewAIModule([agent], output_pydantic={"support": OrderStatus}) — or output_json={...}; keyed by agent role | |
| Google ADK | LlmAgent(..., output_schema=OrderStatus) | |
| Smolagents | No schema parameter — have the agent pass a dict or Pydantic instance to final_answer |
Gotcha (CrewAI): CrewAI puts the output schema on theTask, not theAgent— and Agent Kernel builds the task internally per run, so the schema is passed to theCrewAIModuleconstructor keyed by agent role.
Step 5: Add Handoffs
Wire the new agent into the existing routing so the triage/supervisor agent can delegate to it.
OpenAI Agents SDK:
Add the new agent to the triage agent's handoffs list:
triage_agent = Agent(name="triage",instructions="You route requests to the right specialist agent...",handoffs=[math_agent, general_agent, support_agent], # Add here)
Update the triage instructions to mention the new agent:
instructions = """You route user requests to the right specialist:- math agent: for calculations and math problems- general agent: for general knowledge questions- support agent: for customer support and order inquiries ← ADD THIS"""
LangGraph:
Add the new agent to the supervisor's agents list:
from langgraph_supervisor import create_supervisortriage_agent = create_supervisor(model=model,agents=[math_agent, general_agent, support_agent], # Add hereprompt="Route requests to the right specialist...",).compile(name="triage")
CrewAI:
No explicit routing — CrewAI automatically makes all agents in the Module available. Just add the new agent to the Module list (Step 4b). The Crew will include it.
Google ADK:
Add the new agent to the triage agent's sub_agents:
from google.adk.agents import LlmAgenttriage_agent = LlmAgent(name="triage",model=LiteLlm(model="openai/gpt-4o-mini"),description="Routes requests to specialists",instruction="""Route requests to the right specialist.Use transfer_to_agent to delegate:- math: for calculations- general: for general knowledge- support: for customer support and order inquiries ← ADD THIS""",sub_agents=[math_agent, general_agent, support_agent], # Add here)
Smolagents:
Add the new agent to the triage agent's managed_agents list:
triage_agent = ToolCallingAgent(tools=[],model=model,name="triage",description="You determine which agent to use based on the user's question.",managed_agents=[math_agent, general_agent, support_agent], # Add here)
Step 6: Add Hooks (Optional)
Attach pre/post processing to the new agent. See the ak-add-capabilities skill for full hook patterns.
# Pre-hook: runs before the agent processes the requestmodule.pre_hook(support_agent, [RAGPreHook()])# Post-hook: runs after the agent generates a responsemodule.post_hook(support_agent, [DisclaimerPostHook()])
Where module is the framework Module instance (e.g., OpenAIModule). To use hooks, assign the Module to a variable:
module = OpenAIModule([triage_agent, math_agent, general_agent, support_agent])module.pre_hook(support_agent, [RAGPreHook()])
Framework-native run options (optional): to pass the framework's own run arguments for one agent (OpenAI max_turns, hooks=RunHooks() and run_config=RunConfig(...); LangGraph config; ADK plugins and run_config; Pydantic AI usage_limits; Microsoft Agent Framework options; CrewAI step_callback; smolagents max_steps), declare them with run_options on the same module, in the framework's own types:
from agents import RunConfigmodule.run_options(support_agent, max_turns=25, hooks=ProgressHooks(), run_config=RunConfig(...))
Keys the adapter populates itself (session, context, input, ...) raise ValueError at declaration. See the ak-add-capabilities skill for the per-framework destinations.
To compute options per run, give run_options a callable before the keywords; it is called with (agent, session, requests) on every run and its mapping is merged over the static keywords:
def options_for(agent, session, requests):return {"max_turns": 10} if session.id.startswith("guest") else {}module.run_options(support_agent, options_for, max_turns=25, hooks=ProgressHooks())
Step 7: Update Dependencies (If Needed)
If the new tool or agent requires additional packages, update pyproject.toml:
dependencies = ["agentkernel[openai,api,redis]>=0.9.5","httpx>=0.27.0", # Add any new deps for your tool]
Then run:
uv sync
Step 8: Verify
- Syntax check — Run:
uv run python -c "import app"(or whatever the entry file is)
- Test the new agent — Add a test for the new agent (see
ak-testskill for test setup):
@pytest.mark.order(10)async def test_support_routing(test_client):await test_client.send("What's the status of order 12345?")await test_client.expect(["order", "12345", "shipped"])
- Run tests:
uv run pytest -v
- Manual test (API mode):
python app.py &curl -X POST http://localhost:8000/run \-H "Content-Type: application/json" \-d '{"prompt": "Check order 12345", "session_id": "test-1", "agent": "triage"}'
Common Gotchas
| Gotcha | Details | |
|---|---|---|
| ToolContext access | Always use ToolContext.get() inside the tool function body. Never pass context as a function parameter. | |
| LangGraph `name=` | Always pass name= to create_react_agent(). Without it, the supervisor cannot route to the agent. | |
| CrewAI `role=` | Use role= as the agent identifier, not name=. Agent Kernel reads agent.role as the agent name. | |
| CrewAI `verbose=` | Set verbose=False on agents to prevent noisy console output. | |
| CrewAI conversation history | CrewAI runner keeps its own per-session transcript (last 20 lines) prepended to each task description, independent of the Memory feature. If Memory.remember() fails (e.g. no embedder configured), the runner logs a warning and continues instead of failing the run. | |
| CrewAI structured output | Configured on the module, not the agent: CrewAIModule([agent], output_pydantic={"<role>": Model}). The native crewai.Agent rejects an output_pydantic attribute (it belongs to the Task, which Agent Kernel builds per run). | |
| Google ADK `LiteLlm` | Wrap the model string: LiteLlm(model="openai/gpt-4o-mini"). A bare string won't work. | |
| Env var nesting | Use __ (double underscore) as the nested delimiter: AK_REDIS__URL, AK_WHATSAPP__ACCESS_TOKEN. | |
| Single Module | Only one Module instance per framework. Add new agents to the existing Module's agent list. | |
| Single `bind()` | Add new tools to the existing ToolBuilder.bind() call for that agent. Don't create a second one. |
What to Do Next
Now that you've added new tools and agents to your project, here are natural next steps:
- Add more tools & agents → Use this
ak-buildskill again (it's meant to be used repeatedly) - Add guardrails, tracing, or sessions → Use the
ak-add-capabilitiesskill to add input/output guardrails (OpenAI, Bedrock, Walled AI), observability tracing (Langfuse, OpenLLMetry, Logfire, CloudWatch), session persistence (Redis, DynamoDB, Cosmos DB), MCP server, A2A protocol, custom hooks, or multimodal support - Connect a messaging platform → Use the
ak-add-integrationskill to add Slack, WhatsApp, Messenger, Instagram, Telegram, or Gmail - Deploy to cloud → Use the
ak-cloud-deployskill to deploy to AWS Lambda, AWS ECS/Fargate, Azure Functions, or Azure Container Apps with Terraform - Set up testing → Use the
ak-testskill to configure test modes (score, llm, fallback), write agent tests, and debug common issues