T-H56N-hooks-as-vault-entities
State: planning/proposed · Impact: high · Complexity: large
A hook is the one component every harness documents and Agent Pants cannot author. The renderer
library models one and six runtimes can emit one, but no vault-side code path produces a Hook:
the string does not appear in agent-adapt-core or in determined-agent-skills at all. This task
makes hook a vault kind — a note with typed frontmatter and a body that is the script or the
prompt — and carries it through discovery, the build and the tree.
| Location | Role today |
|---|---|
packages/rust/agent-adapt/src/model/hook.rs#Hook | Command-only: name, slug, description, event, matcher, command, timeout_ms, tags, category, is_template |
packages/rust/agent-adapt/src/render/hooks_json.rs#render_hooks_json | Emits a flat { "hooks": [ { event, matcher, command, timeout_ms } ] }, not the harnesses’ Event → [{matcher, hooks:[…]}] nesting |
packages/rust/agent-adapt/src/runtime.rs#HookCapability | render_hooks, implemented by the Claude Code, Codex, Gemini, OpenCode, Amp and npm runtimes, all delegating to standard::render_hooks |
packages/rust/agent-adapt/src/tools.rs#ToolRegistry | The 11 canonical tool names a matcher’s halves translate through, per runtime |
solutions/agent-pants/crates/agent-adapt-core/src/frontmatter.rs#EntityKind | Five kinds — skill, plugin, command, agent, marketplace. KNOWN lists the accepted spellings |
solutions/agent-pants/crates/agent-adapt-core/src/frontmatter.rs#KindSource | Records which of the four layers answered for a note |
solutions/agent-pants/crates/agent-adapt-core/src/discover.rs#KIND_DIRS | marketplaces, plugins, skills, commands, agents — no hooks |
solutions/agent-pants/crates/agent-adapt-core/src/export.rs#export_skills | Builds a marketplace root from skills and commands; nothing in the crate mentions hooks |
solutions/determined/crates/determined-core/src/wire/mod.rs#AgentSkillNoteFields | The writable frontmatter surface — no hook fields |
solutions/determined/experiences/agent-pants/app/composables/useAgentPantsComponentSource.ts | The tree source; mints kind: addresses per kind folder |
solutions/determined/experiences/agent-pants/app/utils/fieldSpec.ts#ComponentKind | 'skill' | 'command' | 'agent' | 'plugin' | 'marketplace' |
solutions/agent-pants/docs/vault/decisions/entity-kind-and-frontmatter-namespace.md | Already names hook as a kind the vocabulary takes later |
docs/LIBRARIES.md | The tier-1 agent-adapt row: the monorepo is canonical and rust-agent-adapt is its public mirror, so growing the model has a mirror consequence |
Proposed
Section titled “Proposed”EntityKind::Hook joins the vocabulary and hooks joins KIND_DIRS, so a hook note lives at
hooks/<name>.md under whatever holds it, exactly like a command. Its frontmatter models a hook:
ap-kind: hook, event, matcher, type, one of command / url / prompt, timeout, if,
async, once. The body is the script or the prompt text.
agent-adapt’s Hook grows the fields the canonical superset needs — type, url, prompt,
if, async, once — and render_hooks_json emits the nested Event → [{matcher, hooks:[…]}]
shape the harnesses actually read. A plugin build writes hooks/hooks.json under the plugin root
alongside its skills and commands.
Event names translate per target. Gemini’s BeforeTool and Claude Code’s PreToolUse are the same
event and map onto each other; an event with no mapping for that target is refused through
HookCapability, naming the event and the target, and the build says so rather than dropping the
hook. AgentSkillNoteFields, ComponentKind and the component tree learn the kind so a hook note
is discoverable, badged and openable.
A hook note reads:
---name: block-secret-writesdescription: Refuses a write that would land a credential in the repo.ap-kind: hookevent: PreToolUsematcher: Edit|Writetype: commandtimeout: 5000---Growing the tier-1 model is recorded in docs/LIBRARIES.md with its mirror consequence for
rust-agent-adapt.
Approach
Section titled “Approach”- Grow the model in
agent-adapt: addtype,url,prompt,if,asyncandoncetoHook, each optional so existing constructions still compile. Decision inside this step:typeis a closed enum overcommand,http,mcp_tool,prompt,agent, and an unknown spelling is a parse refusal rather than a pass-through. - Rewrite
render_hooks_jsononto the nested shape —hooks > Event > [{matcher, hooks:[{type, command|url|prompt, timeout, if, async, once}]}]— keeping matcher translation throughToolRegistry, with fixture tests for the Claude Code and Gemini renders. - Add per-target event translation and refusal to
HookCapability, keyed off the event table in T-HLEW-harness-capability-matrix. A target that documents no hook model at all refuses every hook, naming the target. - Add
EntityKind::Hookand itsKNOWNspelling inagent-adapt-core, and("hooks", EntityKind::Hook)toKIND_DIRS.KindSourceneeds no change: the hook kind rides the four layers already there. - Parse the hook frontmatter fields in the
ap-reader, and project a note ontoagent_adapt::Hookwith the body as the command script or the prompt text. Decision inside this step:type: commandreads its script from the body,type: promptreads its prompt from the body, and a note that sets both the body and acommandkey is refused rather than silently preferring one. - Emit hooks from the build:
export_skillscollects a plugin’s hook members and writeshooks/hooks.jsonunder that plugin’s root, per target, honouringap-targets. - Carry the kind up:
AgentSkillNoteFieldsgains the hook fields,ComponentKindgains'hook',FIELD_SPECSgains one entry per hook field withkinds: ['hook'], and the tree source mints thehookskind folder. - Add the
agent-adaptrow note indocs/LIBRARIES.mdrecording the model growth and therust-agent-adaptmirror consequence, and document the kind in the vault-layout and frontmatter manual chapters.
| Area | Note |
|---|---|
packages/rust/agent-adapt/src/model/hook.rs | type, url, prompt, if, async, once on Hook; the type enum |
packages/rust/agent-adapt/src/render/hooks_json.rs | Nested Event → [{matcher, hooks:[…]}] output; the new fields |
packages/rust/agent-adapt/src/runtime.rs | Event translation and refusal on HookCapability |
packages/rust/agent-adapt/src/runtimes/gemini_cli.rs | Gemini’s event names and the events it does not carry |
packages/rust/agent-adapt/src/runtimes/claude_code.rs | Claude Code’s event roster as the canonical superset |
packages/rust/agent-adapt/tests/ | Fixture tests for the Claude Code and Gemini hook renders |
solutions/agent-pants/crates/agent-adapt-core/src/frontmatter.rs | EntityKind::Hook, its KNOWN spelling, the hook frontmatter fields |
solutions/agent-pants/crates/agent-adapt-core/src/discover.rs | hooks in KIND_DIRS |
solutions/agent-pants/crates/agent-adapt-core/src/projection.rs | project_hook, the note-to-Hook projection beside project_command |
solutions/agent-pants/crates/agent-adapt-core/src/export.rs | Collect hook members and write hooks/hooks.json per plugin root |
solutions/agent-pants/crates/agent-adapt-core/tests/ | Fixtures for a hook note, an unmapped event, a both-body-and-command refusal |
solutions/determined/crates/determined-core/src/wire/mod.rs | Hook fields on AgentSkillNoteFields |
solutions/determined/crates/determined-agent-skills/src/lib.rs | Read and write the hook fields; hook notes in the roster and the tree |
solutions/determined/experiences/agent-pants/app/utils/fieldSpec.ts | 'hook' in ComponentKind; one FIELD_SPECS entry per hook field |
solutions/determined/experiences/agent-pants/app/composables/useAgentPantsComponentSource.ts | The hooks kind folder in the tree |
docs/LIBRARIES.md | The model growth and its rust-agent-adapt mirror consequence |
solutions/determined/experiences/agent-pants/docs/vault-layout.md | Where a hook note lives |
solutions/determined/experiences/agent-pants/docs/frontmatter-reference.md | The hook frontmatter fields |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: A note at
hooks/<name>.mdwithap-kind: hookis discovered as a hook, and itskind_sourcereports which layer answered. - AC-2:
KIND_DIRScontainshooks, and a hook note in that folder needs noap-kindkey. - AC-3: A plugin build writes
hooks/hooks.jsonunder the plugin root, in the nestedEvent → [{matcher, hooks:[…]}]shape, asserted against a fixture. - AC-4: A
PreToolUsehook renders asBeforeToolfor thegemini-clitarget, and the matcher’s tool names translate throughToolRegistryin the same render. - AC-5: An event with no mapping for a target refuses, naming the event and the target, and the
build reports the refusal instead of writing a partial
hooks.json. - AC-6: A hook note appears in the component tree under a
hooksfolder and opens in the note editor. - AC-7:
Hookcarriestype,url,prompt,if,asyncandonce, and an unknowntypespelling is refused. - AC-8: The
agent-adaptrow indocs/LIBRARIES.mdrecords the model growth and therust-agent-adaptmirror consequence.
Out of scope
Section titled “Out of scope”- The hook editor form and the harness gap table: T-0AF7-hook-editor-and-harness-gap-table.
- MCP servers as a vault kind; hooks are the only kind this task adds.
- Publishing a standalone hook to a destination outside a plugin — a hook reaches a harness through
its plugin’s
hooks.json. - Updating the
rust-agent-adaptmirror. This task records the consequence; the mirror publish is its own release.
Dependencies
Section titled “Dependencies”- T-HLEW-harness-capability-matrix supplies the per-target event table the translation and the refusal in step 3 read.
- T-IIZO-per-target-overrides and T-0AF7-hook-editor-and-harness-gap-table build on this kind; neither blocks it.
Discovery context
Section titled “Discovery context”Member of milestone M-BMFD, from the user’s planning request of 2026-09-07 on the data model.
The namespace decision under solutions/agent-pants/docs/vault/decisions/ — the file
entity-kind-and-frontmatter-namespace.md — already reserved hook as a later kind. Event
rosters and the nested config shape come from
Claude Code hooks,
Claude Code plugins and
Gemini CLI, all fetched 2026-09-07; the per-harness divergence is
catalogued in solutions/agent-pants/docs/vault/reference/harness-comparison.md.
Depends on
Section titled “Depends on”T-HLEW-harness-capability-matrix