Agent skill

Ak Add Capabilities

by yaalalabs in yaalalabs/agent-kernel

Add capabilities to an existing Agent Kernel project. An agent skill from yaalalabs/agent-kernel.

Apache-2.0Auto-check passedDatabases

Install Ak Add Capabilities

skills CLI
$ npx skills add yaalalabs/agent-kernel --skill ak-add-capabilities -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install yaalalabs/agent-kernel ak-add-capabilities --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ 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-src

Use ~/.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/

Facts

Skill name
ak-add-capabilities
GitHub stars
192
Token cost
~13k tokens
SKILL.md length
3,977 words
Files
2
Skills in repo
23
Repo updated
First seen
Licence
Apache-2.0

At a glance

Add capabilities to an existing Agent Kernel project. An agent skill from yaalalabs/agent-kernel.

  • Works in 3 steps: Identify the Project → Ask Which Capability → Generate Changes
  • Tasks that involve NoSQL databases
  • Calls curl and pip; reaches cloud.langfuse.com; needs OPENAI_API_KEY and AZURE_COSMOS_KEY
  • Tasks that involve Observability

What it does

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.

When your agent uses it

  • Tasks that involve NoSQL databases
  • Tasks that involve Observability
  • Tasks that involve LLM guardrails

Example prompts

  • “/ak-add-capabilities”

Requirements

  • Python 3
  • A credential in WALLED_API_KEY
  • A credential in LANGFUSE_PUBLIC_KEY

Workflow steps

3 steps, taken from the step headings in SKILL.md.

  1. Identify the Project
  2. Ask Which Capability
  3. Generate Changes

What it can do on your machine

Read from SKILL.md and the folder at commit 97fa8d9. It shows what the files ask for, not the result of running them.

  • Tool permissions

    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.

  • Runs code

    Shell commands in SKILL.md call:

    • curl
    • pip

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • cloud.langfuse.com

    Also links to:

    • kernel.yaala.ai
    • github.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names these keys or tokens, usually read from environment variables:

    • OPENAI_API_KEY
    • AZURE_COSMOS_KEY
    • WALLED_API_KEY
    • LANGFUSE_PUBLIC_KEY
    • LANGFUSE_SECRET_KEY
    • LOGFIRE_TOKEN
    • NEO4J_PASSWORD
    • STARBURST_PASSWORD
    • WEATHER_API_KEY

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

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.

Always · name and description, kept in context so the agent knows when to use it
~187
When it runs · the whole SKILL.md, loaded when a task matches
~13k

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.

Safety

Auto-check passed

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.

SKILL.md

The full file from yaalalabs/agent-kernel at commit 97fa8d9, republished under its Apache-2.0 licence (© yaalalabs). 3,977 words, ~13,369 tokens.

Download SKILL.mdSave it as .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.
name
ak-add-capabilities
description
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 (AWS), Cosmos DB (Azure), and Firestore (GCP). Conversation threads support in-memory, Redis, Valkey, DynamoDB (AWS), Firestore (GCP), and Cosmos DB (Azure) backends. Generates configuration and code changes needed.
license
Apache-2.0
metadata.author
yaalalabs
metadata.version
0.9.5
metadata.category
user

Add Capabilities

Use this skill to enhance your Agent Kernel project with additional capabilities.

Instructions for the Agent

When the user wants to add a capability, follow this workflow:

Step 1: Identify the Project

Check for an existing Agent Kernel project with pyproject.toml and agent definition file.

Step 2: Ask Which Capability

