AWS Cost Operations
zxkane/aws-skills
AWS cost optimization, monitoring, and operational excellence expert.
Add capabilities to an existing Agent Kernel project. An agent skill from yaalalabs/agent-kernel.
$ npx skills add yaalalabs/agent-kernel --skill ak-add-capabilities -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install yaalalabs/agent-kernel ak-add-capabilities --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/yaalalabs/agent-kernel.git skills-src && mkdir -p .claude/skills && cp -r skills-src/ak-py/src/agentkernel/skills/ak-add-capabilities .claude/skills/ak-add-capabilities && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "ak-add-capabilities" agent skill from https://github.com/yaalalabs/agent-kernel/tree/develop/ak-py/src/agentkernel/skills/ak-add-capabilities into .claude/skills/ak-add-capabilities/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ak-add-capabilities", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/yaalalabs/agent-kernel/tree/develop/ak-py/src/agentkernel/skills/ak-add-capabilitiesType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add yaalalabs/agent-kernel --skill ak-add-capabilities -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install yaalalabs/agent-kernel ak-add-capabilities --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/yaalalabs/agent-kernel.git skills-src && mkdir -p .agents/skills && cp -r skills-src/ak-py/src/agentkernel/skills/ak-add-capabilities .agents/skills/ak-add-capabilities && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "ak-add-capabilities" agent skill from https://github.com/yaalalabs/agent-kernel/tree/develop/ak-py/src/agentkernel/skills/ak-add-capabilities into .agents/skills/ak-add-capabilities/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ak-add-capabilities", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add yaalalabs/agent-kernel --skill ak-add-capabilities -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install yaalalabs/agent-kernel ak-add-capabilities --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/yaalalabs/agent-kernel.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/ak-py/src/agentkernel/skills/ak-add-capabilities .cursor/skills/ak-add-capabilities && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "ak-add-capabilities" agent skill from https://github.com/yaalalabs/agent-kernel/tree/develop/ak-py/src/agentkernel/skills/ak-add-capabilities into .cursor/skills/ak-add-capabilities/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ak-add-capabilities", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/yaalalabs/agent-kernel.git --path ak-py/src/agentkernel/skills/ak-add-capabilities--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add yaalalabs/agent-kernel --skill ak-add-capabilities -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install yaalalabs/agent-kernel ak-add-capabilities --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/yaalalabs/agent-kernel.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/ak-py/src/agentkernel/skills/ak-add-capabilities .gemini/skills/ak-add-capabilities && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "ak-add-capabilities" agent skill from https://github.com/yaalalabs/agent-kernel/tree/develop/ak-py/src/agentkernel/skills/ak-add-capabilities into .gemini/skills/ak-add-capabilities/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ak-add-capabilities", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install yaalalabs/agent-kernel ak-add-capabilitiesInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add yaalalabs/agent-kernel --skill ak-add-capabilities -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/yaalalabs/agent-kernel.git skills-src && mkdir -p .github/skills && cp -r skills-src/ak-py/src/agentkernel/skills/ak-add-capabilities .github/skills/ak-add-capabilities && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "ak-add-capabilities" agent skill from https://github.com/yaalalabs/agent-kernel/tree/develop/ak-py/src/agentkernel/skills/ak-add-capabilities into .github/skills/ak-add-capabilities/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ak-add-capabilities", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add yaalalabs/agent-kernel --skill ak-add-capabilities -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install yaalalabs/agent-kernel ak-add-capabilities --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/yaalalabs/agent-kernel.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/ak-py/src/agentkernel/skills/ak-add-capabilities .opencode/skills/ak-add-capabilities && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "ak-add-capabilities" agent skill from https://github.com/yaalalabs/agent-kernel/tree/develop/ak-py/src/agentkernel/skills/ak-add-capabilities into .opencode/skills/ak-add-capabilities/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ak-add-capabilities", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
ak-add-capabilitiesAdd capabilities to an existing Agent Kernel project. An agent skill from yaalalabs/agent-kernel.
Ak Add Capabilities is an agent skill from yaalalabs/agent-kernel. Add capabilities to an existing Agent Kernel project. This skill guides you through adding guardrails, tracing/observability, session persistence, knowledge bases, MCP server, A2A server, AG-UI server, pre/post hooks, multimodal support, conversation thread support, scheduled tasks (deferred and recurring chat execution), the sandbox capability (isolated code execution), and secret resolution (environment first, then AWS SSM Parameter Store or a custom provider). Session persistence supports Redis, DynamoDB…
Its SKILL.md is about 13k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files (for example `evals/evals.json`).
It sits in Databases, covering NoSQL databases, Observability and LLM guardrails. It works with Amazon Web Services, Amazon DynamoDB, Azure Cosmos DB and Cloud Firestore. The repository describes itself as: The Operating System for Scalable Enterprise AI Agents - Run, orchestrate, and deploy Compliant Enterprise AI Agents at scale across frameworks, without lock-in, rewrites or… The licence is Apache-2.0.
3 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 97fa8d9. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
curlpipFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
cloud.langfuse.comAlso links to:
kernel.yaala.aigithub.comFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
OPENAI_API_KEYAZURE_COSMOS_KEYWALLED_API_KEYLANGFUSE_PUBLIC_KEYLANGFUSE_SECRET_KEYLOGFIRE_TOKENNEO4J_PASSWORDSTARBURST_PASSWORDWEATHER_API_KEYFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Ak Add Capabilities loads about 13k tokens when it runs. Until then it costs about 187 tokens; SKILL.md has 3,977 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from yaalalabs/agent-kernel at commit 97fa8d9, republished under its Apache-2.0 licence (© yaalalabs). 3,977 words, ~13,369 tokens.
.claude/skills/ak-add-capabilities/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.Use this skill to enhance your Agent Kernel project with additional capabilities.
When the user wants to add a capability, follow this workflow:
Check for an existing Agent Kernel project with pyproject.toml and agent definition file.
Which capability would you like to add?
session_idschedule block on a chat request, management routes, agent tools)SecretManager (environment first, then AWS SSM Parameter Store or a custom provider)resume block on the next request)Ask: Input guardrails, output guardrails, or both? Which provider — OpenAI, AWS Bedrock, or Walled AI?
For OpenAI Guardrails:
pyproject.toml:dependencies = [
"agentkernel[openai,api]>=0.9.5",
# OpenAI guardrails use the openai extra — already included if using OpenAI framework
]guardrails_input.json:[
{
"type": "moderation",
"moderation_config": {
"content_type": ["violence", "sexual", "harassment", "self-harm"],
"threshold": 0.5
}
},
{
"type": "jailbreak",
"jailbreak_config": {}
}
]guardrails_output.json:[
{
"type": "pii_detection",
"pii_config": {
"output_handling": "block",
"entities": ["email_address", "phone_number", "ssn"]
}
}
]config.yaml:guardrail:
input:
enabled: true
type: openai
model: gpt-4o-mini
config_path: guardrails_input.json
output:
enabled: true
type: openai
config_path: guardrails_output.jsonFor AWS Bedrock Guardrails:
pyproject.toml:dependencies = [
"agentkernel[openai,api,aws]>=0.9.5",
]config.yaml:guardrail:
input:
enabled: true
type: bedrock
guardrail_id: "<your-bedrock-guardrail-id>"
guardrail_version: "DRAFT"
output:
enabled: true
type: bedrock
guardrail_id: "<your-bedrock-guardrail-id>"
guardrail_version: "DRAFT"For Walled AI Guardrails:
pyproject.toml:dependencies = [
"agentkernel[openai,api,walledai]>=0.9.5",
]config.yaml:guardrail:
input:
enabled: true
type: walledai
pii: true # enable PII redaction on input
output:
enabled: true
type: walledai
pii: true # enable PII unmasking on outputexport WALLED_API_KEY="your-walledai-api-key"WalledProtect. If unsafe, the request is blocked. If pii: true, text is redacted via WalledRedact and the PII mapping is stored in the session's non-volatile cache.pii: true, redacted placeholders in the agent's reply are replaced with the original values using the stored mapping.Ask: Which tracing backend — Langfuse, OpenLLMetry (Traceloop), Pydantic Logfire, or AWS CloudWatch?
For Langfuse:
pyproject.toml:dependencies = [
"agentkernel[openai,api,langfuse]>=0.9.5",
]config.yaml:trace:
enabled: true
type: langfuseexport LANGFUSE_PUBLIC_KEY="pk-..."
export LANGFUSE_SECRET_KEY="sk-..."
export LANGFUSE_HOST="https://cloud.langfuse.com" # or self-hosted URLFor OpenLLMetry (Traceloop):
pyproject.toml:dependencies = [
"agentkernel[openai,api,openllmetry]>=0.9.5",
]config.yaml:trace:
enabled: true
type: openllmetryFor Pydantic Logfire:
pyproject.toml:dependencies = [
"agentkernel[openai,api,logfire]>=0.9.5",
]config.yaml:trace:
enabled: true
type: logfireexport LOGFIRE_TOKEN="your-write-token"For AWS CloudWatch:
pyproject.toml:dependencies = [
"agentkernel[openai,api,cloudwatch]>=0.9.5",
]config.yaml:trace:
enabled: true
type: cloudwatchexport AWS_REGION="us-east-1"
# Optional: export through the CloudWatch agent or an OpenTelemetry collector instead of straight to X-Ray
# export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://localhost:4318/v1/traces"One-time AWS setup: enable CloudWatch Transaction Search in the account, and attach the
AWSXrayWriteOnlyAccess managed policy to the role that runs the agent.
No code changes needed — tracing is automatically applied to all agent executions.
Ask: Which backend — Redis, DynamoDB (AWS), Cosmos DB (Azure), or Firestore (GCP)?
For Redis:
pyproject.toml:dependencies = [
"agentkernel[openai,api,redis]>=0.9.5",
]config.yaml:session:
type: redis
cache: 256 # LRU cache size (optional, improves performance)
redis:
prefix: "ak:<project>:" # Key prefix for namespacing
url: "redis://localhost:6379"
ttl: 3600 # Session TTL in seconds (optional)For DynamoDB:
pyproject.toml:dependencies = [
"agentkernel[openai,api,aws]>=0.9.5",
]config.yaml:session:
type: dynamodb
cache: 256
dynamodb:
table_name: "<your-table-name>"
region: "us-east-1"
ttl: 3600session_id (String) and sort key key (String). Enable TTL on expiry_time attribute.For Cosmos DB:
pyproject.toml:dependencies = [
"agentkernel[openai,api,azure]>=0.9.5",
]config.yaml:session:
type: cosmosdb
cache: 256
cosmosdb:
endpoint: "https://<account>.table.cosmos.azure.com:443/"
table_name: "<your-table-name>"
ttl: 3600AZURE_COSMOS_KEY environment variable.For Firestore (GCP):
pyproject.toml:dependencies = [
"agentkernel[openai,api,gcp]>=0.9.5",
]config.yaml:session:
type: firestore
cache: 256
firestore:
collection_name: "ak_sessions"
project_id: "<your-gcp-project-id>" # optional, inferred from ADC if omitted
ttl: 604800expiry_time field for automatic document expiry.Add durable knowledge tools that your agents can query and update across sessions.
Ask: Which backend do you want to use?
1. Update pyproject.toml dependencies based on backend:
dependencies = [
"agentkernel[openai,api,chromadb]>=0.9.5", # for Chroma
# or "agentkernel[openai,api,neo4j]>=0.9.5"
# or "agentkernel[openai,api,trino]>=0.9.5"
# OKF needs NO extra - pyyaml is a core dependency:
# "agentkernel[openai,api]>=0.9.5"
# ...unless the bundle is served from S3, which uses the aws extra:
# "agentkernel[openai,api,aws]>=0.9.5"
]2. Configure backend + KnowledgeBuilder in your agent file (OpenAI example):
from agents import Agent
from agentkernel.cli import CLI
from agentkernel.knowledgebase.chroma import ChromaManager
from agentkernel.knowledgebase.knowledgebuilder import KnowledgeBuilder
from agentkernel.openai import OpenAIModule, OpenAIToolBuilder
backend = ChromaManager(name="ChromaDB").add_schema(
{
"description": "Semantic vector store for unstructured facts",
"store_payload": {"text": "string", "source": "string"},
"read_payload": {"query": "string", "limit": "int"},
}
)
kb = KnowledgeBuilder([backend])
kb_tools = kb.build() # -> get_schemas, read_kb, write_kb, get_all_kb_descriptions
# (+ search_kb / fetch_kb / browse_kb when a registered
# backend declares those capabilities - see step 4)
# build(writable=False) omits write_kb for a read-only agent
router = Agent(
name="kb_router",
instructions="Call get_schemas() first, then route reads/writes to the correct backend.",
tools=OpenAIToolBuilder.bind(kb_tools),
)
OpenAIModule([router])
if __name__ == "__main__":
CLI.main()3. Multi-backend routing with semantic placeholders (for Starburst or mixed backends):
kb = KnowledgeBuilder(
[backend_a, backend_b],
semantic_map={
"<SHEETS_SOURCE>": "TABLE(kb_sheets.system.sheet(id => 'SHEET_ID'))",
"<MONGO_SOURCE>": "mongodb.default.clients",
},
)4. Backend notes:
ChromaManager: semantic search and fuzzy retrieval. Declares search + writable.Neo4jManager: entity/relationship graphs and Cypher queries. Declares query + writable.StarburstManager: read-only - it declares writable=False, so write_kb reports it as
read-only instead of writing. Use read_kb; there is no way to mis-route a write into silent
data loss.OKFManager: an Open Knowledge Format bundle. Declares search (lexical), fetch, browse and
derives_schema, and inherits the store's writability.Which tools the agent gets is decided by those declarations. Four are always built
(get_schemas, read_kb, write_kb, get_all_kb_descriptions), unless build(writable=False) drops
write_kb for an agent that must never write. fetch_kb, browse_kb and search_kb are added when a
registered backend declares fetch / browse / search. So a Neo4j or Starburst app gets four tools,
a Chroma app five, and an OKF app seven — the agent's prompt never names an operation nothing can
serve.
4a. Open Knowledge Format bundle:
from agentkernel.knowledgebase import DocumentStore, KnowledgeBuilder, LocalDocumentStore, OKFManager
# A directory of markdown documents with YAML frontmatter. Read-only because a bundle checked
# into git is a knowledge source, not a scratchpad; store writability folds into the backend's
# declaration, so this one keyword is what makes write_kb report it as read-only.
backend = OKFManager(
LocalDocumentStore("./bundle", writable=False),
name="OKF",
description="Markdown knowledge bundle: one concept per topic, in browsable namespaces.",
)
# Same bundle from S3 - a store swap, not a backend change (needs the aws extra):
# backend = OKFManager(DocumentStore.from_uri("s3://my-bucket/bundles/kb"), name="OKF")
kb_tools = KnowledgeBuilder([backend]).build()There is no add_schema() call: OKFManager declares derives_schema=True and answers
get_schemas() from the bundle itself. Tell the agent to browse_kb a namespace, then fetch_kb the
concept path it saw — only fetch_kb returns a full document body. See
examples/cli/knowledgebase/openai/okf/ for a runnable demo with a checked-in bundle.
5. Environment variables (examples):
# Neo4j
export NEO4J_URI="bolt://localhost:7687"
export NEO4J_USERNAME="neo4j"
export NEO4J_PASSWORD="password"
# Starburst
export STARBURST_HOST="<cluster>.trino.galaxy.starburst.io"
export STARBURST_USER="<user>"
export STARBURST_PASSWORD="<password-or-token>"
export STARBURST_PORT=443An OKF bundle needs no credentials when it is a local directory. Serving one from S3 uses the standard
AWS chain (AWS_REGION plus whatever credentials the environment already provides), and the bundle
location is a single string you can pass through DocumentStore.from_uri() — a bare path, file://,
or s3://bucket/prefix — so one configuration value covers local-in-development and S3-in-production.
6. If the user asks for a new backend adapter:
KnowledgeBase under ak-py/src/agentkernel/knowledgebase/..agents/skills/ak-dev-new-knowledgebase-integration/SKILL.md for contributor workflows.Expose your agents as MCP (Model Context Protocol) tools so other AI systems can discover and call them.
pyproject.toml:dependencies = [
"agentkernel[openai,api,mcp]>=0.9.5",
]config.yaml:mcp:
enabled: true
expose_agents: true # Auto-expose all registered agents
# agents: # Or list specific agents
# - name: general
# - name: math
url: "http://localhost:8000" # Base URL of your serverEnable Agent-to-Agent communication via Google's A2A protocol.
pyproject.toml:dependencies = [
"agentkernel[openai,api,a2a]>=0.9.5",
]config.yaml:a2a:
enabled: true
agents:
- general # List agents to expose via A2A
url: "http://localhost:8000"/.well-known/agent.json.Stream any streaming-capable agent (OpenAI Agents SDK, LangGraph, Google ADK, Pydantic AI — not CrewAI or Smolagents) to a frontend over the AG-UI protocol: text, tool calls, reasoning, and an optional shared JSON state, all as one typed event stream.
pyproject.toml:dependencies = [
"agentkernel[openai,api,agui]>=0.9.5",
]config.yaml:agui:
agents: ["general"] # omitted = every streaming-capable agent is reachable
prefix: "/agui"
default_agent: "general" # also serves POST /agui; must be one of `agents` when both are set
state:
enabled: true # attaches get_agui_state / update_agui_state
client_context:
enabled: true # attaches read-only get_forwarded_props / get_agui_contextAGUIRequestHandler with an Authoriser (or AuthValidator) — AG-UI has no anonymous
mode, because a run executes an agent on the caller's behalf:from agentkernel.agui import AGUIRequestHandler
from agentkernel.api import RESTAPI
from agentkernel.auth import Authoriser
class MyAuthoriser(Authoriser):
def authorise(self, token: str) -> str | None:
... # validate the token, return the caller's user_id or None
RESTAPI.run(handlers=[AGUIRequestHandler(authoriser=MyAuthoriser())])agui.prefix: GET {prefix}/agents, POST {prefix}/{agent_name}, and
POST {prefix} when default_agent is set. See examples/api/agui for a full demo including a
React/Vite frontend.Add custom pre/post processing to your agents.
Pre-hook example (RAG injection):
from agentkernel.core import Agent, PreHook, Session
from agentkernel.core.model import AgentReply, AgentRequest, AgentRequestText
class RAGPreHook(PreHook):
async def on_run(
self, session: Session, agent: Agent, requests: list[AgentRequest]
) -> list[AgentRequest] | AgentReply:
# Extract the user's prompt
prompt = ""
for req in requests:
if isinstance(req, AgentRequestText):
prompt = req.prompt
break
# Retrieve relevant context (your RAG logic here)
context = await self._retrieve_context(prompt)
# Modify the prompt with additional context
if context:
enhanced_prompt = f"Context: {context}\n\nUser question: {prompt}"
return [AgentRequestText(prompt=enhanced_prompt)]
return requests
async def _retrieve_context(self, query: str) -> str:
# Implement your retrieval logic
return ""
def name(self) -> str:
return "rag_prehook"Post-hook example (disclaimer):
from agentkernel.core import Agent, PostHook, Session
from agentkernel.core.model import AgentReply, AgentRequest
class DisclaimerPostHook(PostHook):
async def on_run(
self, session: Session, requests: list[AgentRequest], agent: Agent, agent_reply: AgentReply
) -> AgentReply:
agent_reply.response += "\n\n_Disclaimer: This is AI-generated content._"
return agent_reply
def name(self) -> str:
return "disclaimer_posthook"Attach hooks to agents:
from agentkernel.openai import OpenAIModule
from agents import Agent
agent = Agent(name="general", instructions="...")
module = OpenAIModule([agent])
module.pre_hook(agent, [RAGPreHook()])
module.post_hook(agent, [DisclaimerPostHook()])Framework-native run options: Agent Kernel hooks wrap the whole run. For the framework's own
per-run arguments and lifecycle hooks, declare them per agent with module.run_options(agent, **options), chained like pre_hook / post_hook; repeated calls merge, the later call winning per
key. Every keyword is one of the framework's native run arguments and Agent Kernel merges it into the
call with the keys it owns written last. A native hook's callbacks run inside the Agent Kernel run,
so Session.current() resolves in them (keep per-run state in the session's volatile cache, not on
the hook instance, which is shared by concurrent runs). This works in any execution mode, including
rest_sync; the streaming hook below is the framework-agnostic path for stream mode only.
from agents import RunConfig, RunHooks
class ProgressHooks(RunHooks):
async def on_tool_start(self, context, agent, tool) -> None:
cache = Session.current().get_volatile_cache()
cache.set("tool_calls", (cache.get("tool_calls") or 0) + 1)
module.run_options(
agent,
max_turns=25, # the SDK default is 10
hooks=ProgressHooks(),
run_config=RunConfig(call_model_input_filter=trim_history),
)Options computed per run: pass a callable before the keywords, module.run_options(agent, options_for, **static). It is called as options_for(agent, session, requests) on every run (sync or
async), after the pre-hooks and inside the run, so Session.current() and ToolContext.get() resolve
in it (except on Google ADK, where the tool context does not exist yet and ToolContext.get() raises), and its mapping is merged over
the static keywords, a factory key winning. A reserved key in the result is rejected on the run, and a
factory that raises fails that run like a framework error. One factory per agent, shared by concurrent
runs: keep per-run state in the session.
def options_for(agent, session, requests):
options = {"run_config": RunConfig(trace_metadata={"session_id": session.id})}
if session.id.startswith("guest"):
options["max_turns"] = 10 # over the static 25 below
return options
module.run_options(agent, options_for, max_turns=25, hooks=ProgressHooks())| Framework | run_options keywords go to | Turn limit | Progress hook | Reserved (raise at declaration) |
|---|---|---|---|---|
| OpenAI Agents SDK | Runner.run / run_streamed | max_turns | hooks=RunHooks() | starting_agent, input, session, context, conversation_id, previous_response_id, auto_previous_response_id |
| LangGraph | ainvoke / astream_events (config is deep-merged: configurable.thread_id stays the session id, callbacks lists concatenate) | config["recursion_limit"] | config["callbacks"] | input, version, stream_mode, output_keys, print_mode, config.configurable.thread_id, and any RunnableConfig key at the top level |
| Google ADK | the per-run App (plugins), the per-run Runner(...) constructor (services) and run_async (run_config; copied with streaming_mode=SSE in stream mode) | RunConfig(max_llm_calls=...) | plugins=[BasePlugin()] | agent, app, app_name, node, session_service, auto_create_session, user_id, session_id, new_message, state_delta, invocation_id, yield_user_message |
| Pydantic AI | agent.run / run_stream_events (event_stream_handler is dropped in stream mode with one warning) | UsageLimits(request_limit=...) | event_stream_handler | user_prompt, message_history, deps |
| CrewAI | the per-run Crew(...) constructor (verbose=False is an overridable default; agents resolve by role; max_rpm is a forwarded rate limit) | max_iter on the native Agent (needs nothing from Agent Kernel) | step_callback / task_callback | agents, tasks, memory |
| smolagents | agent.run | max_steps | step_callbacks on the agent constructor (needs nothing from Agent Kernel) | task, reset, additional_args, stream, return_full_result |
Worked demos: examples/cli/<framework>-run-options for each of the six frameworks, and
examples/cli/openai-dynamic-run-options for options computed per run.
Streaming event hook (optional): override on_stream_event on a PostHook to inspect or modify every event a streamed run produces while execution.mode: stream is active. Unlike on_run it sees the whole stream — message and reasoning text, tool call names, arguments and results, and the boundaries that pair them. Return the event to pass it on, a modified event of the same type to rewrite it, None to drop it, or a list to emit several events in its place (a list is emitted as-is and ends the chain for that event, so return event and return [event] differ). Raise StreamHalt to end the run: Agent Kernel closes any open boundary, emits one error chunk, and does not store the session. Only called when streaming; regular on_run() still handles the non-streaming path.
class RedactingPostHook(DisclaimerPostHook):
async def on_stream_event(self, session, requests, agent, event):
if event.type == "tool_call_result":
return event.model_copy(update={"content": event.content.replace("SECRET", "***")})
return eventTo rewrite text that spans fragments, hold each text_delta by returning None while accumulating it in session.get_volatile_cache(), then return [TextDelta(...), event] at message_end. Accumulate in the volatile cache, never on self — one hook instance serves every concurrent request.
Per-run framework context (optional): hooks are the supported surface for the reserved
framework_context session key — a framework-agnostic, picklable context/state dict that the runner
injects into the native framework call (context=, deps=, session state, ...) and writes back after a
successful run. It is never auto-created; seed it explicitly from a pre-hook, and read it back from a
post-hook once the run has written its results:
class SeedCart(PreHook):
async def on_run(self, session, agent, requests):
if session.get_framework_context() is None:
session.set_framework_context({"cart": []})
return requests
def name(self):
return "SeedCart"
class AppendCart(PostHook):
async def on_run(self, session, requests, agent, agent_reply):
cart = (session.get_framework_context() or {}).get("cart", [])
agent_reply.response += f"\n\nCurrent cart: {', '.join(cart) or '(empty)'}"
return agent_reply
def name(self):
return "AppendCart"Use session.get_framework_context() / set_framework_context(dict) / clear_framework_context() —
these accessors are for hooks only. Tools must use their framework's native handle instead
(RunContextWrapper.context on OpenAI, RunContext.deps on Pydantic AI, tool_context.state on ADK,
...) since a tool writing through ToolContext.get().session writes to a different object than the one
the run is carrying. Round-trip fidelity is framework-dependent (full for OpenAI/Pydantic AI, partial for
ADK/smolagents/LangGraph, unsupported for CrewAI) — see the framework's page under docs/docs/frameworks/
for specifics.
Accessing the framework-native session (optional): unlike framework_context above (an app-defined
dict you seed yourself), session.get_framework_session() reaches the framework adapter's own session
object directly — the same live object each runner stores under its runner-name key (e.g. "openai"),
without you needing to name that key. It only works from inside a hook or a tool (an agent must currently
be running — it raises RuntimeError otherwise), and mutating the returned object through its own methods
is visible immediately, no session.set(...) needed:
class HistoryTrimHook(PostHook):
async def on_run(self, session, requests, agent, agent_reply):
openai_session = session.get_framework_session()
if openai_session is not None:
items = await openai_session.get_items()
if len(items) > 20:
await openai_session.clear_session()
await openai_session.add_items(items[-20:]) # keep only the most recent 20
return agent_reply
def name(self):
return "HistoryTrimHook"See examples/cli/session-context/hooks.py (HistoryTrimHook) for a complete example that caps the
OpenAI Agents SDK's raw conversation history after every turn.
Enable image and file processing in your agents.
Ask: Which storage backend — in-memory (default, dev), Redis (production), or DynamoDB (serverless/AWS)?
Basic setup (in-memory storage, good for development):
pyproject.toml:dependencies = [
"agentkernel[openai,api,multimodal]>=0.9.5",
]config.yaml:multimodal:
enabled: true
max_attachments: 20
description_model: "gpt-4o" # Model for generating brief descriptions
analysis_model: "gpt-4o" # Model for detailed analysis via toolanalyze_attachments) is automatically attached to all agentsanalyze_attachments(attachment_ids, prompt) for detailed analysisFor Redis storage (production, persistent, distributed):
pyproject.toml:dependencies = [
"agentkernel[openai,api,redis,multimodal]>=0.9.5",
]config.yaml:multimodal:
enabled: true
storage_type: redis
max_attachments: 20
description_model: "gpt-4o"
analysis_model: "gpt-4o"
redis:
url: "redis://localhost:6379"
prefix: "ak:attachments:"
ttl: 604800 # Attachment TTL in seconds (7 days)For DynamoDB storage (serverless/AWS):
pyproject.toml:dependencies = [
"agentkernel[openai,api,aws,multimodal]>=0.9.5",
]config.yaml:multimodal:
enabled: true
storage_type: dynamodb
max_attachments: 20
description_model: "gpt-4o"
analysis_model: "gpt-4o"
dynamodb:
table_name: "ak-attachments"
ttl: 604800session_id (String) and sort key attachment_id (String). Enable TTL on expiry_time attribute.Storage type comparison:
| Type | Persistence | Multi-process | Setup | Best for |
|---|---|---|---|---|
in_memory | Lost on restart | Single process | None | Dev/testing |
redis | Persistent | Distributed | Redis server | Production |
dynamodb | Persistent | Distributed | AWS table | Serverless/Lambda |
How it works:
analyze_attachments toolSend multimodal requests via API:
# Base64 image
curl -X POST http://localhost:8000/run \
-H "Content-Type: application/json" \
-d '{
"prompt": "What is in this image?",
"session_id": "test-1",
"agent": "general",
"image": "<base64-data>",
"image_name": "photo.jpg"
}'
# File upload (multipart)
curl -X POST http://localhost:8000/run \
-F "prompt=Summarize this document" \
-F "session_id=test-1" \
-F "agent=general" \
-F "file=@document.pdf"Environment variables (all storage types):
# Required: LLM API key for vision models
export OPENAI_API_KEY="sk-..."
# For Redis storage
export AK_MULTIMODAL__STORAGE_TYPE=redis
export AK_MULTIMODAL__REDIS__URL="redis://localhost:6379"
# For DynamoDB storage
export AK_MULTIMODAL__STORAGE_TYPE=dynamodb
export AK_MULTIMODAL__DYNAMODB__TABLE_NAME="ak-attachments"Enable persistent, named conversation threads keyed by session_id.
Ask: Which thread store backend — in-memory (default, dev), Redis, Valkey, DynamoDB (AWS), Firestore (GCP), or Cosmos DB (Azure)?
Basic setup (in-memory store, good for development):
pyproject.toml:dependencies = [
"agentkernel[openai,api]>=0.9.5",
]from agentkernel.api import RESTAPI
from agentkernel.thread import AgentThreadRequestHandler
RESTAPI.run(handlers=[AgentThreadRequestHandler()])config.yaml (selects the store backend; constructing the handler without this block fails
fast at startup):thread:
type: in_memory # other supported backends: redis | valkey | dynamodb | firestore | cosmosdbuser_id becomes required on the thread handler's chat requests (other surfaces are unaffected)GET /api/v1/threads and GET /api/v1/threads/{session_id} are served by the same handler for reading thread history (open by default, or protected by a pluggable Authoriser)litellm/an API key)thread_name on any chat request sets/renames the thread and locks it against automatic namingFor LLM-based thread naming, add the thread extra:
dependencies = [
"agentkernel[openai,api,thread]>=0.9.5",
]thread:
type: in_memory
naming:
model: "gpt-4o-mini" # LiteLLM model used to name threads
max_length: 80For Redis storage (production, persistent, distributed):
dependencies = [
"agentkernel[openai,api,redis,thread]>=0.9.5",
]thread:
type: redis
redis:
url: "redis://localhost:6379"
prefix: "ak:thread:"
ttl: 2592000 # Thread TTL in seconds (30 days, 0 disables)For Valkey storage (production, persistent, distributed, Redis-protocol compatible):
dependencies = [
"agentkernel[openai,api,valkey,thread]>=0.9.5",
]thread:
type: valkey
valkey:
url: "valkey://localhost:6379"
prefix: "ak:thread:"
ttl: 2592000 # Thread TTL in seconds (30 days, 0 disables)For DynamoDB storage (serverless/AWS):
dependencies = [
"agentkernel[openai,api,aws,thread]>=0.9.5",
]thread:
type: dynamodb
dynamodb:
table_name: "ak-agent-threads" # partition key session_id (S), sort key sk (S)
ttl: 0For Firestore storage (serverless/GCP):
thread:
type: firestore
firestore:
collection_name: "ak-agent-threads"
ttl: 0For Cosmos DB storage (Azure, Table API):
thread:
type: cosmosdb
cosmosdb:
connection_string: "${AZURE_COSMOS_CONNECTION_STRING}"
table_name: "akagentthreads"Protecting the read endpoints with an Authoriser:
from typing import Optional
from agentkernel.api import RESTAPI
from agentkernel.auth import Authoriser
from agentkernel.thread import AgentThreadRequestHandler
class DemoAuthoriser(Authoriser):
def authorise(self, token: str) -> Optional[str]:
# Validate the ****** against your own auth provider, return the user_id or None.
return {"alice-token": "alice", "bob-token": "bob"}.get(token)
RESTAPI.run(handlers=[AgentThreadRequestHandler(authoriser=DemoAuthoriser())])With an Authoriser configured, thread listings are scoped to the resolved user_id and reading another
user's thread is rejected (403). Without one, the read routes are open.
Attachments in thread mode: require multimodal.enabled: true with a shared attachment store —
in_memory, redis, or dynamodb (session_cache is rejected).
Send a chat request with a thread:
curl -X POST http://localhost:8000/api/v1/chat \
-H "Content-Type: application/json" \
-d '{"prompt": "What is the capital of France?", "session_id": "ses-1", "user_id": "alice", "thread_name": "Capitals quiz"}'See examples/api/thread-openai and examples/api/multimodal/thread-openai.
What it does: Lets a chat request run later, once or repeatedly. A request carrying a schedule
block is not executed — it is registered as a scheduled task and acknowledged with HTTP 202. When
an occurrence is due, the provider delivers the stored prompt into the input queue as a plain chat
request and the normal execution path runs it. The block also injects five agent tools
(create_schedule, list_schedules, get_schedule, update_schedule, delete_schedule) so the
agent can defer work itself. The management routes are not mounted from config — the application
mounts ScheduleRESTRequestHandler when it wants them, exactly as it mounts the Slack and thread
handlers.
Ask: Which provider — local (in-process thread, development) or eventbridge (AWS EventBridge
Scheduler, production)? And which task store — in-memory (default, dev), Redis, Valkey, or DynamoDB
(AWS)?
Important: occurrences are delivered into the input queue, so scheduling requires the queue
execution pipeline. Locally the in_memory transport satisfies this inside one process; on AWS it
means deploying in queue mode.
Basic setup (local provider, in-memory store — development):
pyproject.toml:dependencies = [
"agentkernel[openai,api,cron]>=0.9.5",
]The cron extra brings croniter, needed for cron parsing.
config.yaml. The presence of the schedule block is what enables deferring and the agent
tools; the management routes are mounted by the app in step 3:schedule:
provider:
type: local # other supported providers: eventbridge
store:
type: in_memory # other supported backends: redis | valkey | dynamodb
# agents: [assistant] # restrict the schedule tools to named agents; omitted = all agents
execution:
mode: rest_sync
queues:
type: in_memory # required: the provider fires occurrences into the input queueapp.py. Nothing is mounted from config, so an app that skips this
step still defers requests and still gets the agent tools — it just serves no /api/v1/schedules
routes:from agentkernel.pipeline import IOHandler
from agentkernel.schedule import ScheduleRESTRequestHandler
if __name__ == "__main__":
# config.yaml selects the in_memory queue transport, so this boots the whole single-process
# pipeline. The passed handlers are mounted alongside the pipeline's own chat route.
IOHandler.run(handlers=[ScheduleRESTRequestHandler()])schedule block: exactly one of at (ISO-8601 local wall-clock
timestamp, must be in the future) or cron (standard 5-field expression), plus timezone
(IANA, default UTC) and session_mode (reuse the originating session, or new for a fresh
session per occurrence)user_id becomes required on any request that schedules: it is the owner the task is stored
under and the identity later reads and changes are checked againstGET /api/v1/schedules (cursor-paginated) and GET/PUT/DELETE /api/v1/schedules/{task_id}
are mounted for listing, reading, amending and cancelling (open by default, or protected by a
pluggable Authoriser). There is deliberately no POST — creation is the chat block or the
agent toolPUT is full-replacement: send every value, including the ones that are not changing. status
covers the active/paused switch; a cancelled task keeps its record as the audit trailschedule block — use the JSON routeSend a chat request with a schedule:
# One-time
curl -i -X POST http://localhost:8000/api/v1/chat \
-H "Content-Type: application/json" \
-d '{"prompt": "Send me the daily summary", "session_id": "ses-1", "user_id": "alice",
"schedule": {"at": "2030-01-31T09:00:00", "timezone": "Asia/Colombo"}}'
# HTTP/1.1 202 Accepted
# {"result":"{\"status\": \"SCHEDULED\", \"scheduled_task_id\": \"74ca19a5-...\", \"session_id\": \"ses-1\"}","session_id":"ses-1"}
# Recurring, each occurrence in a fresh session
curl -X POST http://localhost:8000/api/v1/chat \
-H "Content-Type: application/json" \
-d '{"prompt": "Send the weekly report", "session_id": "ses-2", "user_id": "alice",
"schedule": {"cron": "0 9 * * 1", "timezone": "Asia/Colombo", "session_mode": "new"}}'
# Manage
curl "http://localhost:8000/api/v1/schedules?user_id=alice"
curl -X DELETE http://localhost:8000/api/v1/schedules/{task_id}For production on AWS (EventBridge Scheduler + DynamoDB):
dependencies = [
"agentkernel[openai,api,aws,cron]>=0.9.5",
]schedule:
provider:
type: eventbridge
store:
type: dynamodb
dynamodb:
table_name: "ak-agent-schedules" # partition key task_id (S), no sort key
ttl: 0 # 0 disables expiry (the default)group_name, role_arn and queue_arn under schedule.provider.eventbridge are supplied by the
Terraform modules as AK_SCHEDULE__PROVIDER__EVENTBRIDGE__* environment variables — do not hardcode
them. See the ak-cloud-deploy skill.
For Redis or Valkey task storage:
dependencies = [
"agentkernel[openai,api,redis,cron]>=0.9.5", # or valkey
]schedule:
store:
type: redis # or valkey, with a `valkey:` block
redis:
url: "redis://localhost:6379"
prefix: "ak:schedule:"
ttl: 0 # unlike threads this defaults to 0 — an expired task would stop firing silentlyTopology rules (validated at startup, not at first use):
| Combination | Rejected because |
|---|---|
local provider + a broker transport (sqs/kafka/nats) | The in-process timers are unreachable from the process serving the management routes — a cancellation would report success while the timer kept firing |
local provider + a shared store | Same split: the timers and the records must live together |
in_memory store + a broker transport | The records would be split across the runner and IO-handler processes |
eventbridge provider + a non-sqs transport | Delivery is baked into the schedule registration as an SQS target |
Protecting the management routes with an Authoriser: the routes are mounted by the application,
so pass the Authoriser to the ScheduleRESTRequestHandler constructor:
from typing import Optional
from agentkernel.auth import Authoriser
from agentkernel.pipeline import IOHandler
from agentkernel.schedule import ScheduleRESTRequestHandler
class DemoAuthoriser(Authoriser):
def authorise(self, token: str) -> Optional[str]:
# Validate the ****** against your own auth provider, return the user_id or None.
return {"alice-token": "alice", "bob-token": "bob"}.get(token)
if __name__ == "__main__":
IOHandler.run(handlers=[ScheduleRESTRequestHandler(authoriser=DemoAuthoriser())])With an Authoriser configured, listings are scoped to the resolved user_id and reading or changing
another user's schedule is rejected (403). Without one, the routes are open.
See examples/api/schedule-openai.
What it does: Lets a run stop mid-way to ask a person something — approve this gated tool, answer
this question — and resume from their decision minutes or hours later, possibly on another replica.
There is no enabled flag and no config block: a pause happens when the framework decides one is
needed, so it is declared where the framework declares it. Agent Kernel answers HTTP 202 with
status: "PAUSED" and the pending interruptions; the next request carries a resume block instead
of a prompt.
Ask: Which framework is the agent on? Only four can pause — OpenAI Agents SDK, LangGraph, Pydantic
AI and Google ADK. CrewAI and smolagents report supports_pause = False rather than pretending.
Important: the pause is written into the session, so a durable one needs a shared session
backend. With session.type: in_memory the record lives in one process and the replica receiving the
decision has never heard of the pause — Agent Kernel logs a warning the first time that happens.
Declaring it, per framework:
# OpenAI Agents SDK — a gated tool
@function_tool(needs_approval=True)
def issue_refund(order_id: str) -> str: ...
# LangGraph — an interrupt returns whatever the resume supplies
def ask(state):
choice = interrupt({"question": "Which method?", "options": ["Card", "Credit"]})
# Pydantic AI — needs DeferredToolRequests among its output types, or neither axis can pause
agent = Agent(model="openai:gpt-4.1-mini", output_type=[str, DeferredToolRequests])
@agent.tool(name="ask_size")
def ask_size(ctx: RunContext, q: str) -> str:
raise CallDeferred # a value; `requires_approval=True` asks for a verdict instead
# Google ADK — a result, or a verdict
LongRunningFunctionTool(func=ask_address)
FunctionTool(func=issue_refund, require_confirmation=True)Answering it:
# The pause: HTTP 202
# {"status": "PAUSED", "run_id": "9f2c...", "agent": "support",
# "interruptions": [{"id": "call_abc123", "kind": "tool_call", "tool_name": "issue_refund"}]}
curl -X POST http://localhost:8000/api/v1/chat -H "Content-Type: application/json" -d '{
"agent": "support", "session_id": "user-123",
"resume": {"decisions": [{"id": "call_abc123", "status": "approved"}]}
}'A decision takes status (approved | denied | cancelled — "nobody decided", which never reaches
the model as a refusal), an optional message for the human's own words, and an optional payload
for a structured answer. run_id is optional: the run resolves from the interruption ids.
What each framework can carry back differs, and Agent Kernel refuses rather than silently dropping.
OpenAI takes no payload at all (an approval is a boolean); Pydantic AI takes one only as a JSON
object on an approval, since it becomes the call's override_args; ADK refuses one on a confirmation
and cannot tell cancelled from denied there. Point the user at the
Human in the Loop page for the full table
before they design the question.
What it does: Lets agents execute code and shell commands in an isolated, permission-bounded
environment. When enabled, agents automatically gain sandbox tools (run_code, run_command,
write_sandbox_file, read_sandbox_file, check_sandbox_task, list_sandbox_sessions,
new_sandbox_session, destroy_sandbox_session) and the usage guidance is injected into their
system prompt — the agent's own instructions need not mention the sandbox.
Ask: Which provider — local_subprocess (no isolation; dev/test only), docker
(container isolation; needs the sandbox-docker extra and a Docker daemon), or another
shipped provider (kubernetes pods, e2b micro-VMs, daytona cloud containers, ec2_ssm
attach-only; see the Sandbox guide)? Should
it apply to all agents or only some (the agents list)? For executions longer than the
process can wait, the queue broker flavor runs them on a separate worker
(sandbox.broker.flavor: queue; same guide).
1. Install the extra (docker only):
pip install "agentkernel[sandbox-docker]"2. Add a sandbox block to config.yaml.
Minimal (single-backend sugar synthesizes a default profile):
sandbox:
enabled: true
type: local_subprocess # or: docker
local_subprocess: {} # or a docker: { image: python:3.12-slim } block
broker:
flavor: thread # thread (CLI/REST default) | embeddedWith explicit profiles, policy, and scoping:
sandbox:
enabled: true
agents: [coder] # optional: only these agents get the sandbox (omit = all)
default_profile: workspace
tool_output_max_chars: 8000
profiles:
workspace:
type: docker
scope: per_session # per_call | per_session | per_runtime
environment: managed # managed (default) | attached (connect to an existing environment; needs attach_to)
idle_timeout: 1800
policy:
network_egress: deny # allow | deny | allowlist
cpu: 1.0
memory_mb: 512
timeout: 30.0
strict: true # fail closed when the provider can't enforce a dimension
docker:
image: python:3.12-slim
broker:
flavor: thread3. No agent code changes needed — the tools and prompt guidance attach automatically. Keep the agent's instructions about what to do; the sandbox usage is injected.
:::caution
local_subprocess runs code directly on the host with no isolation — dev/test only. Use
docker (or another isolating provider) in production.
:::
For per-user identity (running sandboxed code under the invoking user's identity), set
principal_resolver to a dotted path and a profile's identity.mode: user; see the
Sandbox guide and the
examples/sandbox/identity example.
What it does: SecretManager.current().get("OPENAI_API_KEY") resolves an
environment-variable-style key (^[A-Z][A-Z0-9_]*$) in a fixed order: the process environment, then a
TTL'd process cache, then the configured provider (secret.provider.type). A set, non-empty
environment variable always wins; an empty one counts as absent. A resolved value is never written
back to os.environ — hand it to the SDK explicitly. The capability is always on (no enabled
flag); the default env provider costs nothing.
Ask: Which provider — env (default; the process environment), aws_ssm (AWS SSM Parameter
Store; needs the aws extra and secret.prefix), or a dotted path to your own SecretProvider
subclass?
1. Install the extra (aws_ssm only):
pip install "agentkernel[aws]"2. Add a secret block to config.yaml (optional for env, which is the default):
secret:
provider:
type: aws_ssm # env (default) | aws_ssm | my_pkg.secrets.MyProvider
prefix: myproduct-dev-agents # required by aws_ssm; AWS Terraform injects AK_SECRET__PREFIX when ssm_enabled = true
cache_ttl: 300 # seconds a provider hit is cached; 0 disables caching (rotation-pickup window)With aws_ssm, the key OPENAI_API_KEY is read from the SSM parameter
/ak/{prefix}/openai_api_key (key lowercased, GetParameter with decryption). prefix must be a single
path segment. Create the parameters yourself as SecureString.
3. Resolve secrets in agent code instead of reading os.environ:
from agentkernel.secret import SecretManager
from agents import set_default_openai_key
# Required secret: a miss raises SecretNotFoundError at startup, not on the first model call
set_default_openai_key(SecretManager.current().get("OPENAI_API_KEY"))
def get_weather(city: str) -> str:
# Optional secret: default=None turns a miss into None instead of raising
api_key = SecretManager.current().get("WEATHER_API_KEY", default=None)
...Errors: SecretNotFoundError (no layer has the key and no default), SecretError (the provider
failed — credentials, network, IAM; never masked by default), ValueError (malformed key). Use
SecretManager.current().invalidate(key) / .clear() to drop cached values. A custom provider
subclasses agentkernel.secret.SecretProvider, implements get_secret(key) -> Optional[str]
(return None on a miss), and can be checked with the agentkernel.secret.testing.SecretProviderContract
pytest suite. See examples/cli/openai-secret (env) and examples/aws-serverless/openai (aws_ssm).
You've added new capabilities to your project. Here's what you might do next:
ak-build skill to add new tools and specialist agents that leverage your new capabilities (e.g., agents that use guardrails or hooks).ak-add-integration skill to add Slack, WhatsApp, Telegram, or other channels so users can interact with your enhanced agents.ak-cloud-deploy skill to deploy your agent (with all its capabilities) to AWS or Azure.ak-test skill to verify your capabilities work correctly — especially guardrails and hooks..agents/skills/ak-dev-new-knowledgebase-integration/SKILL.md to add new KnowledgeBase adapters.© yaalalabs, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file in ak-py/src/agentkernel/skills/ak-add-capabilities of yaalalabs/agent-kernel.
Open the folder on GitHubat commit 97fa8d9
Ak Add Capabilities next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Ak Add Capabilities this skillyaalalabs/agent-kernel | 192 | — | ~13k | Automated safety check: Pass | Apache-2.0 | |
| AWS Cost Operationszxkane/aws-skills | 367 | — | ~2.4k | Automated safety check: Pass | MIT | |
| Eks Cost Intelligenceaws-samples/appmod-blueprints | 115 | — | ~3.8k | Automated safety check: Warn | MIT-0 | |
| AWS Strands Agents Agentcoresammcj/agentic-coding | 162 | — | ~3k | Automated safety check: Pass | Apache-2.0 | |
| AWS Cost OperationsMicrock/ordinary-claude-skills | 404 | 1 repos | ~2.5k | Automated safety check: Pass | Custom licence | |
| Investigation Cost Guardrailaws/tools-for-devops-agent | 103 | — | ~4.5k | Automated safety check: Pass | Apache-2.0 |
zxkane/aws-skills
AWS cost optimization, monitoring, and operational excellence expert.
aws-samples/appmod-blueprints
Run a live EKS cluster cost efficiency assessment — analyze spending across 6 dimensions (compute efficiency, Spot/Graviton adoption, networking, storage, observability, idle resources), calculate a…
sammcj/agentic-coding
A skill your agent uses when working with AWS Strands Agents SDK or Amazon Bedrock AgentCore platform for building AI agents.
Microck/ordinary-claude-skills
This skill provides AWS cost optimization, monitoring, and operational best practices with integrated MCP servers for billing analysis, cost estimation, observability, and security assessment.
aws/tools-for-devops-agent
Cost guardrail for AWS DevOps Agent that covers ALL AWS services and native agent tools.
diegosouzapw/awesome-omni-skills
AWS Advisor workflow skill. An agent skill from diegosouzapw/awesome-omni-skills.
yaalalabs/agent-kernel
Code quality standards, formatting, Python style rules (classes over script-style functions, configuration-field rules), commit conventions, and PR workflow for Agent Kernel development.
yaalalabs/agent-kernel
Step-by-step guide for adding a new built-in test evaluator provider to Agent Kernel (beyond DeepEval, Opik and JEV).
yaalalabs/agent-kernel
Step-by-step guide for adding a new guardrail provider to Agent Kernel.
yaalalabs/agent-kernel
Step-by-step guide for adding a new knowledge base backend to Agent Kernel.
yaalalabs/agent-kernel
Step-by-step guide for adding a new messaging platform integration to Agent Kernel.
yaalalabs/agent-kernel
Step-by-step guide for adding a new multimodal attachment storage backend to Agent Kernel.
Works with
Add capabilities to an existing Agent Kernel project. An agent skill from yaalalabs/agent-kernel. Ak Add Capabilities is an agent skill from yaalalabs/agent-kernel. Add capabilities to an existing Agent Kernel project.
Ak Add Capabilities fits situations like: tasks that involve NoSQL databases; tasks that involve Observability; tasks that involve LLM guardrails.
Run `npx skills add yaalalabs/agent-kernel --skill ak-add-capabilities -a claude-code`. Or copy the skill folder (ak-py/src/agentkernel/skills/ak-add-capabilities in yaalalabs/agent-kernel) into .claude/skills/ak-add-capabilities in your project. Claude Code loads it when a task matches its description.
Run `npx skills add yaalalabs/agent-kernel --skill ak-add-capabilities -a codex`. Or copy the skill folder (ak-py/src/agentkernel/skills/ak-add-capabilities in yaalalabs/agent-kernel) into .agents/skills/ak-add-capabilities in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add yaalalabs/agent-kernel --skill ak-add-capabilities -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/ak-add-capabilities, .gemini/skills/ak-add-capabilities, .github/skills/ak-add-capabilities and .opencode/skills/ak-add-capabilities in your project.
Going by SKILL.md and its folder, Ak Add Capabilities needs the command-line tools its instructions call (curl and pip) and credentials named OPENAI_API_KEY, AZURE_COSMOS_KEY, WALLED_API_KEY and LANGFUSE_PUBLIC_KEY. Our summary lists: Python 3; A credential in WALLED_API_KEY; A credential in LANGFUSE_PUBLIC_KEY.
SKILL.md names 3 domains. In commands or code: cloud.langfuse.com; the agent is likely to contact it when it follows the instructions. As links in the text: kernel.yaala.ai and github.com. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Ak Add Capabilities is published under the Apache-2.0 licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.
About 13k tokens (SKILL.md is roughly 53k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Ak Add Capabilities: AWS Cost Operations (zxkane/aws-skills, 367 stars), Eks Cost Intelligence (aws-samples/appmod-blueprints, 115 stars), AWS Strands Agents Agentcore (sammcj/agentic-coding, 162 stars) and AWS Cost Operations (Microck/ordinary-claude-skills, 404 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
yaalalabs (a GitHub organization) maintains it in yaalalabs/agent-kernel, which has 192 GitHub stars. The repository holds 23 skills in this directory. The repository was last updated on October 9, 2026.
Source: yaalalabs/agent-kernel on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.