Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Make a reply easier to read and act on with rich blocks inside ordinary Markdown — a choice the user picks from, a form that collects several answers, a procedure as steps with warnings in place, a callout, a Mermaid diagram, a weather card, a live clock or countdown, a dashboard of gauges for a quota, a budget or a job's progress, a chart drawn from data (line, area, bar, pie, scatter, histogram, gauge) and a system-resource board — written as fenced blocks the app renders with its own componen
SkillScore92/100
Test results 92
3/3 safety checks passed
21%92%of test cases passed +71 pts
Passed 22 of 24 test cases with the skill, and 5 of 24 without it.
17 fixed5 passed either way2 still failing0 broke
One test taskWrite a short plain-text summary about the cluster state. Mention that it has three active nodes, five pending pods, and twelve disconnected clients.
Adds every file the skill has, pinned so you can update it. No account needed.
pip install "decimalai>=0.15"
decimalai skills add prism-shadow-a2ui
claude --plugin-dir "$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.agents"The same install from Python, for a notebook or a setup script.
pip install "decimalai>=0.15"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("prism-shadow-a2ui")
if result.exit_code:
raise SystemExit(result.problems)
print("Added. Start Claude Code with the command below.")claude --plugin-dir "$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.agents"Type these in a Claude Code session. No account or API key. It writes the skill and any files it bundles, and asks first when its safety rating isn't Passed.
/plugin marketplace add decimal-labs/decimalai-claude-plugins
/plugin install decimalai@decimalai/decimalai:install prism-shadow-a2uiThe plugin needs python3 and curl on your machine.
No Python or account. Claude Code picks it up in its next session.
curl -fsSL --create-dirs -o .claude/skills/prism-shadow-a2ui/SKILL.md https://app.decimal.ai/s/prism-shadow-a2ui/SKILL.md.claude/skills/prism-shadow-a2ui/SKILL.mdThis gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[claude-agent-sdk]"
decimalai skills add prism-shadow-a2uiimport asyncio
from claude_agent_sdk import ClaudeAgentOptions
from decimalai.claude_agent_sdk import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
async def main():
question = "Hello!"
async with support.run(question) as run: # one user message, one run
async for message in run.query(options=ClaudeAgentOptions(max_turns=6)):
print(message)
# In a notebook or async code: await main()
asyncio.run(main())Prefer its own loader? ClaudeAgentOptions(plugins=[skills_plugin()], skills="all") reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[claude-agent-sdk]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("prism-shadow-a2ui")
if result.exit_code:
raise SystemExit(result.problems)
import asyncio
from claude_agent_sdk import ClaudeAgentOptions
from decimalai.claude_agent_sdk import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
async def main():
question = "Hello!"
async with support.run(question) as run: # one user message, one run
async for message in run.query(options=ClaudeAgentOptions(max_turns=6)):
print(message)
# In a notebook or async code: await main()
asyncio.run(main())Prefer its own loader? ClaudeAgentOptions(plugins=[skills_plugin()], skills="all") reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/prism-shadow-a2ui/SKILL.mdimport asyncio
from pathlib import Path
from claude_agent_sdk import ClaudeAgentOptions, query
skill = Path(".agents/skills/prism-shadow-a2ui/SKILL.md").read_text()
options = ClaudeAgentOptions(system_prompt=f"You are a helpful agent.\n\n{skill}", max_turns=6)
async def main():
async for message in query(prompt="Hello!", options=options):
print(message)
# In a notebook or async code: await main()
asyncio.run(main())This gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai-agents]"
decimalai skills add prism-shadow-a2uifrom agents import Agent, Runner
from decimalai.openai_agents import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
name=support.name,
instructions=support.instructions, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# In a notebook or async code: result = await Runner.run(agent, "Hello!")
result = Runner.run_sync(agent, "Hello!")Using SandboxAgent? capabilities=[Shell(), decimalai.openai_agents.sandbox_skills()] points its own loader at the same folder.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai-agents]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("prism-shadow-a2ui")
if result.exit_code:
raise SystemExit(result.problems)
from agents import Agent, Runner
from decimalai.openai_agents import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
name=support.name,
instructions=support.instructions, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# In a notebook or async code: result = await Runner.run(agent, "Hello!")
result = Runner.run_sync(agent, "Hello!")Using SandboxAgent? capabilities=[Shell(), decimalai.openai_agents.sandbox_skills()] points its own loader at the same folder.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/prism-shadow-a2ui/SKILL.mdfrom pathlib import Path
from agents import Agent, Runner
skill = Path(".agents/skills/prism-shadow-a2ui/SKILL.md").read_text()
agent = Agent(name="support", instructions=f"You are a helpful agent.\n\n{skill}")
# In a notebook or async code: result = await Runner.run(agent, "Hello!")
result = Runner.run_sync(agent, "Hello!")This gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai]"
decimalai skills add prism-shadow-a2uiimport json
from openai import OpenAI
from decimalai.openai import agent_config
client = OpenAI()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your tools, as Chat Completions takes them, and what answers each.
TOOLS = [{"type": "function", "function": {
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
# run.system is your prompt, then the skill list.
messages = [{"role": "system", "content": run.system}, run.message]
while True:
reply = client.chat.completions.create(
model="gpt-5.4-mini",
messages=messages,
tools=[*TOOLS, *run.tools], # your tools, then load_skill
).choices[0].message
messages.append(reply)
if not reply.tool_calls:
break
for call in reply.tool_calls:
# load_skill is answered by the run; your own tools by FUNCTIONS.
function = FUNCTIONS.get(call.function.name)
messages.append(run.tool_result(call) or {
"role": "tool", "tool_call_id": call.id,
"content": function(**json.loads(call.function.arguments)) if function
else f"There is no tool named {call.function.name!r}.",
})
print(reply.content)
# The Responses API takes run.response_tools instead of run.tools.The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("prism-shadow-a2ui")
if result.exit_code:
raise SystemExit(result.problems)
import json
from openai import OpenAI
from decimalai.openai import agent_config
client = OpenAI()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your tools, as Chat Completions takes them, and what answers each.
TOOLS = [{"type": "function", "function": {
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
# run.system is your prompt, then the skill list.
messages = [{"role": "system", "content": run.system}, run.message]
while True:
reply = client.chat.completions.create(
model="gpt-5.4-mini",
messages=messages,
tools=[*TOOLS, *run.tools], # your tools, then load_skill
).choices[0].message
messages.append(reply)
if not reply.tool_calls:
break
for call in reply.tool_calls:
# load_skill is answered by the run; your own tools by FUNCTIONS.
function = FUNCTIONS.get(call.function.name)
messages.append(run.tool_result(call) or {
"role": "tool", "tool_call_id": call.id,
"content": function(**json.loads(call.function.arguments)) if function
else f"There is no tool named {call.function.name!r}.",
})
print(reply.content)
# The Responses API takes run.response_tools instead of run.tools.No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/prism-shadow-a2ui/SKILL.mdfrom pathlib import Path
from openai import OpenAI
client = OpenAI()
skill = Path(".agents/skills/prism-shadow-a2ui/SKILL.md").read_text()
reply = client.chat.completions.create(
model="gpt-5.4-mini",
messages=[
{"role": "system", "content": f"You are a helpful agent.\n\n{skill}"},
{"role": "user", "content": "Hello!"},
],
).choices[0].messageThis gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[anthropic]"
decimalai skills add prism-shadow-a2uiimport anthropic
from decimalai.anthropic import agent_config
client = anthropic.Anthropic()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
question = "Hello!"
with support.run(question) as run: # one user message, one run
answer = client.beta.messages.tool_runner(
model="claude-haiku-5-5",
max_tokens=4096,
system=run.system, # your prompt, then the skill list
tools=[*run.tools], # your tools, then load_skill
messages=[{"role": "user", "content": question}],
).until_done()The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[anthropic]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("prism-shadow-a2ui")
if result.exit_code:
raise SystemExit(result.problems)
import anthropic
from decimalai.anthropic import agent_config
client = anthropic.Anthropic()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
question = "Hello!"
with support.run(question) as run: # one user message, one run
answer = client.beta.messages.tool_runner(
model="claude-haiku-5-5",
max_tokens=4096,
system=run.system, # your prompt, then the skill list
tools=[*run.tools], # your tools, then load_skill
messages=[{"role": "user", "content": question}],
).until_done()No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/prism-shadow-a2ui/SKILL.mdfrom pathlib import Path
import anthropic
client = anthropic.Anthropic()
skill = Path(".agents/skills/prism-shadow-a2ui/SKILL.md").read_text()
reply = client.messages.create(
model="claude-haiku-5-5",
max_tokens=4096,
system=f"You are a helpful agent.\n\n{skill}",
messages=[{"role": "user", "content": "Hello!"}],
)This gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[google-genai]"
decimalai skills add prism-shadow-a2uifrom google import genai
from google.genai import types
from decimalai.google_genai import agent_config
client = genai.Client()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your functions, as declarations for the model, and what answers each.
TOOLS = [{"function_declarations": [{
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters_json_schema": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}]}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
config = types.GenerateContentConfig(
system_instruction=run.system, # your prompt, then the skill list
tools=[*TOOLS, *run.tool_params], # your functions, then load_skill
)
contents = [{"role": "user", "parts": [{"text": run.message}]}]
while True:
response = client.models.generate_content(
model="gemini-3.5-flash", contents=contents, config=config,
)
if not response.function_calls:
break
contents.append(response.candidates[0].content)
parts = []
for call in response.function_calls:
# load_skill is answered by the run; your own functions by FUNCTIONS.
# A loop of your own rather than automatic function calling, which
# raises KeyError when the model asks for a function you did not pass.
function = FUNCTIONS.get(call.name)
parts.append(run.tool_result(call) or {"function_response": {
"name": call.name, "id": call.id,
"response": {"result": function(**(call.args or {})) if function
else f"There is no function named {call.name!r}."},
}})
contents.append({"role": "user", "parts": parts})
print(response.text)The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[google-genai]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("prism-shadow-a2ui")
if result.exit_code:
raise SystemExit(result.problems)
from google import genai
from google.genai import types
from decimalai.google_genai import agent_config
client = genai.Client()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your functions, as declarations for the model, and what answers each.
TOOLS = [{"function_declarations": [{
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters_json_schema": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}]}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
config = types.GenerateContentConfig(
system_instruction=run.system, # your prompt, then the skill list
tools=[*TOOLS, *run.tool_params], # your functions, then load_skill
)
contents = [{"role": "user", "parts": [{"text": run.message}]}]
while True:
response = client.models.generate_content(
model="gemini-3.5-flash", contents=contents, config=config,
)
if not response.function_calls:
break
contents.append(response.candidates[0].content)
parts = []
for call in response.function_calls:
# load_skill is answered by the run; your own functions by FUNCTIONS.
# A loop of your own rather than automatic function calling, which
# raises KeyError when the model asks for a function you did not pass.
function = FUNCTIONS.get(call.name)
parts.append(run.tool_result(call) or {"function_response": {
"name": call.name, "id": call.id,
"response": {"result": function(**(call.args or {})) if function
else f"There is no function named {call.name!r}."},
}})
contents.append({"role": "user", "parts": parts})
print(response.text)No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/prism-shadow-a2ui/SKILL.mdfrom pathlib import Path
from google import genai
from google.genai import types
client = genai.Client()
skill = Path(".agents/skills/prism-shadow-a2ui/SKILL.md").read_text()
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="Hello!",
config=types.GenerateContentConfig(system_instruction=f"You are a helpful agent.\n\n{skill}"),
)This gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[langchain]" langchain-openai
decimalai skills add prism-shadow-a2uifrom langchain.agents import create_agent
from decimalai.langchain import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = create_agent(
"openai:gpt-5.4-mini",
tools=[], # your tools; the middleware adds load_skill
middleware=[support.middleware], # your prompt, the skill list and load_skill
)
result = agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[langchain]" langchain-openaifrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("prism-shadow-a2ui")
if result.exit_code:
raise SystemExit(result.problems)
from langchain.agents import create_agent
from decimalai.langchain import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = create_agent(
"openai:gpt-5.4-mini",
tools=[], # your tools; the middleware adds load_skill
middleware=[support.middleware], # your prompt, the skill list and load_skill
)
result = agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/prism-shadow-a2ui/SKILL.mdfrom pathlib import Path
from langchain.agents import create_agent
skill = Path(".agents/skills/prism-shadow-a2ui/SKILL.md").read_text()
agent = create_agent("openai:gpt-5.4-mini", tools=[], system_prompt=f"You are a helpful agent.\n\n{skill}")
result = agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})This gets SKILL.md only. The skill's 4 other files are not included.
CrewAI's own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" "crewai>=1.15.27"
decimalai skills add prism-shadow-a2uiimport decimalai.crewai
from crewai import Agent, Crew, Task
agent = Agent(
role="Support", goal="Help the customer", backstory="A support agent.",
llm=llm, # your own CrewAI LLM
skills=decimalai.crewai.skills(), # the installed folder
tools=decimalai.crewai.skill_tools(), # reads a skill's files, runs its scripts
)
task = Task(description="Hello!", expected_output="an answer", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()The same install from Python, for a notebook or a setup script. CrewAI's own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" "crewai>=1.15.27"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("prism-shadow-a2ui")
if result.exit_code:
raise SystemExit(result.problems)
import decimalai.crewai
from crewai import Agent, Crew, Task
agent = Agent(
role="Support", goal="Help the customer", backstory="A support agent.",
llm=llm, # your own CrewAI LLM
skills=decimalai.crewai.skills(), # the installed folder
tools=decimalai.crewai.skill_tools(), # reads a skill's files, runs its scripts
)
task = Task(description="Hello!", expected_output="an answer", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()No DecimalAI package. CrewAI's own loader reads the folder, and nothing pins or updates it.
.agents/skills/prism-shadow-a2ui/SKILL.mdfrom pathlib import Path
from crewai import Agent, Crew, Task
agent = Agent(
role="Support", goal="Help the customer", backstory="A support agent.",
llm=llm, # your own CrewAI LLM
skills=[Path(".agents/skills")],
)
task = Task(description="Hello!", expected_output="an answer", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()This gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[adk]"
decimalai skills add prism-shadow-a2uifrom decimalai.adk import DecimalAIPlugin, agent_config
from google.adk.agents import LlmAgent
from google.adk.apps import App
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = LlmAgent(
name="support", # ADK needs a Python identifier
model="gemini-3.5-flash",
instruction=support.instruction, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# DecimalAIPlugin makes each invocation of runners built from this App one trace.
app = App(name="support", root_agent=agent, plugins=[DecimalAIPlugin()])Prefer ADK's own loader? SkillToolset(skills=decimalai.adk.skills(), ...) reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[adk]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("prism-shadow-a2ui")
if result.exit_code:
raise SystemExit(result.problems)
from decimalai.adk import DecimalAIPlugin, agent_config
from google.adk.agents import LlmAgent
from google.adk.apps import App
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = LlmAgent(
name="support", # ADK needs a Python identifier
model="gemini-3.5-flash",
instruction=support.instruction, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# DecimalAIPlugin makes each invocation of runners built from this App one trace.
app = App(name="support", root_agent=agent, plugins=[DecimalAIPlugin()])Prefer ADK's own loader? SkillToolset(skills=decimalai.adk.skills(), ...) reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/prism-shadow-a2ui/SKILL.mdfrom pathlib import Path
from google.adk.agents import LlmAgent
skill = Path(".agents/skills/prism-shadow-a2ui/SKILL.md").read_text()
agent = LlmAgent(
name="support",
model="gemini-3.5-flash",
instruction=lambda context: f"You are a helpful agent.\n\n{skill}",
)This gets SKILL.md only. The skill's 4 other files are not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[pydantic-ai]"
decimalai skills add prism-shadow-a2uifrom pydantic_ai import Agent
from decimalai.pydantic_ai import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
"openai:gpt-4o-mini",
name=support.name,
tools=[], # your tools; the capability adds load_skill
capabilities=[support], # your prompt, the skill list and load_skill
)
# In a notebook or async code: result = await agent.run("Hello!")
result = agent.run_sync("Hello!")On the Pydantic AI harness, decimalai.pydantic_ai_harness.skills(project) uses its own loader instead.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[pydantic-ai]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("prism-shadow-a2ui")
if result.exit_code:
raise SystemExit(result.problems)
from pydantic_ai import Agent
from decimalai.pydantic_ai import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
"openai:gpt-4o-mini",
name=support.name,
tools=[], # your tools; the capability adds load_skill
capabilities=[support], # your prompt, the skill list and load_skill
)
# In a notebook or async code: result = await agent.run("Hello!")
result = agent.run_sync("Hello!")On the Pydantic AI harness, decimalai.pydantic_ai_harness.skills(project) uses its own loader instead.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/prism-shadow-a2ui/SKILL.mdfrom pathlib import Path
from pydantic_ai import Agent
skill = Path(".agents/skills/prism-shadow-a2ui/SKILL.md").read_text()
agent = Agent("openai:gpt-4o-mini", instructions=f"You are a helpful agent.\n\n{skill}")
# In a notebook or async code: result = await agent.run("Hello!")
result = agent.run_sync("Hello!")This gets SKILL.md only. The skill's 4 other files are not included.
Deep Agents' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" deepagents
decimalai skills add prism-shadow-a2uiimport decimalai.deepagents
from deepagents import create_deep_agent
agent = create_deep_agent(model=model, **decimalai.deepagents.skills())
agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})The default backend reads files but has no shell. To run a skill's scripts, pass skills(backend=LocalShellBackend(...)).
The same install from Python, for a notebook or a setup script. Deep Agents' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" deepagentsfrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("prism-shadow-a2ui")
if result.exit_code:
raise SystemExit(result.problems)
import decimalai.deepagents
from deepagents import create_deep_agent
agent = create_deep_agent(model=model, **decimalai.deepagents.skills())
agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})The default backend reads files but has no shell. To run a skill's scripts, pass skills(backend=LocalShellBackend(...)).
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/prism-shadow-a2ui/SKILL.mdfrom pathlib import Path
from deepagents import create_deep_agent
skill = Path(".agents/skills/prism-shadow-a2ui/SKILL.md").read_text()
agent = create_deep_agent(model=model, system_prompt=f"You are a helpful agent.\n\n{skill}")
agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})This gets SKILL.md only. The skill's 4 other files are not included.
Its own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" agent-framework
decimalai skills add prism-shadow-a2uiimport asyncio
from agent_framework import Agent
from decimalai.agent_framework import skills
agent = Agent(client=client, instructions="You are a helpful agent.", context_providers=[skills()])
asyncio.run(agent.run("Hello!"))It caches skills by default, so restart the process after decimalai skills update.
The same install from Python, for a notebook or a setup script. Its own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" agent-frameworkfrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("prism-shadow-a2ui")
if result.exit_code:
raise SystemExit(result.problems)
import asyncio
from agent_framework import Agent
from decimalai.agent_framework import skills
agent = Agent(client=client, instructions="You are a helpful agent.", context_providers=[skills()])
asyncio.run(agent.run("Hello!"))It caches skills by default, so restart the process after decimalai skills update.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/prism-shadow-a2ui/SKILL.mdimport asyncio
from pathlib import Path
from agent_framework import Agent
skill = Path(".agents/skills/prism-shadow-a2ui/SKILL.md").read_text()
agent = Agent(client=client, instructions=f"You are a helpful agent.\n\n{skill}")
asyncio.run(agent.run("Hello!"))This gets SKILL.md only. The skill's 4 other files are not included.
Strands' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" strands-agents
decimalai skills add prism-shadow-a2uifrom decimalai.strands import skills
from strands import Agent
from strands.vended_tools import file_editor, shell
agent = Agent(
model=model, # always pass one: the default is Amazon Bedrock
system_prompt="You are a helpful agent.",
tools=[file_editor, shell],
plugins=[skills()],
)
agent("Hello!")Strands loads an agent's skills once, so build the agent again after decimalai skills update.
The same install from Python, for a notebook or a setup script. Strands' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" strands-agentsfrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("prism-shadow-a2ui")
if result.exit_code:
raise SystemExit(result.problems)
from decimalai.strands import skills
from strands import Agent
from strands.vended_tools import file_editor, shell
agent = Agent(
model=model, # always pass one: the default is Amazon Bedrock
system_prompt="You are a helpful agent.",
tools=[file_editor, shell],
plugins=[skills()],
)
agent("Hello!")Strands loads an agent's skills once, so build the agent again after decimalai skills update.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/prism-shadow-a2ui/SKILL.mdfrom pathlib import Path
from strands import Agent
skill = Path(".agents/skills/prism-shadow-a2ui/SKILL.md").read_text()
agent = Agent(model=model, system_prompt=f"You are a helpful agent.\n\n{skill}")
agent("Hello!")This gets SKILL.md only. The skill's 4 other files are not included.
Adds every file the skill has, pinned so you can update it. Cursor, Copilot, Cline and Warp read this folder by themselves.
pip install "decimalai>=0.15"
decimalai skills add prism-shadow-a2uiNo Python or account. Cursor, Copilot, Cline and Warp read this folder by themselves.
curl -fsSL --create-dirs -o .agents/skills/prism-shadow-a2ui/SKILL.md https://app.decimal.ai/s/prism-shadow-a2ui/SKILL.md.agents/skills/prism-shadow-a2ui/SKILL.mdThis gets SKILL.md only. The skill's 4 other files are not included.
Give its text to your model as instructions. No account or package needed.
This gets SKILL.md only. The skill's 4 other files are not included.
Raw files: /s/prism-shadow-a2ui/SKILL.md · /s/prism-shadow-a2ui.json
Fetch the file and give its text to your model as instructions. No account or package needed.
curl -fsSL https://app.decimal.ai/s/prism-shadow-a2ui/SKILL.mdcurl -fsSL https://app.decimal.ai/s/prism-shadow-a2ui.jsonThis gets SKILL.md only. The skill's 4 other files are not included.
You don't need a DecimalAI account.
Saved prompts and skills, pinned versions, and traces of this skill in your runs. DecimalAI does not run your agent.
Claude Code reads files from your project, so DecimalAI config does not apply there.
On Claude Agent SDK: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On OpenAI Agents: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On OpenAI SDK: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Anthropic: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Gemini API: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On LangChain: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On CrewAI: set DECIMAL_API_KEY and add decimalai.init(crewai=True) to record runs and skill use. The skill version stays in your repo.
On Google ADK: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Pydantic AI: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Deep Agents: set DECIMAL_API_KEY and add decimalai.init(langchain=True) to record runs and skill use. The skill version stays in your repo.
On Microsoft Agent Framework: set DECIMAL_API_KEY and add decimalai.init(otel=True, agent_name="support") to record runs and skill use. The skill version stays in your repo.
On Strands: set DECIMAL_API_KEY and add decimalai.init(otel=True, agent_name="support") to record runs and skill use. The skill version stays in your repo.
Editors read files from your project, so DecimalAI config does not apply there.
Anywhere else: record a use with decimalai.log_skill_activation() inside a trace.
A reply is Markdown. Where a decision, a set of inputs, a procedure, a structure, a few readings or a series of numbers would read better as a component than as prose, write one as a fenced block: ```a2ui holding ONE JSON object, or ```mermaid holding a diagram. The Web App renders the block with its own components and theme; the CLI and the messaging channels show the same block as readable text; nothing is lost on a surface that cannot render it. You never write HTML, scripts or styles — you name a component from the catalog and fill its fields.
Before you send a reply that carries a block, run the checker (see "Check before you send"). It is the test suite for this skill: it rejects what would not render, warns about what would be hard to read, scores the draft and prints the questions a reviewer would ask.
This skill changes how you write replies; it needs no setup. If a message only names the skill without a task — "use a2ui", "show me the blocks" — ask what the user wants to decide, enter or understand, then answer that with the fitting block. Do not demonstrate every component at once: one block that serves the question is the demonstration.
Use a block when it saves the reader work:
recommended when you have a view.warning or caution sits on the step it applies to and is rendered above it.scripts/weather.mjs <place> (see "Get real data"), paste the block it prints, and write one sentence before it.metrics block, never one block per number. kind says how to read a tile: reading, used, remaining or progress. worse says which way is bad (default low for remaining, else high). warn and danger colour the tile once the value passes them.scripts/sysinfo.mjs and paste the block it prints. You give the numbers; the app colours them by its own thresholds.line, area), a comparison (bar), shares of a whole (pie), how two measures relate (scatter), how values spread (histogram), or one reading against its whole (gauge). The data comes from a tool, a file or the user.A chart draws one basic chart from the data you give. metrics, weather and resources are built from the same charts for data of a known shape. Use the composite when the data has its shape: it takes fewer fields and reads better. Use a chart when the data has no such shape. One reading is a chart of kind gauge, several readings are a metrics block, and a machine snapshot is a resources block.
Do not use a block for:
asOf time. Never invent, estimate or round them into a guess.Keep to one or two blocks per reply and at most two questions (choice/form). More than that is a questionnaire, not a conversation.
The full reference, every field and its limits, a valid example of each type and the common mistakes: references/components.md beside this file. Read it the first time you write a block, and whenever the checker rejects one.
| Type | Required | Optional | Limits |
|---|---|---|---|
choice | question, options[] (label) | id, options[].value / description / recommended, multiple, allowOther | question ≤ 120, label ≤ 60 (warn > 40), 2–7 options (warn > 5), labels unique, at most one recommended |
form | fields[] (id, label, kind: text, textarea, number, slider, single, multiple, select, switch, date, time, datetime) | id, title, submitLabel; per field hint, required, default, options (single, multiple, select), placeholder, min/max/step/unit (number, slider), maxLength (text, textarea), pattern (text) | 1–6 fields (warn > 4), field ids unique ^[a-z][a-z0-9_]{0,31}$; a slider needs min and max; a select 2–30 options (warn > 12); a default must pass its field's rule |
steps | steps[] (text) | title, per step warning, caution, note, code, lang | 1–15 steps (warn > 10), text ≤ 200, code ≤ 2000 |
callout | tone (note/tip/caution/warning), text | title | text ≤ 400 |
mermaid | a supported header on the first line | — | no %%{init}%%, no frontmatter config, no click/href/javascript:; warn above 30 nodes or edges |
weather | place, condition, temp | unit (C/F), night, summary, high/low, feelsLike, humidity, windSpeed (+ windUnit, windDirection), hourly[] (time, temp), daily[] (date, high, low, condition), asOf, source | place ≤ 60, summary ≤ 80, 11 conditions, hourly 2–24 (warn > 12), daily 2–7, high ≥ low |
clock | — | title, zones[] (zone: an IANA name or local; label), style (digital/analog/both), hourCycle, seconds, date | 1–4 zones, no zone twice |
countdown | to (ISO date-time or YYYY-MM-DD), label | doneLabel, showTarget | label ≤ 60 |
metrics | items[] (label, value) | title, asOf; per item kind, max/min, gauge (ring/bar/none), unit, prefix, decimals, worse, warn/danger, detail, delta + deltaLabel, history[] | 1–8 items (warn > 6), labels unique, max required for used/remaining/progress and for a gauge |
resources | at least one of cpu (percent), memory / swap (used, total), disks[] (path, used, total), network (unit, rx[], tx[]), load (one, cores), uptimeSec | host, asOf; cpu.history / cores, memory.history, units, load.five / fifteen | percents 0–100, used ≤ total, 1–6 disks, rx and tx 2–60 readings of one length |
chart | kind; categories[] + series[] (label, values[]); scatter series[].points[]; histogram edges[]; gauge item (one metrics item) | title, xLabel / yLabel, xUnit / yUnit, stacked (bar, area), horizontal (bar), donut (pie), min / max, decimals, asOf | 1–60 categories (warn > 24), 1–6 series (warn > 4), one value per category (null for a gap); pie 2–8 slices (warn > 6) and one series; scatter ≤ 500 points per series (warn > 200); histogram 3–61 ascending edges |
The fence is ```a2ui (or ```a2ui json); one JSON object per fence; strict JSON (double quotes, no comments, no trailing commas). An unknown type or a missing required field is an error; an unknown field is ignored with a warning. asOf and to are ISO 8601 date-times (2026-10-04T14:05+08:00); a weather, metrics or resources block without asOf is a warning.
value (default: its label), several picks joined with 、 or ", "; for a form, one line per field, label: answer, a switch as Yes or No. The user can edit that text before sending, or ignore the block and type anything. Read it as you would read any user message; never expect a marker.allowOther: true adds an "Other…" control that clears the composer and focuses it. Use it when your options may not cover the answer.asOf and never update: do not promise that they will.A widget shows data you have, never data you guess. Two scripts beside the checker fetch it and print a ready block. Both run with the Node already on the machine.
node <skill dir>/scripts/weather.mjs "Beijing" --days 3
node <skill dir>/scripts/sysinfo.mjs
weather.mjs <place> reads Open-Meteo (no key): the current conditions, the next hours and the coming days, in the place's own time zone. If Open-Meteo fails, the script asks wttr.in (no key, 3 days in 3-hour steps); --source open-meteo|wttr forces one source. Options: --days 0-7 (default 5), --hours 0-24 (default 12), --unit C|F, --lang zh|en|auto (auto picks zh for a Chinese place name). On stderr it names the place it matched and the source that answered: check that it is the place the user meant.sysinfo.mjs reads the machine the agent runs on and prints a resources block: CPU and memory with a short history, swap, disks, network, load and uptime (swap and network on Linux only). Options: --disk <path>, repeated for up to 6 disks. That machine may not be the user's own computer; say which machine the numbers describe.--json prints the bare object, for a block you assemble from several readings.NODE_USE_ENV_PROXY=1 set.Apply these to the prose of every reply, zh and en. They are what makes model output readable.
warning/caution, never after the harm.For any reply that carries an a2ui or mermaid block (not for plain prose), run the checker on the whole draft and fix what it finds. The paper this follows found that deterministic validation plus up to three rounds of error-feedback repair is what makes generated UI reliable; this is that loop.
The checker is scripts/check.mjs in this skill's directory — the directory you read this SKILL.md from, <app_data_dir>/agents/<agent_id>/agent_state/skills/a2ui/. It is self-contained and runs with the Node already on the machine.
Write the complete draft reply to a temporary file outside the Workspace, for example /tmp/a2ui-draft.md (%TEMP%\a2ui-draft.md on Windows). Alternatively feed it on stdin with a quoted heredoc so no file is written.
Run it:
node <skill dir>/scripts/check.mjs /tmp/a2ui-draft.md --rubric
# or, with no file:
node <skill dir>/scripts/check.mjs --rubric <<'EOF'
...the draft...
EOF
Options: --lang zh|en|auto (the rubric's language; auto picks zh when Chinese dominates), --json for the machine-readable report. Exit code 0 means no errors, 1 means at least one error, 2 means the checker could not run.
Read the report. Errors are blocks that would not render or would mislead (invalid JSON, unknown type, missing field, duplicate label, two recommended options, an unbalanced mermaid line, a forbidden directive): fix every one. Warnings are reading cost (too many options, a question that is not last, an ungrounded block, a widget without asOf): fix the ones that change how the reader experiences the reply. Then answer the rubric questions honestly — they catch what no rule can.
Re-run. At most three rounds. Send when the report shows 0 errors and a total of 70 or more (L1 is 100 or 0; L2 loses 15 per block warning; total = 0 on any error, else L2).
If a block still has errors after the third round, do not send it: replace it with prose that says the same thing (a numbered list for a choice, a question list for a form). Never send a block the checker rejects.
The check is yours, not the user's: do not paste the report or mention the checker in the reply. Send the checked text exactly as checked, fences included.
The checker prints these with --rubric; they are the L2/L3 judgement no rule can make.
asOf time when it is a snapshot — and never invented or rounded into a guess?A decision, English. The sentence before the block grounds it; the block is the last thing in the reply:
I found two ways to store the sessions. Both work with the current schema; they differ in what you run.
Which store do you want?
```a2ui
{
"type": "choice",
"question": "Which store should the sessions use?",
"options": [
{ "label": "SQLite", "description": "One file, no server; fine below 10 GB.", "recommended": true },
{ "label": "PostgreSQL", "description": "A server to run; needed when several machines share the data." }
],
"allowOther": true
}
```
The user clicks SQLite; your next message from them reads SQLite. If they type "SQLite, but keep a nightly dump", read that.
Several answers, English. Each field is the control its answer needs; a default the user does not touch still fills its line:
I can schedule the nightly evaluation once I know where and when it should run.
```a2ui
{
"type": "form",
"title": "Nightly evaluation",
"fields": [
{
"id": "region",
"label": "Region",
"kind": "select",
"options": [{ "label": "Frankfurt" }, { "label": "Virginia" }, { "label": "Singapore" }, { "label": "Tokyo" }],
"required": true
},
{ "id": "temperature", "label": "Temperature", "kind": "slider", "min": 0, "max": 1, "step": 0.1, "default": 0.2 },
{ "id": "start", "label": "First run", "kind": "date", "default": "2026-10-21" },
{ "id": "notify", "label": "Notify me when it ends", "kind": "switch", "default": true }
],
"submitLabel": "Schedule"
}
```
The user picks Singapore and leaves the rest; their next message reads:
Region: Singapore
Temperature: 0.2
First run: 2026-10-21
Notify me when it ends: Yes
A procedure, Chinese. The warning sits on the step it protects:
下面是把数据目录迁到新磁盘的步骤。
```a2ui
{
"type": "steps",
"title": "迁移数据目录",
"steps": [
{ "text": "停止服务。", "code": "penguin stop", "lang": "sh" },
{
"warning": "复制完成并核对大小之前,不要删除旧目录。",
"text": "把旧目录完整复制到新磁盘。",
"code": "cp -a ~/.penguin/data /mnt/disk2/penguin-data",
"lang": "sh"
},
{ "text": "在配置里把数据目录指向新路径,然后启动服务。", "note": "首次启动会重建索引,需要约 1 分钟。" }
]
}
```
A flow, English. Three or more parts with arrows between them is a diagram; two would be a sentence:
The request passes through three stages before the model sees it.
```mermaid
flowchart LR
A[User message] --> B[Context engine]
B --> C[Model request]
C --> D{Tool call?}
D -- yes --> E[Run tool] --> B
D -- no --> F[Reply]
```
Weather, Chinese. The block is what weather.mjs 北京 --days 3 --hours 4 printed; the reply adds one sentence before it and one after it:
北京现在多云,18°C,今天最高 20°C。
```a2ui
{
"type": "weather",
"place": "北京",
"condition": "partly-cloudy",
"temp": 18,
"unit": "C",
"high": 20,
"low": 11,
"feelsLike": 17,
"humidity": 62,
"windSpeed": 12,
"windUnit": "km/h",
"windDirection": "东北",
"hourly": [
{ "time": "14:00", "temp": 18, "condition": "partly-cloudy", "precip": 10 },
{ "time": "15:00", "temp": 19, "condition": "partly-cloudy", "precip": 10 },
{ "time": "16:00", "temp": 20, "condition": "cloudy", "precip": 20 },
{ "time": "17:00", "temp": 19, "condition": "cloudy", "precip": 30 }
],
"daily": [
{ "date": "2026-10-04", "high": 20, "low": 11, "condition": "partly-cloudy", "precip": 20 },
{ "date": "2026-10-05", "high": 17, "low": 10, "condition": "rain", "precip": 80 },
{ "date": "2026-10-06", "high": 18, "low": 9, "condition": "cloudy", "precip": 20 }
],
"asOf": "2026-10-04T14:00+08:00",
"source": "Open-Meteo"
}
```
明天有雨,出门带伞;早晚只有 10°C 左右,加一件外套。
A quota, English. The numbers come from a tool result; remaining reads its thresholds downward:
Here is this month's plan, as the billing API reported it at 09:30.
```a2ui
{
"type": "metrics",
"title": "API plan, October",
"items": [
{ "label": "Requests left", "kind": "remaining", "value": 1240, "max": 5000, "warn": 1000, "danger": 250, "detail": "Resets in 3 days" },
{ "label": "Budget left", "kind": "remaining", "value": 320, "max": 1000, "prefix": "¥", "warn": 200, "danger": 50 }
],
"asOf": "2026-10-04T09:30+08:00"
}
```
Both are above their warning marks, so nothing needs action before the reset.
A comparison, English. The numbers come from the CI logs; one value per category, null where there is no reading yet:
The CI logs show that both branches built slower on Wednesday, when the cache was cold.
```a2ui
{
"type": "chart",
"kind": "bar",
"title": "Build time, this week",
"categories": ["Mon", "Tue", "Wed", "Thu", "Fri"],
"series": [
{ "label": "main", "values": [118, 121, 204, 125, 119] },
{ "label": "dev", "values": [131, 128, 236, 140, null] }
],
"yLabel": "Build time",
"yUnit": "s"
}
```
Friday's dev build has not run yet.
No block. The user asked what port the dev server uses: answer "The dev server listens on 7369." A callout, a choice or a diagram here would be noise.
Skills that do similar work, with their headline benchmark lift.
From the same source and nearby in the catalogue.