Which capability would you like to add?

  1. Guardrails — Content safety filters for input and/or output
  2. Tracing — Observability and monitoring (Langfuse, OpenLLMetry, Pydantic Logfire, or AWS CloudWatch)
  3. Session Persistence — Durable conversation state (Redis, DynamoDB, Cosmos DB, Firestore)
  4. Knowledge Base — Durable cross-session knowledge tools (ChromaDB, Neo4j, Starburst, Open Knowledge Format markdown bundle, or custom backend)
  5. MCP Server — Expose agents as Model Context Protocol tools
  6. A2A Server — Agent-to-Agent communication protocol
  7. Hooks — Custom pre/post processing (RAG, logging, prompt modification)
  8. Multimodal — Image and file attachment support
  9. Conversation Threads — Persistent, named conversation history keyed by session_id
  10. Sandbox — Isolated code/command execution with pluggable providers, workload profiles, policy, and per-user identity
  11. AG-UI Server — Stream any agent to an AG-UI-compliant frontend (text, tool calls, reasoning, shared state)
  12. Scheduled Tasks — Deferred and recurring chat execution (a schedule block on a chat request, management routes, agent tools)
  13. Secret Resolution — Read API keys through SecretManager (environment first, then AWS SSM Parameter Store or a custom provider)
  14. Human in the Loop — Pause a run for a person's approval or answer and resume from their decision (a gated tool or an interrupt, a resume block on the next request)
Step 3: Generate Changes

Guardrails

Ask: Input guardrails, output guardrails, or both? Which provider — OpenAI, AWS Bedrock, or Walled AI?

For OpenAI Guardrails:

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api]>=0.9.5",
    # OpenAI guardrails use the openai extra — already included if using OpenAI framework
]
  1. Create guardrails_input.json:
json
[
  {
    "type": "moderation",
    "moderation_config": {
      "content_type": ["violence", "sexual", "harassment", "self-harm"],
      "threshold": 0.5
    }
  },
  {
    "type": "jailbreak",
    "jailbreak_config": {}
  }
]
  1. Create guardrails_output.json:
json
[
  {
    "type": "pii_detection",
    "pii_config": {
      "output_handling": "block",
      "entities": ["email_address", "phone_number", "ssn"]
    }
  }
]
  1. Update config.yaml:
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.json

For AWS Bedrock Guardrails:

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,aws]>=0.9.5",
]
  1. Update config.yaml:
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"
  1. Prerequisites: Create a guardrail in AWS Bedrock Console and note the guardrail ID.

For Walled AI Guardrails:

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,walledai]>=0.9.5",
]
  1. Update config.yaml:
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 output
  1. Set the environment variable:
bash
export WALLED_API_KEY="your-walledai-api-key"
  1. How it works:
    • Input: Text is checked for safety via 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.
    • Output: If pii: true, redacted placeholders in the agent's reply are replaced with the original values using the stored mapping.

Tracing (Observability)

Ask: Which tracing backend — Langfuse, OpenLLMetry (Traceloop), Pydantic Logfire, or AWS CloudWatch?

For Langfuse:

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,langfuse]>=0.9.5",
]
  1. Update config.yaml:
yaml
trace:
  enabled: true
  type: langfuse
  1. Set environment variables:
bash
export LANGFUSE_PUBLIC_KEY="pk-..."
export LANGFUSE_SECRET_KEY="sk-..."
export LANGFUSE_HOST="https://cloud.langfuse.com"   # or self-hosted URL
  1. No code changes needed — tracing is automatically applied to all agent executions.

For OpenLLMetry (Traceloop):

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,openllmetry]>=0.9.5",
]
  1. Update config.yaml:
yaml
trace:
  enabled: true
  type: openllmetry
  1. Set environment variables per the Traceloop documentation.

For Pydantic Logfire:

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,logfire]>=0.9.5",
]
  1. Update config.yaml:
yaml
trace:
  enabled: true
  type: logfire
  1. Set the write token (optional — without it, Logfire runs locally and does not ship traces):
bash
export LOGFIRE_TOKEN="your-write-token"
  1. No code changes needed — tracing is automatically applied to all agent executions.

For AWS CloudWatch:

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,cloudwatch]>=0.9.5",
]
  1. Update config.yaml:
yaml
trace:
  enabled: true
  type: cloudwatch
  1. Set the region; credentials come from the standard AWS chain (env, profile, SSO, or instance/task role):
