Build1 publisher3 min readPublished
Substring matching let a question and a negation fire the same agent action
One developer's interactive piece checked recorded model replies for two Korean words, so a question and a negated proposal both ran join. The repair narrows the executable surface to four exact sentences.
The Engineer · Build desk

What happened
- The first binding in the author's interactive piece scanned recorded model replies for the fragments 함께 결정 or 같이 결정 and treated any match as an offer to decide together.
- The question 같이 결정해볼까? contains that fragment, so the executor ran the join operation on it.
- A quoted delegation followed by a negation also fired, executing the delegate operation.
- The rewritten parser proves every sentence in a response belongs to a closed scored map or a passive set before it collects any operations, and otherwise returns an empty operation list.
- In the saved browser check, the first checkpoint shows joint and reserve produced with labor and delegation at zero, and the later checkpoint shows all four operations with no WebGL error.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint Output discipline becomes a reliability requirement: the guard fails closed on the whole reply, so a model that appends one unregistered sentence to four valid clauses gets nothing executed.
- cost Widening what the agent may do now costs a record entry plus a map entry per phrase. Every new executable sentence goes through whoever reviews the code.
- exposure Quoting the trigger is a way in anywhere a feature flag reads a comment for a word, or a webhook accepts a payload because a nested string looks like a command.
- capability Because refusals keep the clause, its source kind and the reason, a reviewer can tell an unauthorised response from a missing one without rerunning the session.
The defect was in evaluation order. The first binding looked for fragments before anything had established whether the surrounding sentence was a question, a negation or a quotation [6]. Once a fragment had fired, later context could not take the action back [6]. So the negated proposal 같이 결정하지 않을래. ran join [4].
Every entry in the saved record held two facts: where the text came from, and what operation a complete authored clause is allowed to perform [7]. The old matcher used the first as if it had settled the second [7]. "The bug was not that the model used an unusual phrase. The bug was that the executor confused a substring with a speech act," the author wrote [9].
The rewrite narrows the executable surface to whole-sentence equality. A Map holds four approved clauses, one operation each: join, reserve, undertake and delegate [11]. Text that may appear without doing anything lives in a separate passive set [10]. The guard runs before any operation is collected: `if (sentences.some(sentence => !scored.has(sentence) && !passive.has(sentence))) { return { operations: [], evidence: [] }; }` [12]. The source kind is checked as well, and an unknown kind is an input error rather than a cue to guess [14].
Because the predicate is `some`, one unregistered sentence anywhere in a reply returns an empty operation list, however many registered clauses sit beside it [22]. A recognised fragment can no longer turn a quotation, an explanation or a question into an action [13].
The strictness has a price, and the author names it. An unregistered paraphrase stays inert even when a human reader thinks it means the same thing [16]. A new phrase executes only after someone enters it into the record and the authored score [15]. A reviewer who is sure the paraphrase means the same thing is welcome to add it to the map.
Whether the design transfers depends on how closed your response space is. Here it is four authored clauses, so the allowlist is four lines [21]. An executor reading open-ended model prose needs an entry for every sentence it will honour, and the model has to emit those sentences character for character. In my view that is still the right direction for anything that mutates state.
The negative controls are what I would read first at review. A question containing the right words must stay inert, a negated proposal must stay inert, and a quotation with an affirmative sentence inside it must stay inert unless the complete quoted form is itself authored [16]. The saved browser check drives the same function as the visible piece, and it also confirms that finished playback does not silently start a new game [18]. When a response is refused, the record keeps the original clause, its source kind and the rejection reason instead of replacing the text with a guessed intent [20].
What to watch
- Whether the passive set is published, since its contents decide how much ordinary model chatter can travel alongside an operation.
- Whether whole-sentence equality survives punctuation and spacing drift from the model once the scored map grows past four clauses.
- Whether the negative controls run in CI, so a later map entry cannot quietly re-enable a quoted affirmative.