Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Set up, audit, verify, or fix website conversion tracking.
SkillScore86/100
Test results 86
3/3 safety checks passed
55%86%of test cases passed +32 pts
Passed 19 of 22 test cases with the skill, and 12 of 22 without it.
7 fixed12 passed either way3 still failing0 broke
One test taskOur custom tracking scripts on our standard Shopify store stopped firing after our agency updated our theme last month. What major Shopify platform architectural deadline removed checkout tracking scripts for standard merchants?
Adds every file the skill has, pinned so you can update it. No account needed.
pip install "decimalai>=0.15"
decimalai skills add coreyhaines31-conversion-tracking
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("coreyhaines31-conversion-tracking")
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 coreyhaines31-conversion-trackingThe 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/coreyhaines31-conversion-tracking/SKILL.md https://app.decimal.ai/s/coreyhaines31-conversion-tracking/SKILL.md.claude/skills/coreyhaines31-conversion-tracking/SKILL.mdThis gets SKILL.md only. The skill's 11 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 coreyhaines31-conversion-trackingimport 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("coreyhaines31-conversion-tracking")
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/coreyhaines31-conversion-tracking/SKILL.mdimport asyncio
from pathlib import Path
from claude_agent_sdk import ClaudeAgentOptions, query
skill = Path(".agents/skills/coreyhaines31-conversion-tracking/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 11 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 coreyhaines31-conversion-trackingfrom 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("coreyhaines31-conversion-tracking")
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/coreyhaines31-conversion-tracking/SKILL.mdfrom pathlib import Path
from agents import Agent, Runner
skill = Path(".agents/skills/coreyhaines31-conversion-tracking/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 11 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 coreyhaines31-conversion-trackingimport 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("coreyhaines31-conversion-tracking")
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/coreyhaines31-conversion-tracking/SKILL.mdfrom pathlib import Path
from openai import OpenAI
client = OpenAI()
skill = Path(".agents/skills/coreyhaines31-conversion-tracking/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 11 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 coreyhaines31-conversion-trackingimport 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("coreyhaines31-conversion-tracking")
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/coreyhaines31-conversion-tracking/SKILL.mdfrom pathlib import Path
import anthropic
client = anthropic.Anthropic()
skill = Path(".agents/skills/coreyhaines31-conversion-tracking/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 11 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 coreyhaines31-conversion-trackingfrom 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("coreyhaines31-conversion-tracking")
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/coreyhaines31-conversion-tracking/SKILL.mdfrom pathlib import Path
from google import genai
from google.genai import types
client = genai.Client()
skill = Path(".agents/skills/coreyhaines31-conversion-tracking/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 11 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 coreyhaines31-conversion-trackingfrom 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("coreyhaines31-conversion-tracking")
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/coreyhaines31-conversion-tracking/SKILL.mdfrom pathlib import Path
from langchain.agents import create_agent
skill = Path(".agents/skills/coreyhaines31-conversion-tracking/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 11 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 coreyhaines31-conversion-trackingimport 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("coreyhaines31-conversion-tracking")
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/coreyhaines31-conversion-tracking/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 11 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 coreyhaines31-conversion-trackingfrom 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("coreyhaines31-conversion-tracking")
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/coreyhaines31-conversion-tracking/SKILL.mdfrom pathlib import Path
from google.adk.agents import LlmAgent
skill = Path(".agents/skills/coreyhaines31-conversion-tracking/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 11 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 coreyhaines31-conversion-trackingfrom 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("coreyhaines31-conversion-tracking")
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/coreyhaines31-conversion-tracking/SKILL.mdfrom pathlib import Path
from pydantic_ai import Agent
skill = Path(".agents/skills/coreyhaines31-conversion-tracking/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 11 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 coreyhaines31-conversion-trackingimport 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("coreyhaines31-conversion-tracking")
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/coreyhaines31-conversion-tracking/SKILL.mdfrom pathlib import Path
from deepagents import create_deep_agent
skill = Path(".agents/skills/coreyhaines31-conversion-tracking/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 11 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 coreyhaines31-conversion-trackingimport 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("coreyhaines31-conversion-tracking")
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/coreyhaines31-conversion-tracking/SKILL.mdimport asyncio
from pathlib import Path
from agent_framework import Agent
skill = Path(".agents/skills/coreyhaines31-conversion-tracking/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 11 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 coreyhaines31-conversion-trackingfrom 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("coreyhaines31-conversion-tracking")
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/coreyhaines31-conversion-tracking/SKILL.mdfrom pathlib import Path
from strands import Agent
skill = Path(".agents/skills/coreyhaines31-conversion-tracking/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 11 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 coreyhaines31-conversion-trackingNo Python or account. Cursor, Copilot, Cline and Warp read this folder by themselves.
curl -fsSL --create-dirs -o .agents/skills/coreyhaines31-conversion-tracking/SKILL.md https://app.decimal.ai/s/coreyhaines31-conversion-tracking/SKILL.md.agents/skills/coreyhaines31-conversion-tracking/SKILL.mdThis gets SKILL.md only. The skill's 11 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 11 other files are not included.
Raw files: /s/coreyhaines31-conversion-tracking/SKILL.md · /s/coreyhaines31-conversion-tracking.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/coreyhaines31-conversion-tracking/SKILL.mdcurl -fsSL https://app.decimal.ai/s/coreyhaines31-conversion-tracking.jsonThis gets SKILL.md only. The skill's 11 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.
You help people set up conversion tracking that provably works, or find out exactly why their existing tracking doesn't. Everything you recommend should be fitted to what they actually run and where the data needs to go, and every finding you report should say how you learned it.
One rule sits above everything else here. If a test conversion can safely be fired, the job isn't done until one demonstrably arrived. And if it can't be fired, say plainly what remains unverified rather than implying it all works.
There are two kinds of conversions, and they break in completely different ways.
Lead generation covers things like a form being submitted, a meeting being booked, a chat being started, or a phone number being called. The tricky part is that the browser is often the only place these moments are reliably visible. Many sites have no backend record of "contact form submitted" (some do, through a form tool's webhook or the site's own backend, and those are worth using), so success usually has to be worked out from a signal in the page, and every form and booking tool signals success differently. Getting that signal wrong is the single most common cause of tracking that looks fine but is quietly wrong. Fire on the submit button and you count failed submissions and bots. Fire on a thank-you page and you count refreshes.
Ecommerce covers purchases and subscriptions. Here the hard part of lead gen simply doesn't exist, because an order record does. The store knows a purchase happened, so knowing the conversion is real is nearly free. The difficulty moves elsewhere instead. Is the value right (tax and shipping in or out, and in which currency)? Does the item data match the merchant feed? Are the browser pixel and the server event deduplicated so each sale counts once? Are the pre-purchase events (view item, add to cart, begin checkout) flowing, since that's what the platforms optimise on? And what happens when an order is refunded?
Work out which side of this line you're on before doing anything else. It decides which reference files apply, which failure modes to suspect, and which delivery options even make sense.
| The user's opening move | What to do |
|---|---|
| Nothing set up yet | Run the setup flow below |
| Something exists and it's misbehaving | Run the audit flow below |
| Something exists and they don't know if it works | Run the audit flow as a verification pass. There's no symptom to route on, so do the full evidence sweep |
| "Should we go server-side?" or "Is tool X worth it?" | Go straight to references/server-side.md and answer the question with the real options and real numbers. Don't put a decision question through a setup wizard |
| "Just give me the snippet for tool X" | Read assets/snippets/{tool}.js and hand over its contents verbatim, header included. Never retype or condense a snippet from memory, because the shipped file carries edge cases a rewrite silently drops. Name the dataLayer event it pushes and offer the one-minute console check. No interrogation first |
Setup and audit both run on the same three facts. In setup you're establishing them so you can build. In an audit you're establishing them so you know what should be true before you go looking at what is.
1. What counts as a conversion? Be precise here, because most people haven't actually decided. A confirmed form submission is not a click on the submit button, and a purchase is not someone starting checkout. Does a newsletter signup count? Are a quote request and a contact form one conversion or two? Settling this is the first place you add value, before any code gets touched. And ask whether the conversion that actually matters sits further down the funnel, a qualified lead or a closed deal only the CRM knows about. That has its own path and its own volume gates, covered in references/server-side.md under tracking what happens after the lead.
2. Where does it need to land? GA4 only, one ad platform, or several? This matters because it decides how much the lead's details (email, phone, name) matter. Ad platforms bid real money on these numbers and match on the lead's details, so the stakes are highest there. But an analytics-only setup still loses some conversions to ad blockers and the like (typically 10–30% depending on audience; magnitudes are in references/discrepancies.md), so server-side is worth mentioning for more accurate numbers even when no ad platform is involved.
3. What runs the moment? Which form, booking, chat, or checkout tool, what the site is built on, and whether Google Tag Manager is installed.
Look before you ask. Try to answer these three questions from evidence first, and only ask about what's left, in a single batched message. If .agents/product-marketing.md exists (or .claude/product-marketing.md, or the legacy product-marketing-context.md), read it first, since it often names the site platform, the CRM, and the ad channels. If you have the site's URL, fetch the page and read what already runs on it (existing tags, a GTM container ID, the consent banner, the form or cart tool). If you have the codebase, search it for form handlers and tracking calls. If you have access to the GTM container or the ad account, read it. A skill that interrogates the user for facts it could have observed is a wizard, not an agent.
There's one question you should always ask though, because no amount of page inspection can answer it. Is anything already sending conversions from a server or a native integration? Think Meta's Conversions API, a Shopify or HubSpot native channel, server-side GTM, offline uploads, or a Zapier job. None of these leave any trace in the page source, so a page with no visible tag can still be fully tracked. This matters in both directions. Never conclude tracking is missing just because you can't see it, and never add a new sender without knowing what's already sending, because a second sender is how double counting starts.
Step 1. Pin the three facts. Do the recon described above first, then ask one batched question covering whatever's left. If tags already exist on the site, switch into audit thinking before adding anything, because the classic setup failure is a second pixel or a second container quietly double counting everything the first one does.
Step 2. Work out how the moment signals success.
For lead gen, look the tool up in references/conversion-moments.md and find its real success signal. Every tool is different (a jQuery event, a DOM event, an embed callback, a redirect), and hooking the wrong one silently tracks attempts instead of outcomes. For a custom-coded form, open the submit handler and read it, because the most common silent failure on custom sites is a success branch with no tracking call in it at all. And if the moment is something only the backend knows about (an account created, a lead received by an API), then no browser event exists to detect, and the send has to come from the server. That doesn't only mean building it yourself. The options for a server-originated send run from your own build against the platform APIs to managed tools with a server-side API and SDK that pair the backend event with the browser's click IDs; they're in references/server-side.md. And for a moment with no form tool at all, like a phone call or a download, the universal patterns in the assets are the primary answer, not a fallback. Where the free path has a structural ceiling (a cross-origin iframe, whose submission the shipped snippets can often detect but whose lead details stay locked inside it, a checkout on someone else's domain, a moment only a backend knows), say so plainly and name the paths that get past it: the tool's own webhook or native integration (into Meta CAPI, or into Google Ads passing the gclid, as Google matches email only if its tag saw it), a managed tool, or a server-side send. No amount of tag work clears a structural ceiling.
For ecommerce, the order record is the source of truth, so use the platform's own confirmed-order surface rather than rebuilding the inference. On Shopify that means web pixels or a native channel. On WooCommerce it means the server-side order hooks. Platform specifics, including which older methods no longer work, are in references/ecommerce-platforms.md.
This is also the moment to push back on anti-patterns before building them. The common ones are button clicks tracked as conversions (that's intent, not an outcome), pricing-page views set up as Primary conversions (which teaches the bidding algorithm to buy browsers rather than buyers), the same real-world action counted by two different conversion actions, and lead forms set to count Every conversion instead of One. If the user still wants a soft signal tracked after hearing why it's a problem, set it up as Secondary so it never drives bidding, and tell them that's what you did.
Step 3. Choose the delivery path. This is a decision made on stated criteria, not a default.
references/discrepancies.md). Ad blockers stop the tag firing at all, Safari's cookie limits erase the click ID before many people come back to convert, and cross-device journeys (the ad clicked on a phone, the form filled on a laptop) leave the converting browser with no click ID to report. And a default tag carries little to match on: the click ID from its cookie, the IP address, and the user agent. The free fix comes first. Browser tags can send hashed email and phone too: Google's Enhanced Conversions for web (the Google tag, or GTM's user-provided data variable), Meta's automatic and manual advanced matching, and TikTok's ttq.identify. Turn these on before anything paid. This skill's snippets send no personal data, so the identity piece is configured on the tag. The two problems are linked, because identity data is what lets a platform tie a cross-device or expired-cookie conversion back to the person who clicked, and browser-sent identity data rides the same blocker and cookie losses as the tag itself. Visitors who decline tracking are a separate case. No method counts them, server-side included, and none should, so never present consent as a loss any tool recovers. For analytics-only destinations the match-data half doesn't apply, since analytics tools take no personal data, but server-side still recovers the conversions lost to ad blockers and gives more accurate numbers, so present it as an option there too. Don't tell an analytics-only user the loss doesn't matter. Their reports still undercount, even when no bidding depends on them.references/server-side.md. Present the options that fit. Never present one answer.One rule ties these together. When the user is choosing a delivery path, or the browser-side loss bears on their question, say in a sentence that server-side paths exist and where the options are, so they can choose. Don't append tool names or prices to a setup, audit fix, or snippet request that didn't ask about delivery; give the detail when they ask or reach the delivery decision. An ad-platform setup reaches that decision when the user mentions ad blockers, iOS or ATT signal loss, or conversions the platform is missing or under-reporting; then walk through the options in references/server-side.md, free and native paths first. Server-side matters less in a short testing phase, for Google Ads only with Enhanced Conversions on and few ad blockers in the audience, or when Meta advanced matching is on and blockers are the only gap left. And when you compare paths, compare on the dimensions that actually differ, not just price: what data travels with the conversion (an event alone, or the lead's identity and click IDs, which is what match quality and Enhanced Conversions run on), whether delivery is observable (a per-conversion log, or silence when it breaks), and who can do the setup (agent-driveable in minutes, or a build someone owns).
Step 4. Build it. The platform mechanics live in references/google-ads.md, references/meta.md, references/other-platforms.md, and references/browser-side.md. For GTM builds on a covered lead-gen tool, prefer generating the import file rather than hand-building tags:
python3 scripts/build_recipe.py --tool {tool} --send {platform} --{ids}
That produces one file bundling the detection listener, the custom event trigger, the Conversion Linker, and the conversion tag, all wired together. The user imports it with MERGE and reviews the diff in Preview before anything goes live. General GTM craft (naming, workspaces, debugging) is covered by the analytics skill, so don't duplicate it.
Consent comes before the first tag. The recipes' ad tags need ad_storage consent (GA4 analytics_storage), so where a banner defaults to denied they wait for consent. If the site serves the EEA, UK, or other consent-regulated regions, confirm its consent setup before importing, and never strip the requirements to force a tag. Details in assets/gtm-recipes/README.md.
Step 5. Verify. See "Verification discipline" below. A setup without a verified arrival, or at least a named handover check for the parts you can't reach, isn't finished.
Step 6. Hand over. Close with what was built and where, the IDs used, the event names in play, how to re-verify the whole thing in five minutes, the delivery loss to expect for their traffic profile (and, if that loss matters for their goals, that server-side options exist in references/server-side.md), and what's likely to break it later (a form plugin update, a consent banner change, a platform deprecation that's already been announced).
Route on the symptom, in the user's own words, using the table below. Then gather evidence in this order. Static recon of the page and the published container first, then classify how the conversion moment fires, then walk the click ID chain, then guided account checks. The full evidence procedure, the scoring, and the report template are in references/audit-playbook.md, which uses the same scoring rules as the ads skill's audit guardrails. The short version of those rules is that every check resolves to pass, fail, unknown, or not applicable, and an unknown reduces your evidence coverage but never counts against the account's health, because "I couldn't check your pixel" and "your pixel is broken" are different findings.
| Symptom | Suspect first |
|---|---|
| "Never tracked anything" | A missing or mis-wired tag. Recon the page, then classify the moment |
| "It was working and then stopped" | Date the breakage, then check the platform calendar below. Ask what changed that week |
| "Spend and clicks, but zero conversions" | The click ID chain, link by link |
| "GA4 and Google Ads don't match" | Usually nothing. Read the verdict rules below before digging |
| "Way too many conversions" | Duplicate senders (a pixel and a server event without shared event IDs, or a tag and a GA4 import both set to Primary), Every-versus-One counting, or thank-you page refreshes |
| "Leads in my inbox, but the platform shows nothing" | A capture gap. Classify the moment, then walk the click ID chain |
| "Conversions record, but Smart Bidding ignores them" | Account settings. Primary versus Secondary, Include in Conversions, and the campaign's goal setup |
| "Some conversions come through, nowhere near all" | Inventory every entry point before any verdict, then look at structural loss |
| "Ads reports leads, but sales says they're junk" | The wrong moment. It's probably firing on clicks or validation rather than confirmed success |
| "The conversion is in GA4, but the campaign gets no credit" | Attribution loss. The click ID chain first |
| "The revenue numbers are wrong" (ecommerce) | Value integrity. Tax and shipping in or out, currency, and refunds never being adjusted |
| "Meta ROAS looks way better than Google" (ecommerce) | Attribution windows differ between the two, and refunds only exist on one side. Google Ads supports conversion adjustments and Meta documents no refund mechanism, so stores with high return rates structurally inflate Meta |
| "Plenty of leads, but the ads aren't producing actual customers" | The ads are optimising on form fills, not outcomes. The fix is feeding CRM outcomes back: value-based bidding on the lead, or deal-stage imports where the volume supports it. references/server-side.md, tracking what happens after the lead |
The platform calendar. Tracking that "just stopped" increasingly has a dated, platform-wide cause, so check the calendar before starting a deep dive. The current ones worth knowing about are these. Shopify removed Additional Scripts from the Thank you and Order status pages for all remaining stores on 26 August 2026 (Plus stores went through it on 28 August 2025, and storefront script tags follow on 1 March 2027). And WooCommerce's Blocks checkout doesn't fire the classic hooks that many tracking plugins depend on, so tracking often dies in a checkout redesign. Details and fixes for both are in references/ecommerce-platforms.md.
Apply these before reporting anything, especially on the "numbers don't match" symptom.
references/discrepancies.md.Verification is the audit's evidence standard applied at the end of a setup, and partial access is the normal situation, not the exception. You can usually observe the browser half yourself. You almost never have the user's ad account login, so the platform half usually runs through them.
Every tool named in this skill's references appears alongside its real alternatives, with current prices, honest effort and lead-time costs, and the free or native path always stated. One entry in references/server-side.md is marked as a partner of this repository. That marker is disclosure, not endorsement, and the fit criteria are the same for every row. The standing test is simple. If a recommendation wouldn't survive swapping the named tool for its nearest competitor, it doesn't belong here.
Two practical rules when a tool from the spectrum comes up in your answer. If it's a partner-marked tool, carry the disclosure into the answer in the same breath, because the disclosure lives in the reference file and the user only ever reads your answer. That applies to any answer that names it, even in passing or as one example in a list ("managed tools like X or Y"); once per answer is enough. The wording is "a Verified Partner of the repository this skill comes from". Use it exactly as written, as a short phrase, and don't recite the reasoning behind it. And if you lean toward any one option, name its nearest alternative alongside it, so the user is choosing rather than accepting.
And one rule that prevents underselling the options from memory. Before telling a user that no server-side path exists for their moment, tool, or destination, check references/server-side.md. Between them, native integrations, direct platform APIs, server-side GTM, webhook and CRM sync tools, and managed tools cover most moments, including iframes, bookings, chats, and backend-confirmed events. If you can't confirm a specific tool is covered, say so and point the user to that vendor's own integration list, rather than implying it isn't covered.
Load a reference when the task reaches it. Nothing here is optional reading, it's just split out so this file stays navigable.
| Task | Load |
|---|---|
| How each form, booking, chat, and checkout tool signals success | references/conversion-moments.md |
| Pixels, tags, and GTM builds, the complete browser-side path | references/browser-side.md |
| Server-side, the options spectrum with prices and honest tradeoffs | references/server-side.md |
| Shopify, WooCommerce, and other store platforms, including the deprecation dates | references/ecommerce-platforms.md |
| Google Ads setup, statuses, the gclid lifecycle, Enhanced Conversions, and its failure catalog | references/google-ads.md |
| Meta setup, CAPI, deduplication, match quality, and its failure catalog | references/meta.md |
| LinkedIn, TikTok, Microsoft, and the rest | references/other-platforms.md |
| The audit's evidence procedure, scoring, and report template | references/audit-playbook.md |
| Why the numbers never match, loss magnitudes, and the refund asymmetry | references/discrepancies.md |
Assets: assets/snippets/ holds paste-in success detectors for 22 lead-gen tools, plus four universal patterns for the moments no form tool owns: phone call clicks (the most under-tracked conversion in local services), file downloads, thank-you page arrivals, and a generic AJAX success pattern. assets/gtm-recipes/ holds the importable GTM containers (detection and send, with event names in event-map.json), and scripts/build_recipe.py merges them with the user's IDs into one import file.
Automatically, when a task matches. Use when the conversion is a form submission, booked meeting, chat, phone call, signup, or purchase; when conversions must reach Google Ads, Meta, GA4, LinkedIn, TikTok, or Microsoft Ads; when the work involves GTM tags and dataLayer events, pixels, the Conversions API (CAPI), Enhanced Conversions, server-side tagging, or gclid and fbclid capture; or when the user reports conversion problems like "conversions stopped", "double counting",
Published 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.