bash
export 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"
  1. One-time AWS setup: enable CloudWatch Transaction Search in the account, and attach the AWSXrayWriteOnlyAccess managed policy to the role that runs the agent.

  2. No code changes needed — tracing is automatically applied to all agent executions.


Session Persistence

Ask: Which backend — Redis, DynamoDB (AWS), Cosmos DB (Azure), or Firestore (GCP)?

For Redis:

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,redis]>=0.9.5",
]
  1. Update config.yaml:
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:

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,aws]>=0.9.5",
]
  1. Update config.yaml:
yaml
session:
  type: dynamodb
  cache: 256
  dynamodb:
    table_name: "<your-table-name>"
    region: "us-east-1"
    ttl: 3600
  1. Create a DynamoDB table with partition key session_id (String) and sort key key (String). Enable TTL on expiry_time attribute.

For Cosmos DB:

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,azure]>=0.9.5",
]
  1. Update config.yaml:
yaml
session:
  type: cosmosdb
  cache: 256
  cosmosdb:
    endpoint: "https://<account>.table.cosmos.azure.com:443/"
    table_name: "<your-table-name>"
    ttl: 3600
  1. Set AZURE_COSMOS_KEY environment variable.

For Firestore (GCP):

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,gcp]>=0.9.5",
]
  1. Update config.yaml:
yaml
session:
  type: firestore
  cache: 256
  firestore:
    collection_name: "ak_sessions"
    project_id: "<your-gcp-project-id>"   # optional, inferred from ADC if omitted
    ttl: 604800
  1. Enable a TTL policy on the Firestore collection pointing to the expiry_time field for automatic document expiry.

Knowledge Base

Add durable knowledge tools that your agents can query and update across sessions.

Ask: Which backend do you want to use?

  • ChromaDB (semantic/vector)
  • Neo4j (graph/relationships)
  • Starburst (read-only SQL via Trino)
  • Open Knowledge Format bundle (a directory of markdown documents, on disk or in S3 — no database to run)
  • Custom adapter (developer extension)

1. Update pyproject.toml dependencies based on backend:

toml
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):

python
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):

python
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:

python
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):

bash
# 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=443

An 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:

  • Add a custom backend by implementing KnowledgeBase under ak-py/src/agentkernel/knowledgebase/.
  • Use developer skill .agents/skills/ak-dev-new-knowledgebase-integration/SKILL.md for contributor workflows.

MCP Server

Expose your agents as MCP (Model Context Protocol) tools so other AI systems can discover and call them.

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,mcp]>=0.9.5",
]
  1. Update config.yaml:
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 server
  1. No code changes needed. The MCP server endpoint is automatically available.

A2A Server

Enable Agent-to-Agent communication via Google's A2A protocol.

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,a2a]>=0.9.5",
]
  1. Update config.yaml:
yaml
a2a:
  enabled: true
  agents:
    - general         # List agents to expose via A2A
  url: "http://localhost:8000"
  1. No code changes needed. The A2A well-known endpoint is automatically available at /.well-known/agent.json.

AG-UI Server

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.

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,agui]>=0.9.5",
]
  1. Update config.yaml:
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_context
  1. Mount AGUIRequestHandler with an Authoriser (or AuthValidator) — AG-UI has no anonymous mode, because a run executes an agent on the caller's behalf:
python
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())])
  1. Routes are served under 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.

Custom Hooks

Add custom pre/post processing to your agents.

Pre-hook example (RAG injection):

python
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):

python
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:

python
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.

python
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.

python
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())
Frameworkrun_options keywords go toTurn limitProgress hookReserved (raise at declaration)
OpenAI Agents SDKRunner.run / run_streamedmax_turnshooks=RunHooks()starting_agent, input, session, context, conversation_id, previous_response_id, auto_previous_response_id
LangGraphainvoke / 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 ADKthe 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 AIagent.run / run_stream_events (event_stream_handler is dropped in stream mode with one warning)UsageLimits(request_limit=...)event_stream_handleruser_prompt, message_history, deps
CrewAIthe 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_callbackagents, tasks, memory
smolagentsagent.runmax_stepsstep_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.

