kick Variable Resolution Pipeline
eva intermediate 5 min read
ELI5
kick builds the prompt like a stack of transparencies: first the case file slides on, then your --var flags overlay it, then guard.sh slaps a final correction sticker on top. The last sticker wins, then awk swaps every {{key}} for its value.
Technical Deep Dive
Precedence (lowest → highest, bin/kick:150-182)
examples/<case>/inputs.yml(when--caseis passed)--var key=valueand--var-file path(parsed left-to-right)guard.shstdoutKEY=VALUElines
Each layer appends to the same VARS array; later additions for the same key shadow earlier ones because the awk renderer at bin/kick:32-43 rewrites the document once per pair, in order.
Sequence
sequenceDiagram participant U as user shell participant K as bin/kick participant Y as inputs.yml participant G as guard.sh participant A as awk renderer participant C as claude CLI
U->>K: kick id --case happy --var x=1 --send K->>Y: parse_yaml_flat (case) Y-->>K: case_vars K->>K: VARS = case_vars + cli_vars K->>G: exec with EVA_VAR_* exported G-->>K: stdout KEY=VALUE K->>K: VARS += guard_lines K->>A: render(prompt.xml, VARS) A-->>K: rendered text K->>C: pipe rendered → claude C-->>K: stdout (tee'd to TMP_OUT)Substitution Mechanics
The renderer (bin/kick:32-43) iterates the VARS array, runs awk per pair, and replaces literal {{key}} occurrences with the value verbatim — no escaping, no nested templates, no recursion. A pair whose key is file_contents is what --var-file <path> produces (bin/kick:139), so prompt.xml should reference it as {{file_contents}}.
Env Surface for Hooks
flowchart LR V["VARS array"] -->|export| E["EVA_VAR_<key>"] V --> P["EVA_PROMPT_ID"] V --> CN["EVA_CASE"] E --> G["guard.sh"] E --> Vs["verify.sh"]Key Terms
- VARS — bash array of
KEY=VALUEstrings, populated by case → CLI → guard, consumed byrender. --var-file— shorthand that injects the file’s contents as the variable namedfile_contents.- EVA_VAR_* — every var is exported under this prefix so guard/verify scripts can read it without re-parsing argv.
Q&A
Q: What is the precedence order between —case, —var, and guard.sh output?
A: Case lowest, --var middle, guard.sh stdout highest. Implemented by append-order in the VARS array (bin/kick:150-182); the awk renderer applies pairs in order so later same-key pairs overwrite earlier substitutions on the document.
Q: Which env-var prefix exposes vars to guard.sh and verify.sh?
A: EVA_VAR_<key>, plus EVA_PROMPT_ID and EVA_CASE (bin/kick:162-167).
Q: Which placeholder syntax does kick substitute?
A: Literal {{key}} — exact double-brace, single token, no whitespace, no filters. The renderer is a literal awk index() swap.
Examples
kick refactor --var-file src/foo.py --var 'constraints=no new deps'# VARS = [file_contents=<file>, constraints=no new deps]# prompt.xml references {{file_contents}} and {{constraints}}neighbors on the map
- guard.sh & verify.sh Hook Contract writing a pre-send validator that aborts bad inputs
- eva eval Cases, Assertions & Judges adding a new case to eval.yml
- End-to-End Chain Execution Request Flow tracing a chain execution through the entire system
- ProtocolMessage Envelope adding a new wire message type