From agent-spec-protocol
The Agent Spec Protocol (AP) — the complete spec for encoding agent routing, pipeline state, and content contracts in HTML documents using ap- custom elements. Read this before producing or validating any AP-compliant HTML document.
How this skill is triggered — by the user, by Claude, or both
Slash command
/agent-spec-protocol:specThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
AP is a standard for HTML documents that are simultaneously human-readable pages and machine-navigable agent workspaces. It uses custom HTML elements (`<ap-gate>`, `<ap-source>`, `<ap-gen>`, etc.) to encode which agents own which sections, what pipeline state each section is in, and how agents read, write, and route through the document.
AP is a standard for HTML documents that are simultaneously human-readable pages and machine-navigable agent workspaces. It uses custom HTML elements (<ap-gate>, <ap-source>, <ap-gen>, etc.) to encode which agents own which sections, what pipeline state each section is in, and how agents read, write, and route through the document.
Most AI pipeline definitions live in YAML config files, JSON schemas, or markdown documents. All three have the same problem: they are invisible to humans without tooling, and they separate the pipeline definition from the content it produces. An Airflow DAG file and the data it processes are two completely separate things. A GitHub Actions workflow and the code it builds live in different files. You must hold both in your head simultaneously.
AP collapses those two things into one file. An AP document is the pipeline definition AND the content workspace. Open it in a browser: you see a readable document showing each stage, its current status, and whatever content agents have written. Open it with an XPath query: you see a machine-navigable graph of sections with precise routing and state. The same file, read two ways, serves two different audiences without compromise.
Why not JSON? JSON has no human-readable rendering and no native hierarchy that maps to document content. You cannot open a JSON pipeline config in a browser and read the actual output from each stage.
Why not YAML? Same problem — YAML is config, not content. Pipeline state and pipeline output are still in separate files.
Why not Markdown? Markdown is human-readable but machine-opaque. There is no standard way to attach pipeline metadata, agent assignments, or status to a markdown section. Agents parsing markdown must read and interpret prose to find their work — there is no precise address to navigate to.
HTML's specific advantages for AP:
id attributes are W3C-standardized unique addresses; #competitive inside <ap-gate> is a precise, unambiguous pointer<del>, <mark>, <progress>, <time>, <details> carry meaning that agents can read without custom attributesHTML is the medium — use it fully. AP does not invent structure that HTML already provides. The DOM tree is the pipeline graph: nesting expresses grouping, hierarchy expresses inheritance, element order expresses sequence.
Custom elements are the building block for pipeline stages. Pipeline attributes (agent, status, on-pass, on-fail, depends-on) belong only on custom elements — never on standard HTML elements. A parser finding pipeline stages looks for custom elements only. The hyphenated tag name is the signal. Reading layer attributes (kind, topic) are different: they may appear on any element, standard or custom, because they carry no workflow semantics and produce no parser scanning overhead for pipeline discovery.
The element name is the namespace. <ap-gate id="trends"> — the <ap-gate> wrapper makes trends unambiguous. There is no need to write id="ap-trends" or on-pass="ap-competitive". The surrounding element provides the context. Attributes on custom elements are owned by that element — no data- prefix is needed or wanted.
Hierarchy over repetition. Parent elements declare shared defaults. Child sections override only what is genuinely different. An attribute that appears on every section belongs on their parent.
Self-describing documents. An agent knows its own name. That is the only query it needs: //ap-gate[@agent='idea-trends'][@status='pending']. The document delivers the agent directly to its work — no scanning, no full-document reads.
Framework owns vocabulary. Users own values. AP defines the element names and valid enums. The values inside attributes — agent names, section IDs, output key names — belong to the implementor. Any domain can use AP without AP knowing anything about that domain.
AP operates at two independent layers:
topic and kind attributes on any HTML element. Makes existing content findable by agents without restructuring. No workflow implied.agent, status, on-pass, etc. Defines workflow stages where agents do work.A page can use either layer independently or both together.
AP also separates three concerns that must never mix:
bp-*)The reading layer solves one problem: an agent must find specific content in a document without reading the whole thing.
A documentation page with 40 sections costs 8,000 tokens if an agent reads it in full. With reading layer attributes, the agent issues one XPath query and reads only the sections it needs. Like a book with a precise index — you don't read every page to find the definition of a term.
Two attributes. Both may appear on any HTML element — standard or custom. Neither implies workflow state or pipeline processing.
kind — What type of content is this?A spec-defined closed vocabulary. Every AP-aware tool understands these values consistently.
| Value | Meaning |
|---|---|
summary | A condensed version of larger content |
overview | High-level introduction to a topic |
reference | Detailed lookup material (API docs, specs, tables) |
key-point | A single critical insight worth highlighting |
definition | A term and its meaning |
example | A concrete illustration of a concept |
instruction | Step-by-step directions for doing something |
topic — What subject does this cover?A user-defined open vocabulary. AP defines the attribute name. The implementor defines the values. Any string is valid. Use kebab-case for multi-word topics.
topic="authentication"
topic="error-handling"
topic="pricing"
topic="getting-started"
<!-- a documentation page — no restructuring needed, just annotations -->
<section id="auth-intro" topic="authentication" kind="overview">
<h2>Authentication</h2>
<p>Our API uses bearer tokens...</p>
</section>
<section id="auth-reference" topic="authentication" kind="reference">
<h2>Authentication Reference</h2>
<table>...</table>
</section>
<p id="auth-tldr" topic="authentication" kind="summary">
Pass your API key as a Bearer token in the Authorization header.
</p>
<aside id="rate-limit-note" topic="rate-limiting" kind="key-point">
Requests are limited to 1,000 per minute per API key.
</aside>
An agent answering "how does authentication work?" needs exactly two queries:
//*[@topic='authentication'][@kind='summary'] ← get the one-line answer first
//*[@topic='authentication'][@kind='reference'] ← get the full details if needed
Neither query reads the rate limiting section, the pricing section, or anything else. The document delivers targeted content directly.
kind and topic may appear on any HTML element — standard or customkind takes only the enumerated values above — no custom valuestopic is user-defined — any lowercase kebab-case string is validkind and topic is a valid AP reading-layer document even with no custom elementsEvery AP document is a valid HTML5 file with three distinct zones:
| Zone | Element | Owns |
|---|---|---|
| 1 | <html> | Design system attributes only |
| 2 | <head> | Document-level metadata in <meta> tags |
| 3 | <body> | Content and AP pipeline sections |
<html> ElementOnly design system attributes live here. AP protocol does not touch <html>.
<html data-bp-theme="auto" lang="en">
Document-level metadata lives in <head> as namespaced <meta> tags:
<meta name="[namespace]:[key]" content="[value]">
Namespace rules:
a-z and -Example:
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Memory Layer</title>
<meta name="idea:id" content="ai-memory-layer">
<meta name="idea:status" content="in-progress">
<meta name="idea:current-stage" content="competitive">
<meta name="idea:gates-passed" content="trends">
<meta name="idea:gates-failed" content="">
<meta name="idea:created" content="2026-03-30T01:19:03Z">
</head>
The index contract: An orchestrator reading a portfolio of AP documents reads only <head> metadata — never section content. Meta tags must be complete and current. Every agent that updates a section status must also update the relevant <meta> tags.
Each pipeline stage is an AP custom element in <body>. The element name declares the stage type. Attributes declare the metadata. Content is what agents and humans write.
AP custom elements may be nested. A parent <ap-parallel> contains child elements that run concurrently. All other nesting is for attribute inheritance.
| Element | Who runs it | Gate decision? | Notes |
|---|---|---|---|
<ap-source> | human | no | Immutable origin. Never modified after creation. |
<ap-gen> | AI agent | no | Produces content with no pass/fail outcome. |
<ap-transform> | AI agent | no | Converts inputs to a different output form. |
<ap-gate> | AI agent | yes | Automated pass/fail decision. |
<ap-review by="ai"> | AI agent | yes | AI reads upstream content and decides. |
<ap-review by="human"> | human | yes | Human reads and decides. Status set manually. Never completed by an agent. |
<ap-route> | AI agent | no | Reads context, writes route-to, orchestrator follows. |
<ap-parallel> | orchestrator | no | Container: all children run concurrently. |
<ap-placeholder> | — | — | Child element: marks unfilled content. Removed when agent writes. |
Every leaf AP element must have all three:
| Attribute | Format | Example |
|---|---|---|
id | kebab-case | id="competitive" |
agent | agent name string | agent="idea-competitive" |
status | enum | status="pending" |
| Attribute | Type | Purpose |
|---|---|---|
on-pass | section ID | Where control routes on pass |
on-fail | section ID | Where control routes on failure |
on-skip | section ID | Where control routes on skip |
depends-on | space-separated IDs | Guards: all must be passed before this runs |
context | space-separated IDs | Section IDs to inject as agent context |
updated-at | ISO 8601 datetime | When agent last wrote to this section |
version | positive integer | How many times this section has been written |
| Attribute | Type | Purpose |
|---|---|---|
gate | enum | Whether this stage has a pass/fail gate decision |
format | enum | Expected output shape |
token-budget | positive integer | Max tokens the agent should write |
outputs | space-separated keys | Named values this section produces |
inputs | space-separated keys | Named values this section requires from upstream |
route-to | section ID | Written by <ap-route> agents; orchestrator follows this |
AP attributes cascade from parent to child elements, exactly like CSS properties cascade through the DOM. Declare shared defaults on the nearest common ancestor.
| Attribute | Cascades |
|---|---|
gate | Yes |
format | Yes |
token-budget | Yes |
on-fail | Yes — default escalation path for all children |
| Attribute | Does Not Cascade |
|---|---|
agent | Each element has its own assigned agent |
status | Each element holds its own live state |
depends-on | Each element declares its own guards |
context | Each element specifies its own read targets |
outputs | Each element declares its own produced keys |
inputs | Each element declares its own required keys |
on-pass | Each element declares its own forward route |
on-skip | Each element declares its own skip route |
updated-at | Each element holds its own timestamp |
version | Each element holds its own count |
<!-- Parent sets shared defaults -->
<main gate="required" format="prose">
<!-- Children only declare what is unique -->
<ap-gate id="trends"
agent="idea-trends"
status="pending"
context="raw-idea"
on-pass="competitive"
on-fail="human-triage"
>
<ap-gate id="competitive"
agent="idea-competitive"
status="blocked"
depends-on="trends"
context="raw-idea trends"
on-pass="icp"
on-fail="human-triage"
>
</main>
Closed sets. No values outside these lists are valid.
status| Value | Meaning |
|---|---|
pending | Not yet processed |
running | Agent currently processing |
passed | Gate cleared or section complete |
failed | Gate not cleared |
skipped | Intentionally bypassed |
blocked | Waiting on a dependency that has not passed |
gate| Value | Meaning |
|---|---|
required | Must pass for downstream sections to become runnable |
optional | Informational — pipeline continues regardless of outcome |
none | Purely generative; no gate decision |
format| Value | Meaning |
|---|---|
prose | Narrative text paragraphs |
json | Structured JSON data |
list | Unordered or ordered list |
table | Tabular data |
html | Rich HTML fragment |
mixed | Multiple formats within the section |
AP custom elements handle pipeline-level concerns. For content-level state within a section, use native HTML semantic elements — they carry meaning that both browsers and agents understand without custom attributes.
| HTML element | Human sees | Agent reads as |
|---|---|---|
<del> | strikethrough | done / removed / no longer applies |
<ins> | underline | newly added / just written |
<mark> | highlight | flagged / needs attention |
<progress value="3" max="7"> | progress bar | 3 of 7 steps complete |
<details open> | expanded section | currently active |
<details> (no open) | collapsed | not active / archived |
<time datetime="..."> | readable date | machine-parseable ISO timestamp |
<strong> | bold | high importance |
Example — an agent marking completed checklist items:
<ul>
<li><del>Research competitors</del></li> <!-- done -->
<li>Write ICP analysis</li> <!-- pending -->
<li><mark>Review pricing model</mark></li> <!-- needs attention -->
</ul>
XPath to find incomplete items: //li[not(del)]
Sections that can run concurrently are wrapped in <ap-parallel>. The nesting IS the group declaration.
<ap-parallel id="phase-3" depends-on="competitive">
<ap-gen id="icp" agent="icp-agent" status="blocked">
<ap-gen id="mom-test" agent="mom-test-agent" status="blocked">
<ap-gen id="mvp" agent="mvp-agent" status="blocked">
</ap-parallel>
The orchestrator fans out all children simultaneously when <ap-parallel> becomes runnable. The container completes when all children have status="passed".
Each AP element declares where control goes when it finishes. The orchestrator never needs to scan for what comes next.
| Attribute | Trigger | Meaning |
|---|---|---|
on-pass | section passes | Activate this section ID next |
on-fail | section fails | Activate this section ID next |
on-skip | section is skipped | Activate this section ID next |
depends-on | before running | Guard: all listed IDs must be passed first |
depends-on is a guard — prevents a section from starting too early. on-* are routes — they tell the orchestrator where to go after. Two different jobs; two different attributes.
<ap-route>When routing logic is too complex for a static value, use <ap-route>. The agent reads context, decides the next target, writes route-to on its own element — never on any other element — then sets status="passed". The orchestrator follows route-to.
<ap-route
id="score-router"
agent="scoring-router-agent"
context="trends competitive"
status="pending"
route-to=""
>
<!-- agent writes route-to="icp" or route-to="human-triage" based on scores -->
</ap-route>
Keys are namespaced with a colon: [section-id]:[key-name]. This prevents two sections from producing a key with the same name.
trends:score
trends:recommendation
competitive:gaps
<ap-gate id="trends"
outputs="trends:score trends:recommendation trends:signals">
<ap-gate id="competitive"
inputs="trends:score trends:recommendation"
outputs="competitive:score competitive:gaps">
Every key in inputs must resolve unambiguously to exactly one upstream element's outputs. The CLI validates this graph.
<!-- PENDING -->
<ap-gate id="trends" agent="idea-trends" status="pending" on-pass="competitive">
<ap-placeholder>Trends analysis pending.</ap-placeholder>
</ap-gate>
<!-- COMPLETED -->
<ap-gate id="trends" agent="idea-trends" status="passed" updated-at="2026-05-12T14:30:00Z" version="1" on-pass="competitive">
<h2>Trends Analysis</h2>
<p>Strong market signal across three growth vectors...</p>
<footer>
<time datetime="2026-05-12T14:30:00Z">May 12, 2026 at 2:30 PM</time>
· idea-trends · 387 tokens
</footer>
</ap-gate>
updated-at is agent-writable and XPath-queryable. version tracks rewrites. <time datetime="..."> renders the timestamp for humans in the browser. <ap-placeholder> is removed when the agent writes real content — its presence alone signals pending state.
An agent knows its own name. One query delivers it directly to its work:
//ap-gate[@agent='idea-competitive'][@status='pending']
The CLI materializes inheritance before returning an element to an agent. When ap next --agent=idea-competitive runs, the CLI resolves all attributes inherited from parent elements and returns the section with its full resolved attribute set. Agents never walk the tree themselves — they receive a complete work order.
//ap-gate[@agent='idea-trends'][@status='pending'] ← agent finds its pending work
//ap-gate[@id='competitive'] ← read a specific section
//ap-gate[@status='failed'][@gate='required'] ← find blocking failures
//ap-review[@by='human'][@status='pending'] ← find work waiting on a human
//*[@status='pending'][not(ap-placeholder)] ← pending but already has content
//ap-gate[@version > 1] ← sections rewritten more than once
//li[not(del)] ← incomplete checklist items
The orchestrator reads only <head> meta tags and AP element attributes. It never reads section content.
Algorithm:
id, agent, status, depends-on, gate, on-pass, on-failstatus="pending"depends-on have status="passed" (or attribute is absent)<ap-parallel>, fan out all pending siblings simultaneouslyagent for that elementstatusupdated-at to current ISO 8601 timestampversionon-pass, on-fail, or on-skip to activate the next element<meta> tags in <head>When an agent writes to its element:
context using XPath//ap-gate[@id='[own-id]'] to see current statetoken-budget (if present)format<ap-placeholder> with real contentstatus to passed or failedupdated-at with current ISO 8601 datetimeversion (or set to 1 if absent)[a-z][a-z0-9-]*#competitive today finds the same element indefinitely. Never rename or reuse.Valid: raw-idea, competitive, mom-test
Invalid: Raw_Idea (uppercase/underscore), 3rd (starts with digit)
<!DOCTYPE html>
<html lang="en" data-bp-theme="auto">
<head>
<meta charset="UTF-8">
<title>AI Memory Layer</title>
<meta name="idea:id" content="ai-memory-layer">
<meta name="idea:status" content="in-progress">
<meta name="idea:current-stage" content="trends">
<meta name="idea:gates-passed" content="">
<meta name="idea:created" content="2026-05-12T00:00:00Z">
</head>
<body>
<main gate="required" format="prose">
<ap-source id="raw-idea" agent="human" status="passed" outputs="raw-idea:text">
<h2>Raw Idea</h2>
<p>The raw idea text goes here, exactly as captured.</p>
</ap-source>
<ap-gate id="trends"
agent="idea-trends"
status="pending"
context="raw-idea"
inputs="raw-idea:text"
outputs="trends:score trends:recommendation"
on-pass="competitive"
on-fail="human-triage"
>
<ap-placeholder>Trends analysis pending.</ap-placeholder>
</ap-gate>
<ap-gate id="competitive"
agent="idea-competitive"
status="blocked"
depends-on="trends"
context="raw-idea trends"
inputs="raw-idea:text trends:score trends:recommendation"
outputs="competitive:score competitive:gaps"
on-pass="phase-3"
on-fail="human-triage"
>
<ap-placeholder>Blocked — waiting for trends to pass.</ap-placeholder>
</ap-gate>
<ap-parallel id="phase-3" depends-on="competitive">
<ap-gen id="icp" agent="icp-agent" status="blocked" context="raw-idea competitive">
<ap-gen id="mom-test" agent="mom-test-agent" status="blocked" context="raw-idea competitive">
<ap-gen id="mvp" agent="mvp-agent" status="blocked" context="raw-idea competitive">
</ap-parallel>
<ap-review by="human"
id="final-review"
agent="human"
status="blocked"
depends-on="phase-3"
>
<ap-placeholder>Awaiting human review after all analysis completes.</ap-placeholder>
</ap-review>
</main>
</body>
</html>
AP is a protocol, not a fixed vocabulary. The <ap-*> elements are the reference implementation — the vocabulary AP ships with. Any team can define their own custom elements and remain fully within spec, as long as those elements honor the core attribute contract.
Any custom element is AP-compliant if it:
id, agent, and status on every pipeline stage elementstatus without modificationon-pass, on-fail, on-skip, and depends-on for routing and guards when routing is neededThat is the entire contract. AP does not need to know your element names.
A legal team defines their own elements. An orchestrator that understands the core contract routes through this document without any changes:
<legal-intake id="case-filing" agent="intake-bot" status="pending" on-pass="extraction">
<legal-extract id="extraction" agent="clause-reader" status="blocked" on-pass="risk-gate" depends-on="case-filing">
<legal-gate id="risk-gate" agent="risk-assessor" status="blocked" on-pass="sign-off" on-fail="flagged" depends-on="extraction">
<legal-review id="sign-off" agent="human" status="blocked" depends-on="risk-gate">
The orchestrator reads agent, status, on-pass, on-fail, depends-on — the same six attributes it reads on <ap-gate>. It never needs to know what <legal-gate> means.
legal-gate is valid; legalgate is notstatus="approved" instead of status="passed" breaks orchestrator compatibilityBecause it is HTML, domain teams do not write a new parser. Every language has an HTML parser that handles custom elements:
| Language | Library |
|---|---|
| JavaScript | Native DOM (querySelectorAll, XPath) |
| Python | BeautifulSoup, lxml |
| Ruby | Nokogiri |
| Go | golang.org/x/net/html |
| Rust | html5ever |
An XPath query that works on <ap-gate> works identically on <legal-gate>:
//*[@status='pending'][not(ap-placeholder)] ← finds pending work in any vocabulary
Structure
<html> has no AP attributes<head> contains at least one <meta name="[namespace]:[key]"> tagElements
agent, status, on-pass, on-fail, depends-on, etc.) appear on standard HTML elements (<section>, <div>, <article>, etc.) — this is a violation, not a warningid, agent, status^[a-z][a-z0-9-]*$ (kebab-case, no prefix required)status is one of the enumerated valuesgate is one of the enumerated values (if present)format is one of the enumerated values (if present)<ap-source> element never has status="running"<ap-review by="human"> element is never set to status="passed" by an agentRouting and Dependencies
depends-on reference existing element IDs in the documentcontext reference existing element IDs in the documenton-pass, on-fail, on-skip reference existing element IDsdepends-on chainsContracts
inputs follows the [section-id]:[key-name] conventioninputs appears in a reachable upstream element's outputsEach attribute needs a declared type for validators to be written against the spec without interpretation:
| Type | Meaning | Example |
|---|---|---|
string | Freeform text | agent name |
enum | One value from a closed set | pending, gate |
id | A single kebab-case section ID | trends |
id-list | Space-separated section IDs | trends raw-idea |
key-list | Space-separated namespaced contract keys | trends:score trends:recommendation |
integer | Positive whole number | 3 |
ISO-8601 | Full datetime string | 2026-05-12T00:00:00Z |
Status: Not yet assigned to each attribute. Needed before v1.0 stability declaration.
The spec uses one domain (idea validation) in all examples. Two additional worked examples are needed:
These should live in /examples/ and use real agent names, routing decisions, and inputs/outputs contracts.
Status: Not yet written.
Every attribute needs a maturity level before open source publication:
| Level | Meaning |
|---|---|
stable | Will not change in a breaking way within a major version |
experimental | May change; implementors should expect breakage |
reserved | Defined but not yet implemented |
Status: Maturity levels not yet assigned.
When some children of <ap-parallel> pass and some fail:
partial?on-fail on the container fire if any child fails, or only if all children fail?on-pass fire before all children complete?Status: Semantics not yet defined. Blocking for pipelines using parallel execution with required gates.
npx claudepluginhub revans/apCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.