python
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 event

To 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:

python
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:

python
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.


Multimodal Support

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):

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,multimodal]>=0.9.5",
]
  1. Update config.yaml:
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 tool
  1. No further code changes needed. When enabled:
    • A system tool (analyze_attachments) is automatically attached to all agents
    • Image/file attachments in requests are processed, described by a vision LLM, and stored in a separate in-memory attachment store (outside the conversation history/session state, non-persistent, in-process)
    • Binary data is kept out of conversation history to prevent memory bloat
    • Agents see attachment IDs and descriptions in their context
    • Agents can call analyze_attachments(attachment_ids, prompt) for detailed analysis

For Redis storage (production, persistent, distributed):

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,redis,multimodal]>=0.9.5",
]
  1. Update config.yaml:
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):

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,aws,multimodal]>=0.9.5",
]
  1. Update config.yaml:
yaml
multimodal:
  enabled: true
  storage_type: dynamodb
  max_attachments: 20
  description_model: "gpt-4o"
  analysis_model: "gpt-4o"
  dynamodb:
    table_name: "ak-attachments"
    ttl: 604800
  1. Create a DynamoDB table with partition key session_id (String) and sort key attachment_id (String). Enable TTL on expiry_time attribute.

Storage type comparison:

TypePersistenceMulti-processSetupBest for
in_memoryLost on restartSingle processNoneDev/testing
redisPersistentDistributedRedis serverProduction
dynamodbPersistentDistributedAWS tableServerless/Lambda

How it works:

  • When a user sends an image or file, a vision LLM generates a brief one-sentence description
  • The binary data is saved to the configured storage backend (not in conversation history)
  • The agent receives the text prompt enriched with attachment IDs and descriptions
  • When the agent needs to inspect an attachment in detail, it calls the analyze_attachments tool
  • The tool retrieves the binary from storage, sends it to the analysis LLM, and returns clean text

Send multimodal requests via API:

bash
# 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):

bash
# 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"

Conversation Thread Support

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):

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api]>=0.9.5",
]
  1. Mount the thread handler in the app; this is what enables the feature (it serves the standard chat routes with thread recording, plus the thread read routes):
python
from agentkernel.api import RESTAPI
from agentkernel.thread import AgentThreadRequestHandler

RESTAPI.run(handlers=[AgentThreadRequestHandler()])
  1. Update config.yaml (selects the store backend; constructing the handler without this block fails fast at startup):
yaml
thread:
  type: in_memory  # other supported backends: redis | valkey | dynamodb | firestore | cosmosdb
  1. When enabled:
    • user_id becomes required on the thread handler's chat requests (other surfaces are unaffected)
    • A thread is auto-created on a session's first request
    • 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)
    • Threads are auto-named by an LLM call deriving a concise title from the first prompt (falls back to a truncated prompt prefix without litellm/an API key)
    • Sending thread_name on any chat request sets/renames the thread and locks it against automatic naming

For LLM-based thread naming, add the thread extra:

toml
dependencies = [
    "agentkernel[openai,api,thread]>=0.9.5",
]
yaml
thread:
  type: in_memory
  naming:
    model: "gpt-4o-mini"   # LiteLLM model used to name threads
    max_length: 80

For Redis storage (production, persistent, distributed):

toml
dependencies = [
    "agentkernel[openai,api,redis,thread]>=0.9.5",
]
yaml
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):

toml
dependencies = [
    "agentkernel[openai,api,valkey,thread]>=0.9.5",
]
yaml
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):

toml
dependencies = [
    "agentkernel[openai,api,aws,thread]>=0.9.5",
]
yaml
thread:
  type: dynamodb
  dynamodb:
    table_name: "ak-agent-threads"   # partition key session_id (S), sort key sk (S)
    ttl: 0

