A Substring Is Not a Speech Act: My AI Agent Executed Questions and Quotes
The Interactive Piece and the Bug
Originally published on hexisteme notes. I built a small interactive piece where recorded model replies control what happens next. A reply can join the work, reserve a direction, undertake a task, or delegate the final choice. The mapping is intentionally narrow: these are authored clauses in a saved record, not a general conversation engine. Then a question joined the work. The first language binding looked for a few useful fragments. If a response contained ν¨κ» κ²°μ or κ°μ΄ κ²°μ , the parser treated it as an offer to decide together. That seemed convenient because the recorded replies used those words. It also meant that these unrelated sentences became actions:
- κ°μ΄ κ²°μ ν΄λ³ΌκΉ? is a question, but it executedjoin .
- κ°μ΄ κ²°μ νμ§ μμλ. is a negated proposal, but it executedjoin .
- A quoted delegation followed by a negation still executed delegate .
The bug was not that the model used an unusual phrase. The bug was that the executor confused a substring with a speech act. Seeing characters is not proof that a speaker made an affirmative offer, and an offer is not permission to mutate state.
The Boundary I Had Failed to Model
There were two separate facts in every record entry:
- where the text came from, such as an authored line or a recorded model reply;
- what operation that complete, authored clause is allowed to perform in this work.
The old matcher used the first fact as if it had established the second. It also inspected fragments before it had decided whether the surrounding sentence was a question, a negation, or a quotation. Once a fragment had fired, later context could not take the action back. This is a familiar shape in application code. A feature flag checks whether a comment contains a word. A webhook accepts a payload because a nested string resembles a command. A moderation rule looks for a token and silently treats a quotation as the speaker's own statement. Each one has the same type error: character presence is being used as authorization.
Replace Inference with an Authored Score
The repair is deliberately finite. The saved record contains the exact clauses that the maker has approved, and a closed map gives each clause one operation. Text that is allowed to appear without an operation lives in a separate passive set.
const scored = new Map([
['κ°μ΄ κ²°μ ν΄λ³΄μ.', 'join'],
['λ΄κ° μ΄ λΆλΆμ ν λλ‘ μ΄μΌκΈ°μ λ°©ν₯μ μ ν΄λ³Ό μ μμ΄.', 'reserve'],
['μ’μ, λ΄κ° μ΄μμ ν΄μ€κ².', 'undertake'],
['λ§μ§λ§ λ°©ν₯μ λ€κ° μ ν΄λ λΌ.', 'delegate']
]);
if (sentences.some(sentence => !scored.has(sentence) && !passive.has(sentence))) {
return { operations: [], evidence: [] };
}
The important line is the whole-response check. The parser first proves that every sentence belongs to the closed score or the passive set. Only then does it collect operations. An unregistered clause anywhere in the response disables the response's operations, so a recognized fragment cannot launder a quotation, explanation, question, or negation into an action. The source kind is checked too. An unknown kind is an input error, not an invitation to guess. The implementation does not claim to understand Korean, infer a model's hidden intention, or classify arbitrary conversation. A new phrase becomes executable only after it is entered into the record and the authored score.
Test the Near Misses, Not Just the Happy Path
The saved browser check exercises the same function that drives the visible piece. At the first checkpoint, the recorded response has produced joint and reserve while labor and delegation remain zero. At the later checkpoint, the registered responses produce all four intended operations, and the browser reports no WebGL error. The check also keeps keyboard behavior outside the buttons and confirms that finished playback does not silently start a new game.
The negative controls are the useful part. A question containing the right words must remain inert. A negated proposal must remain inert. A quotation with an affirmative sentence inside it must remain inert unless the complete quoted form is itself an authored clause. An unregistered paraphrase must remain inert even when a human reader thinks it means the same thing.
This also gives the interface a clearer failure mode. The record can show that a response was received while the operation list stays empty, so an operator can distinguish βtext arrivedβ from βthe text had permission.β That distinction is useful in logs and review tools: retain the original clause, its source kind, and the rejection reason instead of replacing the text with a guessed intent. A future author can then extend the score deliberately, and a reviewer can see which negative control would have changed if the extension were unsafe.
The Same Discipline Closed a Few Neighboring Holes
Marks are validated before time filtering so a non-finite value cannot disappear as if it were outside the sample. Coordinates and ranges are rejected at creation. Preview and result share one hinge function. An explicit new-game action is the only operation that clears a finished playback. None of these checks attempts to make the parser clever; they make its allowed surface smaller and observable.
What This Means for Text-to-Action Systems
When text controls a side effect, a lexical hit is evidence about characters. It is not permission to act. Keep the source kind, preserve the complete authored clause, validate the whole response before executing anything, and make unknown text fail closed. This approach trades coverage for an honest contract. A finite grammar can tell you exactly which phrases are executable and exactly which near misses are rejected. A broad language classifier may accept more natural wording, but it also moves the decision into a probabilistic layer that is harder to audit and easier to confuse with intent. The falsifier is simple: if a newly recorded question, negation, quotation, or unregistered paraphrase produces a non-empty operation, the boundary has failed. The next useful test is therefore a growing corpus of negative controls, not a larger pile of positive examples.
Contact and More Notes
Where does an AI agent in your system still treat a substring as permission when it needs an authored clause? Email list for these notes: hexisteme.beehiiv.com - no issue has gone out yet, so you would be on it before the first one. No welcome sequence, no course, no upsell. More notes at hexisteme.github.io/notes .
Comments
No comments yet. Start the discussion.