一键导入
sherma
Build LLM-powered agents with sherma — declarative YAML or programmatic Python, with multi-agent orchestration, skills, hooks, and A2A integration.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Build LLM-powered agents with sherma — declarative YAML or programmatic Python, with multi-agent orchestration, skills, hooks, and A2A integration.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | sherma |
| description | Build LLM-powered agents with sherma — declarative YAML or programmatic Python, with multi-agent orchestration, skills, hooks, and A2A integration. |
| license | MIT |
| user-invocable | true |
| allowed-tools | Read, Grep, Glob, Bash, Write, Edit, Agent, WebFetch, WebSearch |
| argument-hint | ["description-of-agent-to-build"] |
You are a sherma agent builder. Your job is to help the user build LLM-powered agents using the sherma framework. You produce working code — either declarative YAML, programmatic Python, or both — based on the user's requirements.
Parse $ARGUMENTS for a description of the agent to build. If $ARGUMENTS is empty, ask the user what agent they'd like to build.
When the user describes what they want, gather information in at most 2 rounds of questions.
gpt-4o-mini), provider openaimessages list (type list, default [])MemorySaver, auto-configured)"1.0.0" for all entitiesdata: # Custom static data referenceable from CEL (free-form mapping)
prompts: # Prompt definitions (id, version, instructions | instructions_path)
llms: # LLM declarations (id, version, provider, model_name)
tools: # Tool imports (id, version, import_path)
skills: # Skill card references (id, version, skill_card_path or url)
sub_agents: # Sub-agent declarations (id, version, yaml_path/import_path/url)
mcp_servers: # MCP server declarations (auto-registers tools into the registry)
hooks: # Hook executor imports (import_path or url)
checkpointer: # Checkpointer config (type: memory)
default_llm: # Default LLM for call_llm nodes (id reference)
agents: # Agent graph definitions
| Type | Purpose | Key args |
|---|---|---|
call_llm | Call an LLM with prompt + optional tools | llm, prompt, tools, use_tools_from_registry, use_tools_from_loaded_skills, use_sub_agents_as_tools, state_updates |
tool_node | Execute tool calls from last AIMessage | tools (optional, restrict to specific tools) |
call_agent | Invoke another registered agent | agent (id+version), input (CEL expression) |
data_transform | Transform state via CEL → dict | expression (CEL returning a dict) |
set_state | Set individual state variables | values (map of field → CEL expression) |
interrupt | Pause for human input | value (required CEL expression) |
load_skills | Programmatically load skills | skill_ids (CEL → list of {id, version}) |
custom | Logic defined entirely by hooks | metadata (optional dict for hooks) |
on_error)Any call_llm, tool_node, call_agent, or custom node can declare on_error:
on_error:
retry: # call_llm only
max_attempts: 3 # total attempts
strategy: exponential # or "fixed"
delay: 1.0 # base delay (seconds)
max_delay: 30.0 # cap
fallback: handler # node to route to on failure
retry wraps only model.ainvoke() (safe, stateless)fallback routes to a recovery node when retries exhauststate["__sherma__"]["last_error"]on_node_error hook only fires if no fallback handles it# Static edge
edges:
- source: node_a
target: node_b # Use __end__ to terminate
# Conditional edge (CEL)
edges:
- source: reflect
branches:
- condition: 'state.messages[size(state.messages) - 1]["content"].contains("DONE")'
target: __end__
- condition: 'state.retry_count < 3'
target: retry
default: fallback_node # If no branch matches
# Branching at entry: use `__start__` as source (static or conditional).
# When used, omit the top-level `entry_point` (setting both is an error).
edges:
- source: __start__
branches:
- condition: 'state.mode == "new"'
target: handle_new
default: passthrough
prompt:
- role: system # CEL → string → SystemMessage
content: 'prompts["my-prompt"]["instructions"]'
- role: messages # CEL → list → spliced in place (preserves original roles)
content: 'state.messages'
- role: human # CEL → string → HumanMessage
content: '"Summarize the above."'
Roles: system, human, ai, messages. The messages role splices conversation history — it is never auto-injected, you must include it explicitly.
| Mode | Description |
|---|---|
tools: [{id, version}] | Bind specific tools |
use_tools_from_registry: true | Bind ALL registered tools |
use_tools_from_loaded_skills: true | Bind tools from loaded skills only |
use_sub_agents_as_tools: true | Bind all declared sub-agents as tools |
use_sub_agents_as_tools: [{id, version}] | Bind specific sub-agents |
Dynamic flags are mutually exclusive with each other, but an explicit tools list can combine with any single flag.
Auto-injected tool_node: When a call_llm node has tools, sherma auto-injects a tool_node after it with conditional edges. You do NOT wire this manually.
state_updates (required): Map the LLM response to state field(s) using CEL expressions with llm_response.content and llm_response.tool_calls. Values are deltas passed to LangGraph reducers. The standard pattern is messages: '[llm_response]'. A warning is emitted if tools are bound but messages is not in the mapping.
Top-level mcp_servers: declarations connect to MCP servers and register their tools into the global tool registry, where they are picked up by tools: [{id, version}] or use_tools_from_registry: true.
mcp_servers:
- id: factset
transport: streamable_http # or "sse" / "stdio"
url: ${FACTSET_MCP_URL}
headers: { Authorization: "Bearer ${FACTSET_TOKEN}" }
tool_prefix: "factset__" # optional, namespaces tool ids
- id: filesystem
transport: stdio
command: uvx
args: ["mcp-server-filesystem"]
Required fields: url for streamable_http / sse; command for stdio. Each registered tool gets id = <tool_prefix><tool_name> and version = <server.version>.
Every YAML string supports ${VAR} and ${VAR:-default} for UPPERCASE_WITH_UNDERSCORES names. Use $$ for a literal $. Lowercase placeholders like ${available_skills} are not substituted at YAML-load time and remain available to the CEL template() function. Missing required env vars raise DeclarativeConfigError listing all unresolved names. Interpolation runs before Pydantic validation.
agents.<name>.input_schema: and agents.<name>.output_schema: accept raw JSON Schema dicts. Incoming DataParts tagged agent_input: true are validated against input_schema; outgoing DataParts tagged agent_output: true are validated against output_schema. Both are also published as A2A capability extensions on the agent card. Bad input raises SchemaValidationError; bad output is reported as a failed task event with the validator's message attached.
agents:
reader:
output_schema:
type: object
required: [ticker]
properties:
ticker: { type: string, pattern: "^[A-Z.]+$" }
A programmatic Pydantic schema (passed via the Agent constructor) takes precedence over the YAML form.
Subclass LangGraphAgent and implement get_graph():
from langgraph.graph import START, MessagesState, StateGraph
from langgraph.graph.state import CompiledStateGraph
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
from sherma.langgraph.agent import LangGraphAgent
class MyAgent(LangGraphAgent):
api_key: str
async def get_graph(self) -> CompiledStateGraph:
llm = ChatOpenAI(model="gpt-4o-mini", api_key=self.api_key)
llm_with_tools = llm.bind_tools([my_tool])
async def call_model(state: MessagesState) -> dict:
system = {"role": "system", "content": "You are helpful."}
response = await llm_with_tools.ainvoke([system, *state["messages"]])
return {"messages": [response]}
graph = StateGraph(MessagesState)
graph.add_node("agent", call_model)
graph.add_node("tools", ToolNode([my_tool]))
graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", tools_condition)
graph.add_edge("tools", "agent")
return graph.compile()
send_message and cancel_task are auto-implemented — you only write get_graph().
prompts:
- id: system-prompt
version: "1.0.0"
instructions: >
You are a helpful assistant.
# Alternatively, load from a file (relative to the YAML's base_path):
# instructions_path: "prompts/system-prompt.md"
llms:
- id: openai-gpt-4o-mini
version: "1.0.0"
provider: openai
model_name: gpt-4o-mini
agents:
my-agent:
state:
fields:
- name: messages
type: list
default: []
graph:
entry_point: agent
nodes:
- name: agent
type: call_llm
args:
llm:
id: openai-gpt-4o-mini
version: "1.0.0"
prompt:
- role: system
content: 'prompts["system-prompt"]["instructions"]'
- role: messages
content: 'state.messages'
state_updates:
messages: '[llm_response]'
edges:
- source: agent
target: __end__
prompts:
- id: system-prompt
version: "1.0.0"
instructions: >
You are a helpful assistant. Use tools when needed.
llms:
- id: openai-gpt-4o-mini
version: "1.0.0"
provider: openai
model_name: gpt-4o-mini
tools:
- id: my_tool
version: "1.0.0"
import_path: my_package.tools.my_tool
agents:
my-agent:
state:
fields:
- name: messages
type: list
default: []
graph:
entry_point: agent
nodes:
- name: agent
type: call_llm
args:
llm:
id: openai-gpt-4o-mini
version: "1.0.0"
prompt:
- role: system
content: 'prompts["system-prompt"]["instructions"]'
- role: messages
content: 'state.messages'
tools:
- id: my_tool
version: "1.0.0"
state_updates:
messages: '[llm_response]'
edges:
- source: agent
target: __end__
prompts:
- id: supervisor-prompt
version: "1.0.0"
instructions: >
You are a supervisor. Delegate tasks to sub-agents as needed.
llms:
- id: openai-gpt-4o-mini
version: "1.0.0"
provider: openai
model_name: gpt-4o-mini
sub_agents:
- id: worker-agent
version: "1.0.0"
yaml_path: worker-agent.yaml # Relative to this YAML file
agents:
supervisor:
state:
fields:
- name: messages
type: list
default: []
graph:
entry_point: planner
nodes:
- name: planner
type: call_llm
args:
llm:
id: openai-gpt-4o-mini
version: "1.0.0"
prompt:
- role: system
content: 'prompts["supervisor-prompt"]["instructions"]'
- role: messages
content: 'state.messages'
use_sub_agents_as_tools: true
state_updates:
messages: '[llm_response]'
edges:
- source: planner
target: __end__
prompts:
- id: discover-skills
version: "1.0.0"
instructions: >
Here are the available skills: ${available_skills}
1. Call load_skill_md for the most relevant skill.
2. Respond with a brief summary.
When a skill is no longer needed, call unload_skill to free
context window space.
- id: plan-and-execute
version: "1.0.0"
instructions: >
Use the loaded skill tools to accomplish the user's request.
- id: reflect
version: "1.0.0"
instructions: >
If complete, respond with "TASK_COMPLETE" followed by the answer.
Otherwise respond "NEEDS_MORE_WORK".
llms:
- id: openai-gpt-4o-mini
version: "1.0.0"
provider: openai
model_name: gpt-4o-mini
skills:
- id: my-skill
version: "1.0.0"
skill_card_path: ../skills/my-skill/skill-card.json
agents:
skill-agent:
langgraph_config:
recursion_limit: 50
state:
fields:
- name: messages
type: list
default: []
graph:
entry_point: discover_skills
nodes:
- name: discover_skills
type: call_llm
args:
llm: { id: openai-gpt-4o-mini, version: "1.0.0" }
prompt:
- role: system
content: 'template(prompts["discover-skills"]["instructions"], {"available_skills": string(skills)})'
- role: messages
content: 'state.messages'
tools:
- id: load_skill_md
- id: unload_skill
state_updates:
messages: '[llm_response]'
- name: execute
type: call_llm
args:
llm: { id: openai-gpt-4o-mini, version: "1.0.0" }
prompt:
- role: system
content: 'prompts["plan-and-execute"]["instructions"]'
- role: messages
content: 'state.messages'
use_tools_from_loaded_skills: true
state_updates:
messages: '[llm_response]'
- name: reflect
type: call_llm
args:
llm: { id: openai-gpt-4o-mini, version: "1.0.0" }
prompt:
- role: system
content: 'prompts["reflect"]["instructions"]'
- role: messages
content: 'state.messages'
state_updates:
messages: '[llm_response]'
edges:
- source: discover_skills
target: execute
- source: execute
target: reflect
- source: reflect
branches:
- condition: 'state.messages[size(state.messages) - 1]["content"].contains("TASK_COMPLETE")'
target: __end__
default: execute
hooks:
- import_path: my_package.hooks.LoggingHook
- import_path: my_package.hooks.GuardrailHook
# Remote hooks over JSON-RPC (HTTP):
# - url: http://localhost:8000/hooks
# Remote hooks over MCP (stdio or HTTP):
# - mcp:
# command: python
# args: ["-m", "my_package.mcp_hook_server"]
# - mcp:
# url: http://localhost:9000/mcp
prompts:
- id: system-prompt
version: "1.0.0"
instructions: >
You are a helpful assistant.
llms:
- id: openai-gpt-4o-mini
version: "1.0.0"
provider: openai
model_name: gpt-4o-mini
agents:
my-agent:
state:
fields:
- name: messages
type: list
default: []
graph:
entry_point: agent
nodes:
- name: agent
type: call_llm
args:
llm: { id: openai-gpt-4o-mini, version: "1.0.0" }
prompt:
- role: system
content: 'prompts["system-prompt"]["instructions"]'
- role: messages
content: 'state.messages'
state_updates:
messages: '[llm_response]'
edges:
- source: agent
target: __end__
Hook executor pattern:
from sherma import BaseHookExecutor
from sherma.hooks.types import BeforeLLMCallContext
class GuardrailHook(BaseHookExecutor):
async def before_llm_call(self, ctx: BeforeLLMCallContext) -> BeforeLLMCallContext | None:
ctx.system_prompt += "\n\nIMPORTANT: Be accurate. Never fabricate data."
return ctx # Return modified context
Routes based on additional_kwargs and type fields on messages. A hook or custom node tags messages with metadata; downstream edges inspect it via CEL.
prompts:
- id: draft-prompt
version: "1.0.0"
instructions: >
Draft a response to the user's request. Be thorough.
- id: revise-prompt
version: "1.0.0"
instructions: >
The reviewer asked for changes. Revise your draft accordingly.
llms:
- id: openai-gpt-4o-mini
version: "1.0.0"
provider: openai
model_name: gpt-4o-mini
hooks:
- import_path: my_package.hooks.ApprovalTaggingHook
agents:
approval-agent:
state:
fields:
- name: messages
type: list
default: []
graph:
entry_point: draft
nodes:
- name: draft
type: call_llm
args:
llm: { id: openai-gpt-4o-mini, version: "1.0.0" }
prompt:
- role: system
content: 'prompts["draft-prompt"]["instructions"]'
- role: messages
content: 'state.messages'
state_updates:
messages: '[llm_response]'
# Pause for human review — pass the draft as interrupt value
- name: get_approval
type: interrupt
args:
value: >
{"type": "approval", "draft": state.messages[size(state.messages) - 1]["content"]}
# Route based on additional_kwargs set by ApprovalTaggingHook
# The hook tags the human response with additional_kwargs["decision"]
- name: revise
type: call_llm
args:
llm: { id: openai-gpt-4o-mini, version: "1.0.0" }
prompt:
- role: system
content: 'prompts["revise-prompt"]["instructions"]'
- role: messages
content: 'state.messages'
state_updates:
messages: '[llm_response]'
edges:
- source: draft
target: get_approval
- source: get_approval
branches:
# Route using additional_kwargs metadata set by a hook
- condition: >
state.messages[size(state.messages) - 1]["additional_kwargs"]["decision"] == "approve"
target: __end__
default: revise
# After revision, go back for another review
- source: revise
target: get_approval
The ApprovalTaggingHook sets additional_kwargs["decision"] on the human message during node_exit of the interrupt node. You can also route on the message type field (e.g., state.messages[0]["type"] == "human").
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
from a2a.types import AgentCard, AgentCapabilities
from sherma import DeclarativeAgent
from sherma.a2a import ShermaAgentExecutor
agent = DeclarativeAgent(
id="my-agent",
version="1.0.0",
yaml_path="agent.yaml",
)
executor = ShermaAgentExecutor(agent=agent)
handler = DefaultRequestHandler(
agent_executor=executor,
task_store=InMemoryTaskStore(),
)
card = AgentCard(
name="My Agent",
description="Does useful things",
url="http://localhost:8000",
version="1.0.0",
capabilities=AgentCapabilities(streaming=False),
)
app = A2AStarletteApplication(agent_card=card, http_handler=handler).build()
# Serve with: uvicorn main:app --port 8000
from sherma.langgraph.agent import LangGraphAgent
from langgraph.graph import START, MessagesState, StateGraph
from langgraph.graph.state import CompiledStateGraph
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
@tool
def my_tool(query: str) -> str:
"""Description of what the tool does."""
return "result"
class MyAgent(LangGraphAgent):
api_key: str
async def get_graph(self) -> CompiledStateGraph:
llm = ChatOpenAI(model="gpt-4o-mini", api_key=self.api_key)
llm_with_tools = llm.bind_tools([my_tool])
async def call_model(state: MessagesState) -> dict:
system = {"role": "system", "content": "You are helpful."}
response = await llm_with_tools.ainvoke([system, *state["messages"]])
return {"messages": [response]}
graph = StateGraph(MessagesState)
graph.add_node("agent", call_model)
graph.add_node("tools", ToolNode([my_tool]))
graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", tools_condition)
graph.add_edge("tools", "agent")
return graph.compile()
# Usage:
# agent = MyAgent(id="my-agent", version="1.0.0", api_key="sk-...")
# async for event in agent.send_message(message): ...
CEL (Common Expression Language) is used in YAML for dynamic behavior.
# Access last message content
'state.messages[size(state.messages) - 1]'
# Access last message's content field (for AIMessage objects)
'state.messages[size(state.messages) - 1]["content"]'
# String literal (MUST use inner quotes)
'"hello world"'
# Integer literal
'42'
# Build a dict for data_transform
'{"count": state.count + 1, "status": "done"}'
# Conditional check
'state.messages[size(state.messages) - 1]["content"].contains("COMPLETE")'
# Reference a registered prompt (top-level, no state prefix)
'prompts["my-prompt"]["instructions"]'
# String concatenation
'"Hello, " + state.user_name'
# List size
'size(state.messages)'
# Boolean check
'state.retry_count < 3 && state.status != "failed"'
# Message type (returns "ai", "human", "system", "tool")
'state.messages[size(state.messages) - 1]["type"] == "ai"'
# Message metadata via additional_kwargs
'state.messages[0]["additional_kwargs"]["type"] == "approval_decision"'
# Reference custom static data from the top-level `data:` section
'data.thresholds.max_retries' # nested map access
'data["labels"][state.sentiment]' # state-driven lookup
'state.retry_count < data.thresholds.max_retries' # threshold check
All bindings are top-level except state (which uses the state. prefix):
| Variable | Source | Scope |
|---|---|---|
state | agent state.fields | every expression |
prompts["<id>"]["instructions"] | top-level prompts: | every expression |
llms["<id>"]["model_name"] | top-level llms: | every expression |
skills["<id>"] → id/version/name/description | top-level skills: (when declared) | every expression |
data | top-level data: (when declared) — free-form | every expression |
llm_response → content/tool_calls | the LLM reply | only in call_llm state_updates |
llm_response is the only runtime-scoped binding — it is unavailable in prompts, edges, or non-call_llm nodes.
# Filter: keep elements matching a predicate
'state.messages.filter(m, m["type"] == "human")' # filter by type
'state.items.filter(x, x > 0)' # filter primitives
# Exists: check if any element matches
'state.messages.exists(m, m["type"] == "ai")' # any AI message?
'state.messages.exists(m, m["additional_kwargs"]["type"] == "approval_decision")'
# All: check if all elements match
'state.items.all(x, x > 0)' # all positive?
# Map: transform each element
'state.messages.map(m, m["type"])' # extract field from each
'state.items.map(x, x * 2)' # double each
# Count matching elements
'size(state.messages.filter(m, m["type"] == "human")) > 0'
# findLast pattern: last() + filter()
'last(state.messages.filter(m, m["type"] == "human"))' # last human message
'default(last(state.messages.filter(m, m["additional_kwargs"]["type"] == "approval_decision"))["content"], "")'
# JSON: parse structured data from strings
'json(state.response)["status"]' # parse JSON, access field
'json(state.messages[0]["content"])["action"]' # parse message content as JSON
'jsonValid(state.data)' # check if string is valid JSON
'jsonValid(state.data) && json(state.data)["ok"] == true' # guard + parse
# Safe access: fallback on errors
'default(json(state.response)["action"], "continue")' # fallback if parse/key fails
'default(state.missing_field, 0)' # fallback for missing state
# List utilities
'last(state.items)' # last element (error if empty)
'last(state.messages.filter(m, m["type"] == "ai"))' # findLast pattern
'default(last(state.messages.filter(m, ...))["content"], "")' # safe findLast with fallback
# String extensions (cel-go compatible)
'state.tags.split(",")' # split → list
'" hello ".trim()' # strip whitespace
'"HELLO".lowerAscii()' # lowercase
'"hello".upperAscii()' # uppercase
'"hello world".replace("world", "CEL")' # replace substrings
'"hello".indexOf("ll")' # find index (or -1)
'["a", "b", "c"].join(", ")' # join list → string
'"hello world".substring(0, 5)' # extract substring
# Templating: ${key} substitution from a map
'template("Hello ${name}!", {"name": state.user})' # basic substitution
'template(prompts["plan"]["instructions"], {"skills": state.skill_list})' # prompt templating
# Combining functions
'json(state.data.trim())["name"].lowerAscii()' # trim → parse → lowercase
'state.tags.split(",").join(" | ")' # split → rejoin
state. prefix: Use state.messages, state["counter"] — not bare messages or counter. Extra vars like prompts, llms, skills, and data are top-level.'"hello"' not 'hello'. Without inner quotes, CEL treats it as a variable name.size() not len(): CEL uses size() for list/string length.{"key": value} — keys must be strings in double quotes.template() for substitution or + for concatenation: template("Count: ${n}", {"n": state.count}) or '"Count: " + string(count)'default() is expression-level: default(expr, fallback) must be the outermost call — it catches errors in expr and returns fallback.Auto-injected tool_node: When call_llm has tools, sherma auto-injects a tool_node with conditional edges. Do NOT add your own tool_node for tool-equipped call_llm nodes.
Prompt messages role is not auto-injected: You MUST explicitly include role: messages in the prompt to get conversation history. Without it, the LLM has no context of previous messages.
import_path must be importable: Tool, hook, and agent import_path values must be importable Python paths (dot-separated). The module must be on sys.path. For tools, the path must point to a @tool-decorated function.
String values in set_state: Each value is a CEL expression. String literals require inner quotes: '"ready"' not 'ready'.
Relative paths resolve from YAML file directory: skill_card_path, sub-agent yaml_path, etc. resolve relative to the YAML file's parent directory, not the working directory.
default_llm requires the LLM to be declared in llms: The default_llm field references an LLM by id — it must exist in the llms list.
Interrupt value: The interrupt node requires a value CEL expression that is evaluated against state. Use a string literal (e.g., '"question"') or reference state (e.g., state.messages[size(state.messages) - 1].content).
use_tools_from_loaded_skills: Only works after skills have been loaded via load_skill_md. Put the discovery node before the execution node.
custom node hooks can access registries: NodeExecuteContext exposes ctx.registries (a RegistryBundle) so node_execute hooks can call chat models (ctx.registries.chat_models[llm_id]), resolve tools, render prompts, etc. without wiring dependencies at agent-init time. Remote (JSON-RPC and MCP) hooks receive registries=None because it contains live Python objects.
MCP hook tool names use . not /: MCP restricts tool names to [A-Za-z0-9_.-]+, so MCP-based hook tools are named hooks.before_llm_call (dot separator), not hooks/before_llm_call. The executor discovers tools with the hooks. prefix at connect time and only dispatches the hooks the server has registered.
Follow these steps when building an agent:
$ARGUMENTS for the agent description.agent.yaml + main.py (entry point) + tool files if neededagent.py (LangGraphAgent subclass) + main.py + tool filesserver.py with A2A boilerplateimport_path values point to real modules__end__ is reachable from every pathEntityBase, Prompt, LLM, Tool, Skill, SkillCard, SkillFrontMatter, LocalToolDef, MCPServerDef
Agent, LocalAgent, RemoteAgent, LangGraphAgent, DeclarativeAgent
Registry, RegistryEntry, RegistryBundle, TenantRegistryManager, PromptRegistry, LLMRegistry, ToolRegistry, SkillRegistry, AgentRegistry
HookExecutor, BaseHookExecutor, HookManager, HookType, HookFastAPIApplication, HookStarletteApplication, RemoteHookExecutor, MCPHookExecutor, MCPHookTransport, MCPHookServer. Remote hook servers (JSON-RPC and MCP) are authored as BaseHookExecutor subclasses with typed contexts — same as in-process hooks.
DeclarativeConfig, load_declarative_config
create_skill_tools
SCHEMA_INPUT_URI, SCHEMA_OUTPUT_URI, validate_data, schema_to_extension, make_schema_data_part, create_agent_input_as_message_part, create_agent_output_as_message_part, get_agent_input_from_message_part, get_agent_output_from_message_part
EntityType, Markdown, Protocol, DEFAULT_TENANT_ID
ShermaError, EntityNotFoundError, VersionNotFoundError, RegistryError, RemoteEntityError, DeclarativeConfigError, GraphConstructionError, CelEvaluationError, SchemaValidationError
ShermaAgentExecutor, a2a_to_langgraph, langgraph_to_a2a, combine_ai_messages
sherma.http)get_http_client, HttpClientFactory, mcp_http_client_factory, McpHttpClientFactory. A single shared httpx.AsyncClient is cached in a ContextVar and used by LLM, A2A, registries, remote hooks (JSON-RPC), and MCP hooks (streamable_http / sse). Configure headers/auth/event hooks/transport on it once via get_http_client(...).
For detailed signatures, see references/api-reference.md.