For Firestore storage (serverless/GCP):

yaml
thread:
  type: firestore
  firestore:
    collection_name: "ak-agent-threads"
    ttl: 0

For Cosmos DB storage (Azure, Table API):

yaml
thread:
  type: cosmosdb
  cosmosdb:
    connection_string: "${AZURE_COSMOS_CONNECTION_STRING}"
    table_name: "akagentthreads"

Protecting the read endpoints with an Authoriser:

python
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:

bash
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.


Show full SKILL.md (1,467 more words)Show less
Scheduled Tasks

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):

  1. Update pyproject.toml:
toml
dependencies = [
    "agentkernel[openai,api,cron]>=0.9.5",
]

The cron extra brings croniter, needed for cron parsing.

  1. Update 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:
yaml
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 queue
  1. Mount the management routes in app.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:
python
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()])
  1. When enabled:
    • A JSON chat request may carry a 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 against
    • GET /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 tool
    • PUT 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 trail
    • The five schedule tools and their guidance are injected into every agent's system prompt; each acts as the invoking user, so an agent can never reach another user's schedules
    • A scheduled request creates no conversation thread; the occurrences that later fire do
    • Multipart chat routes cannot carry a schedule block — use the JSON route

Send a chat request with a schedule:

bash
# 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):

toml
dependencies = [
    "agentkernel[openai,api,aws,cron]>=0.9.5",
]
yaml
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:

toml
dependencies = [
    "agentkernel[openai,api,redis,cron]>=0.9.5",   # or valkey
]
yaml
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 silently

Topology rules (validated at startup, not at first use):

CombinationRejected 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 storeSame split: the timers and the records must live together
in_memory store + a broker transportThe records would be split across the runner and IO-handler processes
eventbridge provider + a non-sqs transportDelivery 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:

python
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.


Human in the Loop

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:

python
# 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:

bash
# 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.

Sandbox

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):

bash
pip install "agentkernel[sandbox-docker]"

2. Add a sandbox block to config.yaml.

Minimal (single-backend sugar synthesizes a default profile):

yaml
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) | embedded

With explicit profiles, policy, and scoping:

yaml
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: thread

3. 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.


Secret Resolution

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):

bash
pip install "agentkernel[aws]"

2. Add a secret block to config.yaml (optional for env, which is the default):

yaml
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:

python
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).


What to Do Next

You've added new capabilities to your project. Here's what you might do next:

  • Add more tools & agents → Use the ak-build skill to add new tools and specialist agents that leverage your new capabilities (e.g., agents that use guardrails or hooks).
  • Connect a messaging platform → Use the ak-add-integration skill to add Slack, WhatsApp, Telegram, or other channels so users can interact with your enhanced agents.
  • Deploy to cloud → Use the ak-cloud-deploy skill to deploy your agent (with all its capabilities) to AWS or Azure.
  • Set up testing → Use the ak-test skill to verify your capabilities work correctly — especially guardrails and hooks.
  • Extend knowledge backends → Contributors can use .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

Files

SKILL.md and 1 other file in ak-py/src/agentkernel/skills/ak-add-capabilities of yaalalabs/agent-kernel.

  • SKILL.md
  • evals/evals.json

Open the folder on GitHubat commit 97fa8d9

Compare with similar skills

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.

Ak Add Capabilities compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Ak Add Capabilities this skillyaalalabs/agent-kernel192—~13kAutomated safety check: PassApache-2.0
AWS Cost Operationszxkane/aws-skills367—~2.4kAutomated safety check: PassMIT
Eks Cost Intelligenceaws-samples/appmod-blueprints115—~3.8kAutomated safety check: WarnMIT-0
AWS Strands Agents Agentcoresammcj/agentic-coding162—~3kAutomated safety check: PassApache-2.0
AWS Cost OperationsMicrock/ordinary-claude-skills4041 repos~2.5kAutomated safety check: PassCustom licence
Investigation Cost Guardrailaws/tools-for-devops-agent103—~4.5kAutomated safety check: PassApache-2.0

