Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Run charter interview, generation, context, and sync workflows for project governance in Spec Kitty 3.x. Access doctrine artifacts programmatically via DoctrineService. Resolve agent profiles. Load action-scoped governance context iteratively, not all at once. Triggers: "interview for charter", "generate charter", "sync charter", "use doctrine", "set up governance", "charter status", "extract governance config", "load doctrine", "agent profile", "DoctrineService", "action index". Does NOT handle
SkillScore90/100
Test results 90
3/3 safety checks passed
20%90%of test cases passed +70 pts
Passed 18 of 20 test cases with the skill, and 4 of 20 without it.
14 fixed4 passed either way2 still failing0 broke2 errored
One test taskWhat spec-kitty CLI command lists all agent profiles available in the current project?
Adds every file the skill has, pinned so you can update it. No account needed.
pip install "decimalai>=0.15"
decimalai skills add priivacy-ai-spec-kitty-charter-doctrine
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("priivacy-ai-spec-kitty-charter-doctrine")
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 priivacy-ai-spec-kitty-charter-doctrineThe 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/priivacy-ai-spec-kitty-charter-doctrine/SKILL.md https://app.decimal.ai/s/priivacy-ai-spec-kitty-charter-doctrine/SKILL.md.claude/skills/priivacy-ai-spec-kitty-charter-doctrine/SKILL.mdThis gets SKILL.md only. The skill's 2 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 priivacy-ai-spec-kitty-charter-doctrineimport 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("priivacy-ai-spec-kitty-charter-doctrine")
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/priivacy-ai-spec-kitty-charter-doctrine/SKILL.mdimport asyncio
from pathlib import Path
from claude_agent_sdk import ClaudeAgentOptions, query
skill = Path(".agents/skills/priivacy-ai-spec-kitty-charter-doctrine/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 2 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 priivacy-ai-spec-kitty-charter-doctrinefrom 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("priivacy-ai-spec-kitty-charter-doctrine")
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/priivacy-ai-spec-kitty-charter-doctrine/SKILL.mdfrom pathlib import Path
from agents import Agent, Runner
skill = Path(".agents/skills/priivacy-ai-spec-kitty-charter-doctrine/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 2 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 priivacy-ai-spec-kitty-charter-doctrineimport 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("priivacy-ai-spec-kitty-charter-doctrine")
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/priivacy-ai-spec-kitty-charter-doctrine/SKILL.mdfrom pathlib import Path
from openai import OpenAI
client = OpenAI()
skill = Path(".agents/skills/priivacy-ai-spec-kitty-charter-doctrine/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 2 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 priivacy-ai-spec-kitty-charter-doctrineimport 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("priivacy-ai-spec-kitty-charter-doctrine")
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/priivacy-ai-spec-kitty-charter-doctrine/SKILL.mdfrom pathlib import Path
import anthropic
client = anthropic.Anthropic()
skill = Path(".agents/skills/priivacy-ai-spec-kitty-charter-doctrine/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 2 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 priivacy-ai-spec-kitty-charter-doctrinefrom 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("priivacy-ai-spec-kitty-charter-doctrine")
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/priivacy-ai-spec-kitty-charter-doctrine/SKILL.mdfrom pathlib import Path
from google import genai
from google.genai import types
client = genai.Client()
skill = Path(".agents/skills/priivacy-ai-spec-kitty-charter-doctrine/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 2 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 priivacy-ai-spec-kitty-charter-doctrinefrom 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("priivacy-ai-spec-kitty-charter-doctrine")
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/priivacy-ai-spec-kitty-charter-doctrine/SKILL.mdfrom pathlib import Path
from langchain.agents import create_agent
skill = Path(".agents/skills/priivacy-ai-spec-kitty-charter-doctrine/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 2 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 priivacy-ai-spec-kitty-charter-doctrineimport 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("priivacy-ai-spec-kitty-charter-doctrine")
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/priivacy-ai-spec-kitty-charter-doctrine/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 2 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 priivacy-ai-spec-kitty-charter-doctrinefrom 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("priivacy-ai-spec-kitty-charter-doctrine")
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/priivacy-ai-spec-kitty-charter-doctrine/SKILL.mdfrom pathlib import Path
from google.adk.agents import LlmAgent
skill = Path(".agents/skills/priivacy-ai-spec-kitty-charter-doctrine/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 2 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 priivacy-ai-spec-kitty-charter-doctrinefrom 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("priivacy-ai-spec-kitty-charter-doctrine")
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/priivacy-ai-spec-kitty-charter-doctrine/SKILL.mdfrom pathlib import Path
from pydantic_ai import Agent
skill = Path(".agents/skills/priivacy-ai-spec-kitty-charter-doctrine/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 2 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 priivacy-ai-spec-kitty-charter-doctrineimport 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("priivacy-ai-spec-kitty-charter-doctrine")
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/priivacy-ai-spec-kitty-charter-doctrine/SKILL.mdfrom pathlib import Path
from deepagents import create_deep_agent
skill = Path(".agents/skills/priivacy-ai-spec-kitty-charter-doctrine/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 2 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 priivacy-ai-spec-kitty-charter-doctrineimport 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("priivacy-ai-spec-kitty-charter-doctrine")
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/priivacy-ai-spec-kitty-charter-doctrine/SKILL.mdimport asyncio
from pathlib import Path
from agent_framework import Agent
skill = Path(".agents/skills/priivacy-ai-spec-kitty-charter-doctrine/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 2 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 priivacy-ai-spec-kitty-charter-doctrinefrom 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("priivacy-ai-spec-kitty-charter-doctrine")
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/priivacy-ai-spec-kitty-charter-doctrine/SKILL.mdfrom pathlib import Path
from strands import Agent
skill = Path(".agents/skills/priivacy-ai-spec-kitty-charter-doctrine/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 2 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 priivacy-ai-spec-kitty-charter-doctrineNo Python or account. Cursor, Copilot, Cline and Warp read this folder by themselves.
curl -fsSL --create-dirs -o .agents/skills/priivacy-ai-spec-kitty-charter-doctrine/SKILL.md https://app.decimal.ai/s/priivacy-ai-spec-kitty-charter-doctrine/SKILL.md.agents/skills/priivacy-ai-spec-kitty-charter-doctrine/SKILL.mdThis gets SKILL.md only. The skill's 2 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 2 other files are not included.
Raw files: /s/priivacy-ai-spec-kitty-charter-doctrine/SKILL.md · /s/priivacy-ai-spec-kitty-charter-doctrine.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/priivacy-ai-spec-kitty-charter-doctrine/SKILL.mdcurl -fsSL https://app.decimal.ai/s/priivacy-ai-spec-kitty-charter-doctrine.jsonThis gets SKILL.md only. The skill's 2 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.
Manage the charter lifecycle: interview, generate, context-load, sync,
and status. Access doctrine artifacts programmatically via DoctrineService.
Resolve agent profiles for role-scoped behavior. Load governance context
iteratively at action boundaries rather than dumping everything upfront.
.kittify/charter/charter.yaml is the Spec Kitty runtime governance
source for a project — the single git-tracked, structured file nesting
governance, directives, catalog, activation, and overrides.
.kittify/charter/charter.md is a curated, human-readable companion the
runtime never parses or resolves policy from. A repository may also keep
public governance docs outside .kittify/; those docs are human-facing
authority unless charter.yaml's governance.charter.governance_references
points at them. The doctrine layer (src/charter/offering/) provides the
reusable knowledge artifacts (directives, tactics, paradigms, styleguides,
toolguides, procedures, agent profiles, step contracts) that the charter
references.
spec-kitty is a CLI tool invoked BY an LLM harness (Claude Code, Codex, Cursor, Gemini, etc.). The LLM harness IS the inference engine. spec-kitty must NEVER call any LLM API itself.
This is not a preference. It is a hard architectural rule with no exceptions.
| What you are reading this in | What that means |
|---|---|
| Claude Code | Claude IS already running. It should generate doctrine. |
| Codex CLI | Codex IS already running. It should generate doctrine. |
| Cursor / Gemini / Kiro / ... | Same — the harness IS the inference engine. |
If spec-kitty called the Anthropic SDK internally:
ANTHROPIC_API_KEY — a second API key alongside the one
the harness already uses.Charter synthesis is an LLM reasoning task. YOU (the agent running this skill) are the LLM that should do it.
The synthesis workflow is:
answers.yaml (the interview answers — what the user told spec-kitty about the project)
+
doctrine schemas (the YAML shapes expected for directives, tactics, styleguides)
+
THIS SKILL (the instructions you are reading right now)
↓
YOU generate the doctrine YAML files
↓
spec-kitty CLI commands (validate, stage, and promote the files you wrote)
spec-kitty's CLI provides validation, schema-checking, DRG validation, neutrality gating, staging, and atomic promotion. It does NOT provide generation — that is your job as the agent.
anthropic.Anthropic() or imports the anthropic SDKinside spec-kitty source files, that is a bug. Remove it immediately.
Canonical locations that must NEVER contain import anthropic or any Anthropic SDK call:
src/charter/ — any filesrc/specify_cli/ — any filepyproject.toml — must not list anthropic as a runtime dependencyWhen a user says "synthesize charter doctrine", "generate project doctrine", or "run charter synthesize", you do the following — you do NOT call any CLI command that triggers an LLM. You ARE the LLM.
cat .kittify/charter/interview/answers.yaml
This file contains the user's responses: project intent, languages, testing requirements, quality gates, etc.
The current synthesis scope is: directive, tactic, styleguide.
Read shipped examples to understand the expected YAML shape.
There is no doctrine list or doctrine show CLI command — use the programmatic
DoctrineService API (documented in the Programmatic Doctrine Access section below)
or read the YAML files directly from packs/built-in/<kind>/ (artifacts live at
<type>/<pack>/[<category>/]<name> — ADR 2026-07-26-2):
from charter.activation.doctrine_service_builder import build_activation_aware_doctrine_service
service = build_activation_aware_doctrine_service(project_root)
# Read a directive
directive = service.directives.get("<a-directive-id>")
# Read a tactic
tactic = service.tactics.get("<a-tactic-id>")
# Read a styleguide
styleguide = service.styleguides.get("<a-styleguide-id>")
To validate your project-layer doctrine artifacts run:
spec-kitty charter validate .kittify/doctrine
The interview fields map to target artifact kinds:
project_intent, quality_gates, risk_boundaries → directivestesting_requirements → directives + tactics (TDD flavour)languages_frameworks → styleguides (language-specific)performance_targets, deployment_constraints → directivesFor each synthesis target, derive: kind, slug (kebab-case, project-specific),
title, and body (the full artifact content as YAML matching the shipped schema).
All five supported project kinds are directive, tactic, styleguide,
procedure, and agent_profile. For direct authoring, run charter new <kind> <id>, complete the scaffold, and run charter validate .kittify/doctrine.
charter activate agent-profile <id> --cascade all registers authored project
artifacts in the DRG and provenance manifest and activates referenced guidance.
Project procedures live in .kittify/doctrine/procedure/; profiles live in
.kittify/doctrine/agent_profiles/. These source files remain user-owned.
For interview-generated directives, tactics and styleguides, use the generated input path below. Procedures and profiles use the direct-authoring path above.
The harness writes artifact inputs here; spec-kitty charter synthesize
validates, stages, and promotes them into the live doctrine tree.
.kittify/charter/generated/
directives/
<NNN>-<slug>.directive.yaml
tactics/
<slug>.tactic.yaml
styleguides/
<slug>.styleguide.yaml
Use the shipped artifact YAML structure as your template. Make the content specific to the project based on the interview answers. Do not write generic filler.
spec-kitty charter synthesize --dry-run
This is a real stage-and-validate pass. It writes the agent-authored artifacts into the staging tree, runs schema validation, project DRG validation, and the neutrality gate, then wipes the staging directory on success.
spec-kitty charter synthesize
By default this reads from .kittify/charter/generated/ via the generated
adapter and promotes the validated outputs into:
.kittify/doctrine/ for artifact content and project graph.yaml.kittify/charter/provenance/ plus synthesis-manifest.yaml for bookkeepingList what the synthesis actually wrote, then commit exactly those files by name (never a blanket or directory add):
git status --porcelain --untracked-files=all -- .kittify/doctrine .kittify/charter/provenance .kittify/charter/synthesis-manifest.yaml
spec-kitty safe-commit <each path git status listed> -m "feat(charter): promote project-local doctrine from generated inputs"
A renamed file is listed as R old -> new: pass both paths to safe-commit, or the old path stays in HEAD and a staged deletion is left behind.
The charter is a governance-as-code framework. A single git-tracked,
structured YAML file — charter.yaml — holds the project policy directly;
the runtime reads it without any parse/extract step in between.
Structured charter (.kittify/charter/charter.yaml) — The single
authoritative source. Nests four kinds of section:
governance / directives — hand-authored policy (testing,
quality, performance, branching, doctrine selections; numbered project
rules with severity and scope). Edit these directly; nothing overwrites
them except a deliberate hand edit.activated_kinds, activated_directives,
mission_type_activations, …) — hand-authored, mirrors
src/charter/activation/packs/default.yaml.catalog / metadata — generator-refreshed. charter generate
rewrites these two sections deterministically on every run (doctrine
reference manifest, generation timestamp); everything else in the file
is preserved byte-for-byte through a round-trip merge.overrides — hand-authored, forward-compatible project doctrine
overrides.Curated companion (.kittify/charter/charter.md) — Human-editable
markdown. Written by hand, or by an agent during /spec-kitty.charter's
chat flow — never by charter generate. The runtime never
parses it for policy — it exists purely for human review/onboarding, and
optionally to summarize charter.yaml or point at a public constitution.
If the repository also has a public constitution or handbook, reference
it from charter.yaml's governance.charter.governance_references (or
authority_paths), not just from the companion prose.
.kittify/config.yaml carries a single charter: pointer (default
.kittify/charter/charter.yaml) that resolves the active charter file —
useful for redirecting to a shared or cross-project charter.
Interview Answers (answers.yaml)
|
v
[generate command] <-- doctrine templates, mission config
|
v
charter.yaml
governance: <-- hand-authored, preserved byte-for-byte by generate
directives: <-- hand-authored, preserved byte-for-byte by generate
activated_*: <-- hand-authored, preserved byte-for-byte by generate
catalog: <-- REFRESHED every run (doctrine reference manifest)
metadata: <-- REFRESHED every run (generation timestamp)
|
v
[context command] at each workflow action
|
v
Text injected into agent prompt
charter.md sits beside this flow as a hand-authored (or agent-authored,
e.g. via /spec-kitty.charter's chat flow) companion — generate never
writes it, and it is never read back in.
governance and directives deserialize straight into the existing
GovernanceConfig / DirectivesConfig models — the same shapes the old
governance.yaml / directives.yaml used, now nested inside charter.yaml
instead of living in separate files:
governance:
testing:
min_coverage: 90 # Minimum test coverage %
tdd_required: false # TDD mandatory
framework: <project-runner> # Test framework / runner
type_checking: "<project-type-checker>" # Type checker
quality:
linting: "<project-linter>" # Linter
pr_approvals: 1 # Required approvals before merge
pre_commit_hooks: false # Pre-commit hooks required
commits:
convention: conventional # Commit convention (or null)
performance:
cli_timeout_seconds: 2.0 # Max CLI command duration
dashboard_max_wps: 100 # Max work packages dashboard displays
branch_strategy:
main_branch: main # Primary branch
dev_branch: null # Development branch (optional)
rules: [] # Branch naming/protection rules
charter:
selected_paradigms: [] # Active paradigm IDs
selected_directives: [] # Active directive IDs
available_tools: [] # Active tool IDs
template_set: null # Mission template set
authority_paths: [] # Directories surfaced as required reading
governance_references: [] # Supporting external governance documents
enforcement: {} # Enforcement policy by domain
directives:
- id: DIR-001 # Auto-generated or custom ID
title: "Short title" # First 50 chars
description: "Full text" # Full description
severity: warn # error (blocks), warn (displayed), info (logged)
applies_to: [implement, review] # Actions where directive fires
There is no keyword-classified prose parser any more — the old "sync"
extraction that scanned charter.md headings and regex-matched values like
90%+ coverage is retired. Populate these sections directly (by hand, or
through the interview → charter generate bootstrap path) instead of
writing prose you expect a parser to interpret.
charter.yaml is the sole content-hash input (content_hash_files) for the
bundle's freshness signal — a SHA-256 over the file itself. There is no
charter.md-hash staleness check any more: metadata intentionally carries
no self-referential charter_hash field (a hash of charter.yaml cannot
live inside charter.yaml). charter sync is retained only for
canonical-root resolution and back-compat call sites — it performs no
extraction and always reports synced=False.
When you run /spec-kitty.specify, /spec-kitty.plan, /spec-kitty.implement,
or /spec-kitty.review, the runtime automatically calls
spec-kitty charter context --action <action>. The returned text is
injected into the agent prompt.
Three context modes:
| Mode | When | Content |
|---|---|---|
bootstrap | First load for an action | Full policy summary (up to 8 bullets) + reference doc list (up to 10) |
compact | Subsequent loads | Resolved paradigms, directives, tools, template_set only |
missing | No charter exists | Instructions to create one |
First-load state is tracked in .kittify/charter/context-state.json.
Each action (specify, plan, implement, review) has an independent first-load
timestamp.
Doctrine organizes knowledge into 8 artifact kinds. Each kind has a
dedicated repository in DoctrineService, follows built-in -> org -> project
loading, and is accessible programmatically or via CLI.
Directives — Numbered project rules that constrain agent behavior.
Each directive has a severity (error, warn, info), an applies_to
scope listing which actions it fires on, and may reference tactics.
Directives are the what you must do layer.
directive = service.directives.get("DIRECTIVE_034")
# directive.title → "Test-First Development"
# directive.severity → "warn"
# directive.applies_to → ["implement", "review"]
# service.directives is a filtered dict (no .list_all()); to enumerate every
# directive regardless of activation, use the raw-repository escape hatch:
# service.raw_repository("directives").list_all()
# ...or read packs/built-in/directives/ directly.
Tactics — Reusable implementation approaches that describe how to do something. Tactics cover testing (TDD, ZOMBIES, acceptance-test-first), domain modeling (bounded context, aggregate boundaries), refactoring (strangler fig, extract class), review (intent-and-risk-first), and planning (problem decomposition, eisenhower). The shipped set includes a refactoring sub-catalog.
tactic = service.tactics.get("tdd-red-green-refactor")
# tactic.title, tactic.description, tactic.steps
Paradigms — High-level development philosophies that group related
tactics and directives. A paradigm (e.g., domain-driven-design) declares
which tactics it recommends. Paradigms are selected during the charter
interview and scope which tactics appear in governance context.
paradigm = service.paradigms.get("domain-driven-design")
# paradigm.tactics → ["bounded-context-identification", ...]
Styleguides — Language- or domain-specific writing and coding style
rules. Applied when the charter's languages_frameworks answer
matches the styleguide's target language.
styleguide = service.styleguides.get("python-conventions")
Toolguides — Operational guidance for specific tools. Teaches agents how to use git, the project's test runner, diagramming tools, etc. within the project's governance constraints.
toolguide = service.toolguides.get("efficient-local-tooling")
Procedures — Multi-step workflow primitives with prerequisites and ordered steps. Procedures are the reusable building blocks that step contracts delegate to. They describe a complete mini-workflow (e.g., "refactoring", "test-first-bug-fixing", "situational-assessment").
procedure = service.procedures.get("refactoring")
# procedure.steps → ordered list of actions
# procedure.prerequisites → what must be true before starting
Agent Profiles — Role definitions with these sections: purpose,
specialization, collaboration, mode_defaults, and initialization_declaration.
Doctrine references are authored on the top-level *-references fields
(directive-references, tactic-references, toolguide-references,
styleguide-references) — the retired context-sources block was removed
(mission doctrine-drg-silent-drop-boundary; #3629). Relationship fields such
as specializes_from are not profile fields; lineage belongs in the doctrine
DRG. Profiles support weighted matching against task context (DDR-011 algorithm).
profile = service.agent_profiles.get("implementer")
# profile.purpose.mandate → what this agent is responsible for
# profile.specialization.boundaries → what it should not do
# `find_best_match` is a repository method, not available on the filtered
# `agent_profiles` dict above — reach it through the pinned lineage/mutation
# accessor instead:
best = service.agent_profile_repository.find_best_match(task_context)
spec-kitty agent profile list
spec-kitty agent profile show implementer
Step Contracts — Structured action definitions that link public actions
(specify, plan, implement, review) to doctrine artifacts via DelegatesTo.
Each contract defines ordered steps; each step may delegate to a tactic,
directive, or procedure by kind and candidate list.
contract = service.mission_step_contracts.get("implement")
for step in contract.steps:
if step.delegates_to:
# Load the referenced doctrine artifact
artifact = getattr(service, step.delegates_to.kind + "s").get(
step.delegates_to.candidates[0]
)
There is no doctrine list or doctrine show CLI command. Use the programmatic
DoctrineService API or read artifact YAML files directly:
from charter.activation.doctrine_service_builder import build_activation_aware_doctrine_service
service = build_activation_aware_doctrine_service(project_root)
# List or inspect artifacts by kind
directive = service.directives.get("DIRECTIVE_034")
tactic = service.tactics.get("tdd-red-green-refactor")
paradigm = service.paradigms.get("<paradigm-id>")
# Shipped artifacts: packs/built-in/<kind>/
# Project-layer artifacts: .kittify/doctrine/<kind dir>/ (written by
# `spec-kitty charter new`; see Step 4 for the per-kind directories)
To validate project-layer artifacts:
spec-kitty charter validate .kittify/doctrine
To list registered mission types (every visible type, activated or not):
spec-kitty charter mission-type list --include-inactive
To list agent profiles:
spec-kitty agent profile list
Shipped artifacts live in packs/built-in/<kind>/. Project-local
overrides live in .kittify/<kind>/. Two-source loading merges both,
with project artifacts taking precedence on field-level merge.
Template sets (from packs/built-in/missions/):
software-dev-default — Core development workflowplan-default — Goal-oriented planningdocumentation-default — Documentation creation (Divio)research-default — Research and evidence gatheringDefault tool registry: spec-kitty, git
Minimal (8 questions — fast bootstrap):
| Question | Governance use |
|---|---|
project_intent | Policy summary, preamble |
languages_frameworks | Styleguide selection (e.g., Python) |
testing_requirements | testing.framework, testing.min_coverage |
quality_gates | Quality Gates section |
review_policy | quality.pr_approvals, Branch Strategy |
performance_targets | performance.cli_timeout_seconds |
deployment_constraints | branch_strategy.rules |
Comprehensive (11 questions — adds 4 more):
| Question | Governance use |
|---|---|
documentation_policy | Added to Project Directives |
risk_boundaries | Added to Project Directives |
amendment_process | Amendment Process section |
exception_policy | Exception Policy section |
schema_version: "1.0.0"
mission: "software-dev"
profile: "minimal"
answers:
project_intent: "..."
languages_frameworks: "..."
testing_requirements: "..."
quality_gates: "..."
review_policy: "..."
performance_targets: "..."
deployment_constraints: "..."
# comprehensive only:
documentation_policy: "..."
risk_boundaries: "..."
amendment_process: "..."
exception_policy: "..."
selected_paradigms:
- "<project-paradigm>"
selected_directives:
- "<project-directive>"
available_tools:
- "spec-kitty"
- "git"
- "<project-tool>"
spec-kitty charter status --json
Reports synced or stale, current and stored hashes, library doc count,
and per-file sizes. governance/directives are always read live from
charter.yaml — a stale report here does not mean governance config is
out of date; it is a legacy staleness signal, not a gate on correctness.
For agent-mediated governance setup and revision, the preferred discovery surface is the chat itself, not the CLI questionnaire.
Recommended flow:
.kittify/charter/interview/answers.yaml directly.spec-kitty charter generate --from-interview --json.Use the CLI interview only as a fallback when the user explicitly wants the CLI prompt loop or wants deterministic defaults.
CLI defaults path (fallback only):
spec-kitty charter interview --mission-type software-dev --profile minimal --defaults --json
CLI comprehensive path (fallback only):
spec-kitty charter interview --mission-type software-dev --profile comprehensive
Key flags: --profile minimal|comprehensive, --defaults, --json,
--selected-paradigms, --selected-directives, --available-tools.
See references/charter-command-map.md for all flags.
Output: .kittify/charter/interview/answers.yaml
spec-kitty charter generate --from-interview --json
Key flags: --mission-type, --template-set, --force, --from-interview, --json.
Generation refreshes charter.yaml's catalog and metadata sections
deterministically. governance/directives/activation/overrides are
preserved byte-for-byte through the shared round-trip merge (bootstrapped
from a legacy triad only the first time charter.yaml is created).
charter.md is never written by this command.
Output: .kittify/charter/charter.yaml (refreshed sections).
To commit the generated charter inputs, use:
spec-kitty safe-commit --message "chore: generate project charter" \
.kittify/charter/interview/answers.yaml \
.kittify/charter/charter.yaml \
.gitignore
Do not preload all action contexts after generation.
The runtime calls context automatically during slash commands. Manual invocation is useful only for debugging one immediate action, and should avoid consuming first-load state:
spec-kitty charter context --action specify --json --no-mark-loaded
Load context iteratively at the action boundary, not as part of charter setup.
charter.yamlEdit charter.yaml's governance: or directives: sections directly —
there is no separate sync step. The next charter context call reads the
file as-is.
spec-kitty charter sync --json # retained for back-compat; always a no-op
charter sync no longer extracts anything from charter.md. It always
reports synced=False / files_written=[], regardless of --force.
charter.activation.resolver.DoctrineService — built through
charter.activation.doctrine_service_builder.build_activation_aware_doctrine_service —
is the single, sanctioned entry point for programmatic access to all doctrine
artifacts. It wraps the inner charter.offering.service.DoctrineService and applies
charter activation filtering; never construct charter.offering.service.DoctrineService
or charter.offering.agent_profiles.AgentProfileRepository directly (five
architectural gates in tests/architectural/ ban that construction outside
this module).
from charter.activation.doctrine_service_builder import build_activation_aware_doctrine_service
service = build_activation_aware_doctrine_service(project_root)
Nine properties are activation-gated: each returns a plain dict[str, <Artifact>]
filtered to whatever the charter has activated (or every shipped artifact of
that kind, when nothing is configured). A dict supports .get(id) but not
repository-only operations like .list_all(), .save(), or
find_best_match() — see the raw-repository escape hatch below for those.
| Property | Returns | Artifacts |
|---|---|---|
service.agent_profiles | dict[str, AgentProfile] | Agent role profiles with DDR-011 matching |
service.directives | dict[str, Directive] | Numbered project rules (TEST_FIRST, etc.) |
service.tactics | dict[str, Tactic] | Reusable implementation approaches (TDD, ZOMBIES, etc.) |
service.styleguides | dict[str, Styleguide] | Language/domain writing style guides |
service.toolguides | dict[str, Toolguide] | Tool-specific operational guidance |
service.paradigms | dict[str, Paradigm] | High-level development paradigms |
service.procedures | dict[str, Procedure] | Multi-step reusable workflow primitives |
service.mission_step_contracts | dict[str, MissionStepContract] | Structured action contracts with delegation |
service.glossary_packs | dict[str, GlossaryPack] | Canonical terminology packs |
.get() works directly on the gated dict:
# Get a specific artifact by ID
tactic = service.tactics.get("tdd-red-green-refactor")
Operations the filtered dict cannot support — .list_all(), .save(),
.get_provenance() — go through the raw-repository escape hatch,
service.raw_repository(kind), which returns the underlying repository
object unfiltered (a deliberate, sanctioned bypass of activation filtering,
not of construction: it still comes from the one wrapped service, never a
second DoctrineService()):
# List all artifacts of a kind (raw repository, not the filtered dict)
all_tactics = service.raw_repository("tactics").list_all()
# Save a project-local artifact (procedures, step contracts)
service.raw_repository("procedures").save(my_procedure)
Agent profiles support weighted context-based matching and specializes_from
lineage traversal. Both are repository operations, not available on the
filtered agent_profiles dict, so they go through the pinned
agent_profile_repository accessor instead of raw_repository("agent_profiles"):
from charter.offering.agent_profiles.profile import TaskContext
context = TaskContext(
language="python",
framework="typer",
file_paths=["src/specify_cli/cli.py"],
keywords=["cli", "testing"],
)
profile = service.agent_profile_repository.find_best_match(context)
# profile.purpose.mandate → what this agent is responsible for
# profile.specialization.boundaries → what it should not do
# profile.initialization_declaration → startup context text
Profile lineage is represented by DRG edges, not a specializes_from field on
profile YAML. Language-specific profiles can still be related to base roles in
the DRG and resolved through profile matching.
Each mission action (specify, plan, implement, review) has an action index that lists which doctrine artifacts are relevant to that step:
from charter.offering.missions.action_index import load_action_index
index = load_action_index(missions_root, "software-dev", "implement")
# index.directives → ["TEST_FIRST"]
# index.tactics → ["tdd-red-green-refactor", "zombies-tdd"]
# index.procedures → ["implementation-handoff"]
The charter context builder uses these indices internally. When you call
spec-kitty charter context --action implement, only the doctrine
artifacts listed in the implement action index are included.
Step contracts define the structure of each public action and link to
doctrine artifacts via DelegatesTo:
contract = service.mission_step_contracts.get("implement")
for step in contract.steps:
if step.delegates_to:
# step.delegates_to.kind → ArtifactKind (e.g., "tactic")
# step.delegates_to.candidates → ["tdd-red-green-refactor", ...]
pass
This is the bridge between the mission execution surface and the doctrine knowledge layer. Step contracts say what to do; doctrine artifacts say how.
Agents should load doctrine context iteratively, not all at once. The architecture supports this through depth-controlled context and per-artifact retrieval.
initialization_declaration.charter context --action <action>.
First call gets bootstrap (depth-2), subsequent calls get compact (depth-1).DoctrineService.Each doctrine artifact consumes tokens. Loading all directives, tactics, paradigms, and styleguides at session start wastes context on artifacts that are irrelevant to the current action. Action indices exist specifically to scope which artifacts matter for each step.
Doctrine constrains runtime behavior when the charter has been generated
and the agent is executing a workflow action (specify, plan, implement, review).
The specific constraints come from the project's own charter — load them
with spec-kitty charter context --action <action> --json rather than
assuming fixed policy values.
Doctrine does NOT constrain when:
charter.yaml's generated sections — catalog and
metadata are overwritten by charter generate on every run. Edit
governance/directives/activation/overrides instead — those are
preserved byte-for-byte.charter.md edits to change runtime policy — the runtime
never parses charter.md. Edit charter.yaml for policy changes;
charter.md is a curated companion only.charter sync expecting an effect — it is retained for
back-compat call sites only and always no-ops. There is no required
post-edit step after changing charter.yaml by hand..kittify/charter/charter.yaml, not .kittify/memory/ and not a
governance.yaml/directives.yaml/references.yaml triad.See references/doctrine-artifact-structure.md for the full anti-pattern table.
references/charter-command-map.md -- Full CLI command reference with all flags and output fieldsreferences/doctrine-artifact-structure.md -- File layout, authority classes, and data flowPublished skills whose descriptions closely match this one, with their headline benchmark lift.
Skills that do similar work, with their headline benchmark lift.
From the same source and nearby in the catalogue.