darwin-agent/harnessx/reference
Claude Code and agent skill by darwin-agent/harnessx
- community
- ext:[{"type":"hook","description":"Pre/post lifecycle hooks"}]
Checking that the SKILL.md file is still in the repository.
Component-authoring reference for HarnessX evolve rounds. Covers the @tool signature and TOOL_SPEC workflow, MultiHookProcessor (hook dispatch table, event fields, messages-mutation contract), system-prompt template editing (Cases A/B/C), config.yaml shape, and the signal→knob guide for the Configuration lever. Read when implementing any lever — Action, Control, Instruction, or Configuration.
Install
npx vskill@latest install darwin-agent/harnessx/referenceSemi-extensibleMentions customization, but needs skill-specific knowledge. Read the extensible skills standard
SKILL.md
Reference — HarnessX component authoring
Where things live
| File | Purpose | |
|---|---|---|
output_dir/config.yaml | The new HarnessConfig the caller loads | |
output_dir/tools/<name>.py | One Tool object per file | |
output_dir/processors/<name>.py | One MultiHookProcessor subclass per file | |
output_dir/prompt/<name> | System prompt for SystemPromptProcessor |
All authored files are referenced from config.yaml with absolute paths. Relative paths are rejected by the loader.
Authoring a new @tool
This section assumes you've already decided Action is the right lever. For whether to author a new tool at all (vs. retune an existing one, write a Control hook, or edit the prompt), see the analyze skill — lever choice lives there, authoring mechanics live here.
Before you type — write the shape spec
For anything larger than a short stdlib helper, write the spec in _meta_scratch/TOOL_SPEC.md before any code. Short sections, no prose padding:
A spec you cannot write honestly is a spec that is too broad — narrow it. The spec's value is framing the inputs concretely and pinning anti-patterns (no generic replacements, no hardcoded task ids) at the design stage.
Pre-flight
For pure-stdlib utilities, the pre-flight is small:
- Dump the `Tool` dataclass fields — learn what the decorator
builds before you write a call site for it: ``bash python -c "from dataclasses import fields; from harnessx.tools.base import Tool; \ [print(f.name, '=', f.type) for f in fields(Tool)]" ``
- Read the nearest builtin in
harnessx/tools/builtin/that is
structurally similar (same return shape, same env access pattern, same failure mode). Match its signature and error-handling style; don't invent a new convention.
If your tool touches the network or imports a third-party package:
- Write the imports as usual. If dry-fire reports
ImportError, either fall back to stdlib or record the dep in _meta_scratch/NEEDS_FROM_HUMAN.md and try a different approach.
- Check prior art under
harnessx/tools/builtin/for the
keyword in your diagnosis — but decide by observed behaviour on this benchmark's inputs, not by whether a similarly named builtin exists (see Prior-art rule).
Minimal shape
Concrete tool examples (search fallbacks, file-format parsers, domain-specific clients) belong in the recipe's <bench>-playbook skill — they encode the benchmark's input distribution and failure classes, which this skill deliberately does not.
Rules:
- Exactly one module-level
Toolobject per file (produced by
the @tool decorator on a function).
async defor regulardef— both are supported.- Input args must have type hints; they become the JSON Schema.
- Return a string, or something stringifiable. Tool outputs feed
back to the model as text.
- Third-party imports: just import at module top-level. Dry-fire
- replay-gate handle env uncertainty downstream.
- No hardcoded task ids, URLs, or answers in fn bodies — the
literals validator rejects these (R4).
Reference from config.yaml:
The string after :: is the function name (what the tool will be called as). If the inner agent should use your tool instead of a builtin, remove the builtin's PascalCase name from tools.builtin.
Authoring a new MultiHookProcessor
Mandatory pre-flight
Most contract violations in evolve rounds come from skipping one of these. Walk them in order every time, regardless of how trivial the hook looks:
- Pick the hook deliberately. Cross-check against the
hook dispatch table — an on_* method whose suffix isn't a known event becomes an import-time TypeError.
- Dump the event dataclass (see
Event fields) before typing any event.<field> access. Field names drift; don't trust memory or pattern-match from another processor.
- If your hook mutates `event.messages` (only
on_step_start
and on_before_model are allowed to), read harnessx/core/processor.py::_validate_messages_contract end-to-end, plus check_post_hook_invariants in the same file. Then re-check your hook body against every violation category listed in Messages-mutation contract.
- Grep prior art under
harnessx/processors/control/,
context/, memory/. If a builtin's observed behaviour is close to what you want, a config-layer retune beats a new file.
Minimal shape
Zero-arg-friendly constructor: the loader may instantiate with no kwargs if the YAML entry doesn't pass any.
(Benchmark-specific processors — for example, one that enforces a particular answer-format marker — belong in the recipe, authored by the meta-agent when the benchmark's playbook identifies a grader requirement that the current config isn't meeting.)
Hook dispatch table
| Event class | Method name | |
|---|---|---|
TaskStartEvent | on_task_start | |
StepStartEvent | on_step_start | |
BeforeModelEvent | on_before_model | |
ModelResponseEvent | on_after_model | |
ToolCallEvent | on_before_tool | |
ToolResultEvent | on_after_tool | |
StepEndEvent | on_step_end | |
TaskEndEvent | on_task_end |
Name a method on_* but not one of those → import-time TypeError. Use @on(CustomEventClass) to handle a custom event under a free name.
Event fields — look them up, never guess
Referencing a non-existent field (event.message when the field is messages) passes canonicalize but explodes on the next benchmark run. Copy-paste field names from the dataclass.
Messages-mutation contract
Only two hooks may touch event.messages: on_step_start and on_before_model. Violations surface at runtime as CONTRACT [<type>] hook=... processor=... warnings, and raise ContractViolationError under HARNESSX_CONTRACT_MODE=strict.
Single source of truth is `harnessx/core/processor.py::_validate_messages_contract` (plus check_post_hook_invariants for chain-level rules). The list below is a scanning checklist.
Violation categories:
- Empty messages (
empty_messages) —before_modeloutput must
be non-empty.
- Length delta out of range (
length_exceeded) —before_model:
a processor may not remove messages, and may add at most +1 per step.
- Tail role & insertion rules — when
before_modeladds +1:
the pre-hook tail must not already be user (content-edit only in that case), and the inserted message itself must have role="user".
- Chain-level insertion cap — across all processors in a
single before_model chain, at most +1 user insertion total.
- `system_window` invariance —
step_startmay not change
messages[0] when its role is "system".
- Structural-vs-content edits —
step_startmay not ONLY change
the tail (that's before_model's job). before_model with delta==0: if tail is user, only its content may change and the prefix must be identical; if tail is not user, no change allowed.
Chain-level invariants in check_post_hook_invariants:
systemcount ≤ 1 and must sit at index 0.raw_trackandeffective_trackmust have equal length, matching
roles at every index, and matching tool_call_id for role=='tool'.
Safest default when unsure: a pure content rewrite of the existing tail user message (delta==0, prefix untouched) — valid in both step_start and before_model's user-tail branch.
Yielding
A hook is an async def generator. Yield one or more events:
yield event— pass through unchangedyield dataclasses.replace(event, messages=new_tuple)— mutate a
frozen dataclass field
yield ProcessorTriggerEvent(...)— emit an observability event
alongside the main one
Yielding nothing blocks the hook; don't do this unless you intentionally want to suppress a tool call or model turn.
Ordering
_singleton_group prevents two processors of the same kind stacking. _order: 10 = pre, 20 = normal, 30 = post. Builtins use token_budget=10, cost_guard=10, checkpoint=10, loop_detection=20, otel=30. Slot new guards at 15–25 unless you specifically need to run before or after a named one.
Reference from config.yaml:
Kwargs in the YAML map directly to the processor's __init__ args.
Editing the inner agent's system prompt (instruction layer)
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingharnessx/meta_harness/workspace/skills/reference/SKILL.md
Popularity
- Repository stars
- 100
- Repository forks
- 9
- 7-day trend score
- 23
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Extension points
hookPre/post lifecycle hooks
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/darwin-agent/harnessx/reference)