Similar skills

  • AWS Cost Operations

    zxkane/aws-skills

    AWS cost optimization, monitoring, and operational excellence expert.

    367 GitHub stars~2.4k tokensUpdated 3 mo ago
    DevOps & CloudAuto-check passed
  • Eks Cost Intelligence

    aws-samples/appmod-blueprints

    Official

    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…

    115 GitHub stars~3.8k tokensUpdated 2 days ago
    DevOps & CloudAuto-check: warnings
  • AWS Strands Agents Agentcore

    sammcj/agentic-coding

    A skill your agent uses when working with AWS Strands Agents SDK or Amazon Bedrock AgentCore platform for building AI agents.

    162 GitHub stars~3k tokensUpdated 2 days ago
    DevOps & CloudAuto-check passed
  • AWS Cost Operations

    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.

    404 GitHub starsUsed in 1 repo~2.5k tokens
    DevOps & CloudAuto-check passed
  • Investigation Cost Guardrail

    aws/tools-for-devops-agent

    Official

    Cost guardrail for AWS DevOps Agent that covers ALL AWS services and native agent tools.

    103 GitHub stars~4.5k tokensUpdated yesterday
    AI & LLM EngineeringAuto-check passed
  • AWS Advisor

    diegosouzapw/awesome-omni-skills

    AWS Advisor workflow skill. An agent skill from diegosouzapw/awesome-omni-skills.

    159 GitHub stars~4.3k tokensUpdated 3 mo ago
    DevOps & CloudAuto-check passed

More from yaalalabs/agent-kernel

All 23 skills in this repo
  • Ak Dev Code Quality

    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.

    192 GitHub stars~2.5k tokensUpdated yesterday
    Auto-check passed
  • Ak Dev New Evaluator Provider

    yaalalabs/agent-kernel

    Step-by-step guide for adding a new built-in test evaluator provider to Agent Kernel (beyond DeepEval, Opik and JEV).

    192 GitHub stars~3.4k tokensUpdated yesterday
    Auto-check passed
  • Ak Dev New Guardrail Provider

    yaalalabs/agent-kernel

    Step-by-step guide for adding a new guardrail provider to Agent Kernel.

    192 GitHub stars~3.5k tokensUpdated yesterday
    Auto-check passed
  • Step-by-step guide for adding a new knowledge base backend to Agent Kernel.

    192 GitHub stars~5.1k tokensUpdated yesterday
    Auto-check passed
  • Ak Dev New Messaging Integration

    yaalalabs/agent-kernel

    Step-by-step guide for adding a new messaging platform integration to Agent Kernel.

    192 GitHub stars~4.6k tokensUpdated yesterday
    Auto-check passed
  • Ak Dev New Multimodal Storage

    yaalalabs/agent-kernel

    Step-by-step guide for adding a new multimodal attachment storage backend to Agent Kernel.

    192 GitHub stars~4.3k tokensUpdated yesterday
    Auto-check passed

Questions about Ak Add Capabilities

What does Ak Add Capabilities do?

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.

When should I use Ak Add Capabilities?

Ak Add Capabilities fits situations like: tasks that involve NoSQL databases; tasks that involve Observability; tasks that involve LLM guardrails.

How do I install Ak Add Capabilities in Claude Code?

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.

How do I install Ak Add Capabilities in Codex?

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.

Can I use Ak Add Capabilities in Cursor, Gemini CLI or GitHub Copilot?

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.

What does Ak Add Capabilities need to run?

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.

Does Ak Add Capabilities access the network?

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.

Is Ak Add Capabilities safe to install?

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.

What licence does Ak Add Capabilities use?

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.

How many tokens does Ak Add Capabilities use?

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.

What are the alternatives to Ak Add Capabilities?

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.

Who maintains Ak Add Capabilities?

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.