Each block isolates one decision an agentic system forces (what counts as done, when knowledge
loads) and gives a verdict with its reasoning. Under each block: how my own system answers it.
THE LOOPspecificationWhich part of the agent loop is yours to engineer?The one around the turns: what done means, what checks it, and what stops it.
Both loops in one picture: the tool loop feeds a real result back for the next decision, and the task loop checks the answer against the goal.
Two loops: the tool loop inside, the task loop around it
Each turn the model chooses: call a tool, or answer. A tool call sends a real result back for the next decision, and that cycle is the tool loop. An answer leaves it, and the task loop takes over: the success test checks the answer against the goal, and it passes to done or returns for another pass. The green dot marks the model-call step, re-entered every pass, where the branch is decided. The success test is a plain box and the stop rule a code-owned cap: both are code, not the model.
The agent loop is two loops. The tool loop alternates model calls and tool results until the
model answers, and it is standard machinery: an SDK can run it for you, or you can run it
yourself against the API. The task loop sits above it, and that is the engineered one: the
goal, what done means, the checks along the way, and the bound.
All four of those live in one file, written before the work starts: the goal as stated, then
numbered sub-goals, each carrying the check that would prove it false. The loop climbs
against that file and closes a sub-goal only on evidence. What follows is how to write one
so it holds up under a run that would rather be finished.
freeze the goalFreeze the stated goal verbatim before writing a single sub-goal. Everything downstream optimizes toward whatever the goal says, so a paraphrase means the run spends itself optimizing the paraphrase. Frozen in a field nothing may rewrite, softening shows up as a diff instead of a feeling.
the substitution a frozen line catches
stated_goal: |
make the nightly report land in my inbox before I start work
wanted to close on: "report job rescheduled to 05:00"
against the goal: not the same claim. the job takes three
hours. reopened.
name the checkGive every sub-goal the check that would prove it false, on the same line. A sub-goal with no named check negotiates its own standard at closing time, and the standard always drops. Written next to it, the spec stops describing the test suite and becomes one.
a sub-goal carrying its own falsifier
- [ ] SUBGOAL-4: every quote in the research file appears
verbatim in the source it names.
Check: a script matches each quote against its source;
an unmatched quote kills the entry.
name the antiWrite at least one sub-goal about what must not happen. A spec of what to build passes cleanly when the build also did three things you did not want, and prose about what is out of scope does not test.
the same shape, pointed the other way
- [ ] SUBGOAL-5 [MUST NOT]: no private detail reaches a
publishable field.
Check: scan every publishable line for file paths, real
names, dates. Zero hits.
quote evidenceClose a sub-goal only on evidence you can quote in the same breath. A gate that reads your wording passes on rewording; a gate that reads the actions actually recorded does not.
real close lines from the run behind this page
SUBGOAL-3 closed: model line reads "executed=fable-5" on all
six run logs
SUBGOAL-4 closed: 128 of 128 quotes matched their named sources
SUBGOAL-5 closed: 0 hits, 15 patterns, 384 lines
never renumberNever renumber a sub-goal. Split it into children and tombstone what you drop. The number is the only stable handle between a run and the record of it, so renumbering breaks every reference at once and silently, and the failure presents as work that vanished rather than an id that moved.
a split and a tombstone, side by side
- [ ] SUBGOAL-7.1: umlauts survive the export
- [ ] SUBGOAL-7.2: the file opens with no import dialog
- [~] SUBGOAL-9: [DROPPED, superseded by 7.2]
unverifiedWhen the right check is unavailable, mark it unverified and never substitute a weaker one. The cheap check is always available, which is exactly why it gets reached for the moment the real one breaks. A named third state lets you ship past something unchecked on purpose instead of by accident.
not passed, not failed, and not quietly closed
- [ ] SUBGOAL-6 [UNVERIFIED]: renders correctly on a phone.
The real browser is wedged.
Not closed. Re-check before deploy.
state, not todoBetween runs this file is the project’s state, not a task list. A task list says what is left and says nothing about what is currently true, while sub-goals plus the checks that closed them answer what the system does right now without reading the code.
what the file answers between runs
phase: complete
progress: 14/16 closed sub-goals, counted, never a guess
so "add a feature" means "add sub-goals that do not hold yet"
My instanceOne way to build the task loop: a written definition of done, and a claim graph when the work is big enough.
One way to build the task loop, not the only way. A written definition of done fits almost any task; the claim graph is the heavier tool, for work big enough to need it.
Definition of done
Done written down before the work starts, as numbered checks, each naming the tool result that would prove it false, with the evidence kept beside it. The spec of record for one piece of work, and it outlives the session that wrote it. It fits almost any task.
The heavier version, for work whose checks depend on each other: the definition of done becomes a graph, and the loop walks only the ready frontier, the checks whose dependencies are already met. A check stays a candidate until its evidence is reconciled; a gap found later can reopen it and lower the verified count. Not every task needs it.
a claim-first method
INTEGRATIONcompositionHow should an agent reach a system it does not already know?Through something that describes itself, so the knowledge lives with the connection instead of in the prompt.
agent appagent appagent app
→MCP→
a systema system
Describe a system once through MCP, and any client that speaks it can call it. N clients and M systems meet through one hub instead of N×M separate wirings.
MCP, the Model Context Protocol, is an open standard for connecting an agent to external
systems: data sources, tools, and workflows, through one contract instead of a custom
integration per system. The protocol is stateless as of its 2026-07-28 revision:
self-contained requests, capabilities negotiated per request, so a server deploys like
any other HTTP workload.
Method
Where the knowledge lives
When it fits
the API directly
in docs, an SDK, or the context you assemble
the system is well known and the call is simple
a command-line tool
in the tool, read at run time from its help output
a controlled execution environment, broad reach, output you parse
an MCP server
in what the server advertises: tools, resources, prompts
the integration should be bounded, stable, and reused
text outA model emits text. Everything real runs outside it.
the axisAPI, CLI, MCP. Same question each time: where does the knowledge to make the call live.
describe onceAn MCP client can discover and call what any MCP server exposes. N times M wirings become N plus M. With an API or a CLI, each client has to know that interface itself.
mcp vs skillMCP carries the how: tools for actions, resources for data, prompts for workflow templates. A skill carries the when, and how calls chain. That is the split drawn today; a working group is drafting skills into MCP itself.
the taxA tool the model can always reach sits in its context, costing tokens whether it is called or not.
My instanceCLI-first across a trusted host, MCP where an integration is bounded and durable.
One way to make the call, not the only way. The interface follows the scope: a broad operator runtime leans on a CLI, a bounded or shared integration on MCP.
CLI-first, MCP for the bounded, durable ones
My agents are generalist operators in one trusted host, so they reach most systems through typed command-line tools with JSON output, the broadest and fastest substrate. I add MCP where an integration should stay stable and bounded and can carry its own schemas and context. The interface follows the scope.
one answer: CLI-first, MCP where scope demands it
Knowledge lives closest to its trigger. A skill attaches know-how to a situation; a self-describing tool attaches it to the tool call, the tightest trigger there is. MCP absorbs tool-shaped knowledge; skills keep workflow-shaped knowledge.
SKILLScompositionWhen should knowledge load?Only when its relevance triggers. Cold until then.
the model’s goal→semantic match→load that skill
descriptions, always loaded
reviewing a design
researching a topic
shipping a claim
what loads, and when
descriptionalways in context
bodyloads only when picked
resourcesload as the skill runs
A description says in plain words when its skill applies. The model matches that against what it is trying to do and routes itself to the one that fits, by meaning rather than by a keyword you typed. Only the skill it picks ever loads.
routingIn a skill-based agent, descriptions are the routing layer; optimize them for routing, not documentation.
description decidesThe agent can pick a skill mid-run from the task in front of it, with no prompt from you at that moment. The description controls that routing: specific enough to match the intent, distinct enough not to misfire on neighbours, tight enough to stay loaded.
the contractA skill is a narrow responsibility with explicit inputs, outputs, failure modes, and safety boundaries.
no triggerAn invariant has no trigger moment, so it cannot be lazily loaded. A rule that must always hold goes where the agent always sees it.
the tradeLazy loading buys context and pays in routing risk. Better models change the price, they do not remove the trade.
You loaded this block by triggering it; the rest stayed collapsed until you asked.
The kinds of skillA universal taxonomy: what triggers each kind, what it carries.
Kind
Triggered by
Carries
Integration
reading or acting through a specific external system
the system’s surface: its commands, auth, and quirks
Operation
a recurring procedure with a defined end state
the runbook: ordered steps, verification, cleanup
Expertise
building, fixing, or diagnosing inside a domain
judgment: what good looks like, and the failure modes
Generation
an artifact to produce or transform
the pipeline from input to finished artifact
Evaluation
a claim or artifact needing an independent check
the criteria, kept blind to whoever produced it
Orchestration
work that should run on another agent or model
routing rules: who gets what, how results come back
Method
a class of problem, whatever the domain
a way of thinking: how to decompose, analyze, synthesize
My instanceThe same taxonomy, filled with skills I actually run.
Integration
├─GoogleWorkspacethe Workspace CLI surface, taught once
├─Telegramsend, read, and thread messages
└─CloudflareWorkers, KV, and DNS from one tool
Operation
├─GitWorkflowcommit, review, push, to a clean end
├─TriggerDevDeployship a task to the runner
└─Migratemove a system without losing state
Expertise
├─CreateCLIwhat a good command-line tool is
├─Frontendhow a page should be built
└─Troubleshootingfind why it broke
Generation
├─DocForgeturn inputs into a document
├─VisualExplainerturn an idea into a diagram
└─LogoGeneratorturn a brief into a mark
Evaluation
├─autoreviewa fresh model reviews the diff
├─RedTeambreak the claim before it ships
└─Evalsmeasure the output against a bar
Orchestration
├─Delegationhand the work to another agent
├─Councilseveral models weigh in, then decide
└─CodexBridgeroute a task across to Codex
Method
├─FirstPrinciplesdeconstruct to axioms
├─RootCauseAnalysistrace a symptom to its cause
└─SystemsThinkingsee the whole, not the part
Three of these encode judgment rather than commands: autoreview has a fresh model review the diff before it lands, FirstPrinciples deconstructs a problem to its axioms, Delegation routes work to another model.
Projects
Repositories I built. Each card says what it does and what was measured.
project
audit-grade-rag
A self-hosted question-answering system over a fixed document collection. It rejects answers without supporting passages, verifies each citation against retrieved text, and records the inputs and model version so later runs can expose changes.
RAG over 497 near-identical MOSFET datasheets, graded on 2,718 questions. Dense retrieval alone finds the right one about two times in three. Fusing in the part number fixes it.
A read-only MCP server that lets an agent query an agent-commerce standard by section. It serves a committed index built at a pinned commit, hash-locked so it cannot drift.
Five agent patterns on the Claude Agent SDK, from one one-shot agent up to an orchestrator with parallel sub-agents and a plan-execute-reflect flow that splits roles by model cost.
A published n8n community node that swaps many per-tool model calls for one. Where the agent node grows calls quadratically as the pipeline lengthens, code-mode stays at a single execution.
Messy documents to schema-validated JSON: OCR, model extraction, and a labelled golden corpus to score accuracy against, each number naming its provider and model. The corpus runs the real extraction path, so the score is measured, not asserted.
project
openclaw-hardened
A six-layer prompt-injection defense for a self-hosted AI agent, run as a native plugin across five gateway hooks. The runtime is external and the config auditor is a third-party plugin; my contribution is the hardening.
An implementation-architecture reference for a Storj storage-node cluster: topology, monitoring, hardening, and an operations runbook. The architecture is the deliverable, with no earnings or runtime claims.
Three infrastructure references for solo operators in Germany: storage, compute, and validation. I selected activities where the operator does not hold or manage another person's funds, because custodial work may require financial licensing.
A SIEM alert enters, a scored and MITRE-mapped triage decision comes out. Every threat-intel lookup fires in parallel inside one sandboxed call, so the model reads the result once.
An independent verifier for an agent-commerce standard. It rebuilds each specified result byte for byte and reports mismatches with the relevant section of the standard. It is read-only and holds no wallet or keys.
A German phone agent for medical practices: booking against a real availability API, emergency detection, call transfer, holiday-aware greetings. 156 tests. It has never run against a live practice.
Invoice PDFs into structured JSON: text extraction, schema-constrained model output, a low-confidence review queue, and accuracy reported against a small ground-truth corpus rather than asserted.
An MCP server that hands any client a sandbox: the model writes TypeScript that chains tool calls, runs once, and returns. Published to npm, forty-four tests.
Agents that can read blockchain state but cannot publish a transaction without explicit permission for that action. When an authorized action runs, the system records the request, decision, transaction, and result.
Claude Code as an always-on agent I reach from my own phone: a bridge to the CLI, persistent memory, guardrails, and an agent-to-agent pipeline. 460 tests.
The operator layer around an upstream node runtime I did not write: setup, preflight, verification, burn-in, recovery, and a harness that detects a dirty host. No tenant data, no live fleet data.
Five POC workflows proving the n8n lifecycle can run without clicking, authored from the terminal. A measured run collapses eleven model calls into one, five times faster.
The system records the retrieved passages, prompt, model settings, answer, and citations during each run. Those details cannot be reconstructed reliably afterward.
Because
Nothing downstream can reconstruct which chunks came back, which prompt ran, which seed.
Unless
No one has to defend the answer afterwards. Then the answer box is the product.
Minimal to rebuildAn append-only ledger with each row hashed onto the row before it, and a claim validator that rejects an answer whose citations do not resolve to the snapshot that was retrieved. Build those two first, because the signing and the report bundles that sit on top of them are only worth as much as they are. Low-evidence retrieval refuses before the model is ever called.
An answer is an event, not a string
Triangle = a trigger. Plain box = a deterministic step. Green dot = a model call. Dashed frame = a zone. Dashed arrow = a read-back. Only one box in this picture is a model, and it is the only one that cannot be replayed exactly. Everything the replay compares was recorded before it ran.
Claim
To a vector, the neighbouring MOSFET looks almost identical to the one asked for.
Because
An embedding places them by what they describe, and they describe the same thing.
Unless
The corpus is small enough that no two parts look alike.
Minimal to rebuildOne route ingests the datasheets, one answers queries, and one runs the evaluation. A separate parser derives the test questions and expected answers. The retrieval system never sees that parser, so its answers are graded against an independently produced reference. Another 183 parts stay outside the index to test whether the system refuses instead of using a neighbouring part. The fused primary-key lookup scores 1.000; dense retrieval alone scores 0.643.
Semantic recall picks the wrong part
Triangle = a trigger. Plain box = a deterministic step. Green dot = a model call. Dashed frame = a zone. Embedding is the only model call; the fusion is plain code. The questions are generated from the datasheets by a parser the system under test never sees, so the label and the answer come from different mechanisms, which is the only reason grading one against the other means anything.
Claim
The expensive part was never the API calls. It was re-reading the history.
Because
Each enrichment turn replays the system message, the alert, and every prior response.
Unless
The intel APIs are the slow part. Then the model overhead is noise.
Minimal to rebuildOne node that gathers every intel source in a single Promise.allSettled, and one model read over the combined result. A seventh source then costs a hundred milliseconds of parallel HTTP and no model overhead. Split enrichment back into per-source turns and the token curve bends the wrong way again.
The model should read the evidence once
Triangle = a trigger. Plain box = a deterministic step. Green dot = a model call. Dashed frame = a zone. The top rail is the cost being avoided; the bottom rail is the workflow. The whole idea is one line of code: replace four model round-trips with one Promise.allSettled and a single read.
Claim
A standard cannot be judged precise by reading it, only by implementing it.
Because
Ambiguity shows up where two conformant implementations would disagree on a byte.
Unless
The spec is formal enough to be machine-checked without an implementation.
Minimal to rebuildA canonicalizer, a hash over the signed scope, and a signature check against a fixed registry. Then the part that earns its keep: when an expected byte does not reproduce, write the section number, not just a failing assertion. When bytes differ, I first check whether the verifier is wrong. I report a specification problem only after the verifier itself reproduces correctly. It holds no wallet and no keys, because a verifier that can transact is one that has to be trusted.
Implementing the spec is how you read it
Triangle = a trigger. Plain box = a deterministic step. Dashed frame = a zone. Dashed arrow = a read-back. No model runs here, so no accent dot. The output that earns its keep is the section-referenced disagreement, written back to the standard in its own numbering.
Claim
The voice layer only speaks. Booking and escalation live on the server.
Because
A prompt rule can be talked around, and a signature check cannot.
Minimal to rebuildA voice provider that emits tool calls, and a server that verifies the webhook signature before doing anything. Everything a wrong answer would cost, booking and escalation, lives on the server side of that boundary, where a test can reach it.
The model talks, the server decides
Triangle = a trigger. Plain box = a deterministic step. Green dot = a model call. Dashed frame = a zone. The one accent dot is the whole voice stack, and it is the only part that guesses. Booking, transfer and signature checks are code, which is what puts them inside the reach of the 156 tests.
Claim
An extractor that returns a value for every field hides its worst outputs.
Because
A wrong field and an uncertain field look identical once both are filled.
Minimal to rebuildExtraction, schema validation, and a confidence threshold that sends the uncertain parses to a queue instead of passing them through. The correction is stored back, so a later invoice from the same vendor retrieves it as a prior. The pipeline improves because a human closed the loop, not because the model got better.
Low confidence is a queue, not a guess
Triangle = a trigger. Plain box = a deterministic step. Green dot = a model call. Dashed frame = a zone. Dashed arrow = a read-back. Remove the gate and the pipeline still runs, but it stops reporting which answers it was unsure about.
Claim
The token saving is a side effect. The move is reading the output once.
Because
A per-call chain carries every intermediate result forward in the transcript.
Unless
There is only one call. Then there is nothing to chain and nothing to save.
Minimal to rebuildA sandbox with a timeout and a memory limit, a way to register tools as typed interfaces, and one entry point that runs a model-written chain and returns a single result. Isolation is not optional: the model is writing the code that runs, so the sandbox boundary is the security model.
Write the chain, do not narrate it
Triangle = a trigger. Plain box = a deterministic step. Green dot = a model call. Dashed frame = a zone. The model call is authoring, not orchestration. The saving is not fewer tools, it is the model reading their combined output once instead of replaying it after every call.
Claim
A pin without a failing check is a comment, and comments do not stop drift.
Because
CI re-hashes the spec at the pin, so bumping it without regenerating cannot ship.
Minimal to rebuildA generator that reads the spec once at a pinned commit and emits a committed index, which the running server loads by static import. Every response is stamped with the spec commit, so a caller knows which words they got.
The server cannot drift from the spec
Triangle = a trigger. Plain box = a deterministic step. Dashed frame = a zone. No model runs here, so no accent dot. Drift is caught by a blocking job that re-derives the hash, which is what makes the pin a lock rather than a label.
Claim
One strong model doing all three roles pays a premium rate for the cheap parts.
Because
Planning and critique are cheap work next to the research execution.
Minimal to rebuildA planner, an executor, and a reflector, run once in a line. The saving is assigning the strong model to exactly one of the three roles, not to all of them. The reflection has to score plan coverage, source quality and contradictions, or it is a rubber stamp.
Spend the strong model only in the middle
Triangle = a trigger. Plain box = a deterministic step. Green dot = a model call. Three model calls, and only the middle one needs to be strong. The reflection is a final self-critique that flags gaps in the draft; the three phases run once, in a line, not as a loop back into execution.
Claim
A single filter is a single point of failure.
Because
The payload that slips it reaches everything behind it.
Unless
The layers all screen the same class of attack. Then six filters are one.
Minimal to rebuildOne layer that blocks the obvious injection, hooked into the gateway so it runs on every inbound message. Then add layers for the classes the first one misses. The install-time validate check is what stops the whole thing from being decorative.
Six layers around a runtime I didn't write
Triangle = a trigger. Plain box = a deterministic step. Dashed frame = a zone. The runtime is external, so no accent dot marks it. With six independent layers across the agent's lifecycle, defeating one still leaves five.
Claim
A reference dressed in earnings and uptime it never had is unfalsifiable.
Because
Nobody can check a number that came from no run.
Minimal to rebuildA reproducible topology, a hardening checklist, and a scope statement that names what the artifact does not claim: not a turnkey runtime, no earnings, never run under sustained load. The scope statement is what makes the rest safe to publish under a real name.
The architecture is the deliverable
Plain box = a documented component. Dashed frame = a zone. No model runs here, so no accent dot. The right column is drawn at the same size as the left, because what the artifact does not claim is part of the artifact.
Custodial means holding other money, staking for others, managing funds.
The doctrine file puts custodial activity inside BaFin licensing.
Non-custodial stays inside the Gewerbe envelope: storage, compute, own-stake validation.
The doctrine file states what the templates refuse to claim.
Minimal to rebuildPick the boundary first, then the projects. Name the regulatory line explicitly, place each reference on the safe side of it, and write down what the templates refuse to claim. The boundary is the design input, not an afterthought.
The boundary picks the projects
Plain box = a documented option. Dashed line = the boundary. No model runs here, so no accent dot. The interesting decision was made before any code: the three references exist because they are the infrastructure a solo operator can run without stepping over the licensing line.
Claim
Acting requires a grant issued on purpose, for that one action family.
Because
An agent holding a wallet acts by default, and a default is not a decision.
Minimal to rebuildA read path that is always open, and a write path that is closed until a specific grant opens one action. The act path leaves a proof packet; the preview simply spends nothing.
A wallet-backed agent that spends nothing by default
Triangle = a trigger. Plain box = a deterministic step. Green dot = a model call. Dashed frame = a zone. The model decides, but the decision does not reach the chain without a grant. The proof packet is written when the agent acts; the no-spend preview returns no record.
Claim
Background work survives the terminal closing, and resumes where it left off.
Because
Claude Code stops when its terminal does, and a phone has no terminal.
Minimal to rebuildAn always-on process that relays messages to the CLI and back, plus a memory store so a new session is not a blank one. Guardrails come next: once it is reachable from a phone, refusing the wrong action stops being optional.
A terminal agent you can reach from a train
Triangle = a trigger. Plain box = a deterministic step. Green dot = a model call. Dashed frame = a zone. Dashed arrow = a read-back. The CLI is the same tool it always was. What changed is where it lives: the bridge keeps it up, the memory survives the session, and the guardrails gate the bridge's own operations, though not the CLI's native tools, while you are away.
Claim
The dirty-host detection is the part a naive kit skips.
Because
A host that quietly drifted from clean fails months in, not on install.
Minimal to rebuildA verifier that proves a host is healthy and a harness that detects when it has drifted. Setup and recovery wrap those. It holds no tenant inventory and no live fleet data, because an operator kit that carries a customer's secrets is a liability.
The runtime is upstream, the operations are mine
Triangle = a trigger. Plain box = a deterministic step. Dashed frame = a zone. The runtime is upstream, so no accent dot claims it. The deliverable is the operations layer, and the dirty-host detection is the part a naive kit skips.
Claim
A score is only evidence if the corpus runs the same extraction path a real document does.
Because
A score measured on a different path is measuring a different system.
Minimal to rebuildA labelled corpus of synthetic German cases, scored field by field and cell by cell, with every number naming its provider and model. The corpus now runs the real extraction path, so the score is a measurement: 264 of 264 fields, 161 scalar and 103 table cells.
A number without its model is a rumour
Triangle = a trigger. Plain box = a deterministic step. Green dot = a model call. Dashed frame = a zone. Dashed arrow = the corpus feeding the same extraction path. Every accuracy result is compared with a labelled set of expected fields and table cells. The corpus runs through the real extraction path, so the number is measured, not asserted. A headline accuracy figure still means nothing until it names the provider and model that produced it.
In the agent node, every tool call is another model round-trip.
Each round-trip carries the whole conversation history with it.
A measured five-tool pipeline: eleven model calls collapse to one.
An estimated eighteen thousand tokens down to seven hundred.
Twelve and a half seconds down to two and a half.
Minimal to rebuildOne node lets the model write a tool chain that runs once instead of calling each tool through another model turn. In the measured five-tool workflow, eleven model calls fell to one. The difference grows when more tools would otherwise require more model round-trips.
One call, however long the pipeline gets
Plain box = a measured value. Green dot = a model call, marked at every pipeline size on the code-mode row. The accent stays flat across all four columns while the traditional row climbs, because each traditional tool replays the whole conversation and code-mode does not.
Claim
The canvas has no representation an agent can write, diff, or test.
Because
So the whole lifecycle has to leave the canvas, authoring and runtime both.
Minimal to rebuildA terminal-based loop writes, deploys, tests, and debugs the workflow. The verification writes a workflow from the terminal, deploys it, and runs that same workflow on the runtime without opening the visual editor.
Author and run, both without clicking
Triangle = a trigger. Plain box = a deterministic step. Green dot = a model call. Dashed frame = a zone. Dashed arrow = a read-back. The one accent dot is the runtime model call, now singular. The debug arrow loops to write, because code-first means the fix is an edit, not a drag.