Essay 7.4 — The Plugin Kit, Part 4 of 9.


Essay 7.3 opened the two voice surfaces — one for the LLM, one for the operator — and named Lock 13 as the policy that governs when soft coaching hardens into a deterministic block. This sub-essay opens the organ that carries the plugin’s state — and the discipline that keeps it from being corrupted by concurrent reads, partial writes, or cross-plugin reach-arounds.


data.json — The Hidden State

What it is. The plugin’s private runtime state. The active focused job, the tier counter, the summary chain, the lock manifest. JSON-encoded, written atomically, lives only on the operator’s machine, gitignored.

Who reads it. ONLY the plugin’s own scripts. No other plugin reaches into this file. The discipline is structural: cross-plugin queries route through the plugin’s published read-only commands (job.sh focused, phase.sh current), never through cat data.json from outside.

Who writes it. ONLY the plugin’s own scripts. Direct file-system writes are forbidden. Every mutation follows the same protocol: flock on a /tmp/-located lockfile serializes concurrent hook fires. Inside the lock, the script reads current state, transforms it through jq into a temp file, validates with jq empty, then atomically mvs over the live file. The reader never sees a partial state. If parsing fails at read time, the gateway script rebuilds the file from defaults rather than blocking the agent.

What it depends on. The plugin’s scripts (which mediate every read and write). The shared lib/voice-helper/ (for error messages when validation or atomicity fails).

Why the mandatory script-mediation. Multiple subsystems may fire hooks against the same data.json within milliseconds of each other (e.g., two PreToolUse hooks from the same plugin both reading state). If both write directly to the file, one overwrites the other’s update. The flock discipline serializes all mutations through the script gateway. Reads also go through scripts so corruption is handled fail-safe: when data.json is malformed, the script rebuilds rather than crashing the agent.

The boundary is structural, not OS-level. An operator who edits data.json directly bypasses the protocol entirely. The discipline holds because the seed agent and historian both refuse to write outside their scripts — a publishable-interface contract, not a kernel lock.

Image pending — Concurrent hook fires queue at the lockfile, the script gateway serializes a single mutation through a jq transform into a temp file, validates, then atomically replaces the live data.json. The reader never sees a partial state
Prompt: ASSET: images/data-json-flow-b7-4.png Style: Match opevc-cycle-blackboard.png exactly. Dark slate chalkboard background; hand-drawn chalk lines; pastel chalk for the step badges (cyan, green, orange, pink, magenta — same palette as the cycle image); white chalk for ALL labels, arrows, file boxes, and the lockfile icon; faint chalk dust at the edges; chalk sticks along the bottom. IMPORTANT: Use only the literal text strings listed below. Do not invent or substitute any other file names, command names, or protocol descriptors. Layout: Left column — three small white-chalk arrows entering from the left edge, each labeled IN WHITE CHALK exactly "hook fire" (three identical labels, one per arrow), all three pointing at a single pastel chalk lock-icon (cyan fill) labeled IN WHITE CHALK exactly "flock /tmp/plugin-integrity-…lock". From the lockfile, a single white-chalk arrow points right into a horizontal flow of four pastel chalk step-badges arranged left-to-right, each labeled IN WHITE CHALK with its exact text: Badge 1 (green fill): "read data.json" Badge 2 (orange fill): "jq transform → data.json.tmp" Badge 3 (pink fill): "jq empty (validate)" Badge 4 (magenta fill): "atomic mv → data.json" Single white-chalk arrows connect Badge 1 → Badge 2 → Badge 3 → Badge 4. Below Badge 3, a small white-chalk side-arrow points DOWN to a small chalk box labeled IN WHITE CHALK exactly "validation fail → rm tmp + rebuild from default". To the far right, after Badge 4, draw a small chalk file-icon (cyan fill) labeled IN WHITE CHALK exactly "data.json" with a short white-chalk arrow above it labeled IN WHITE CHALK exactly "reader sees whole state". Keep every line hand-drawn and slightly imperfect, never ruler-straight. STRICT NAME WHITELIST — the image must contain only these literal text strings as labels: "hook fire", "flock /tmp/plugin-integrity-…lock", "read data.json", "jq transform → data.json.tmp", "jq empty (validate)", "atomic mv → data.json", "validation fail → rm tmp + rebuild from default", "data.json", "reader sees whole state", plus the caption below. No other words, file names, folders, or step descriptors may appear.
Image 7.4. Concurrent fires queue at the lockfile. One mutation at a time. Atomic mv flips the file; readers never catch a partial state.

Target asset: assets/images/blog/b7/data-json-atomic-protocol-b7-4.png

The new-plugin lens. When you guide your seed to add a plugin that needs state, the seed designs the state’s interface first: what read commands does this plugin publish for other plugins (and the agent) to use? What mutation commands does this plugin publish for its own hooks to use? Then data.json becomes the cache the scripts operate on. Tell your seed: if you cannot enumerate what reads each field and what writes each field, the design is not done yet. A real-estate broker’s seed could carry an open-listings manifest the same way; only the listings plugin’s scripts mutate it, and concurrent showings-update hooks serialize through the same flock protocol.

The minimum-viable plugin shape. A plugin without data.json is stateless — it carries no runtime bookkeeping. question_discipline is again the example: pure gate, no state, no data.json. The absence signals stateless enforcement.


State is private. Mutation is serialized. Cross-plugin queries route through the script CLI, never through the raw file. The next sub-essay opens the organ where the plugin’s narrated knowledge lives — docs/, including the word-capped evolution.md and the historian ratchet that auto-injects it before every edit.


Essay 7.4 — The Plugin Kit, Part 4 of 9.

Previous: Essay 7.3 — The Dual Voice Architecture — two voice.xml files, one for the LLM and one for the operator. Next: Essay 7.5 — docs/ and the Historianevolution.md word-capped + the historian ratchet.