feat(api): let one suspect answer a question - #20
Merged
Conversation
E2's first half. A turn runs end to end, guarded at every step, and none of it
needs an API key: dossier, prompt, schema, scope, canary, stance machine and
turn accounting all work against the fake model. Only the words are synthetic.
The order is the design:
dossier -> prompt -> model -> schema -> scope -> canary -> stance -> statement
Everything before the model is projection; everything after it is verification.
The model produces one thing, a line of dialogue, and every consequence of that
line is decided by code around it (RN-022, RN-023, RN-042).
Building it found a hole the rules had anticipated and the code had not. The
culprit was alone with the victim, so no presence fact was ever written about
them at that hour, and nothing in their own dossier incriminates them — a
culprit would have answered with the same easy conscience as an innocent without
an alibi, and the mystery would have had no centre. RN-011 already allowed for
this: the Solution entity stays out, but "o culpado sabe apenas da própria
culpa". So the dossier became the boundary. It is the one place that reads the
solution, and what crosses is a single bit about oneself — never the means, the
motive, the chain, or who else might have done it.
The stance machine takes the model's suggestion and overrules it when the move
is illegal, silently for the player and reported for the evals. `broken` is
unreachable from a suggestion: a suspect breaks when confronted with evidence,
which is a computed game event and not a mood a model may adopt because the
question felt intense.
A rejected reply still costs the turn. A budget that only charged for answers
the system liked would be a budget to farm by provoking failures.
Also fixes the fake to omit optional fields rather than invent them. It was
filling fact_referenced with a synthetic id, which the scope guard correctly
rejected — an invented id is precisely what that guard exists to catch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First half of E2 (#6). A suspect answers, and every consequence of the answer is
decided by code.
The order is the design
Everything before the model is projection. Everything after is verification. The
model produces one thing — a line of dialogue — and nothing it says decides
anything (RN-022, RN-023, RN-042).
All of it runs with no API key:
Synthetic words, real pipeline.
The hole this found
The rules anticipated it; the code did not. The culprit did not know they were
the culprit.
They were alone with the victim, so no presence fact was ever written about them
at that hour — no witness, no fact. Nothing in their own dossier incriminates
them. A culprit would have answered with the same easy conscience as an innocent
who happens to lack an alibi, and the mystery would have had no centre.
RN-011 already covered it: the
Solutionentity stays out of a suspect'scontext, but "o culpado sabe apenas da própria culpa". So
Dossierbecame theboundary — the one place that reads the solution, emitting one bit about
oneself and nothing else. Not the means, the motive, the chain, or who else
might have done it. Tests assert exactly that.
Worth naming why it stayed hidden: an invariant that nothing exercises is an
invariant nobody has checked. It surfaced the moment an NPC actually had to
answer.
The stance machine
The model suggests; the machine decides (RN-023). An illegal move is overruled
silently for the player and reported for the evals.
brokenis unreachable from any suggestion. A suspect breaks when confrontedwith evidence that invalidates what they said — a computed game event (phase 4),
not a mood a model adopts because the question felt intense.
A rejected reply still costs the turn
Deliberate. A budget that only charged for answers the system liked would be a
budget a player could farm by provoking failures.
Also here
The fake now omits optional fields instead of inventing them. It was filling
fact_referencedwith a synthetic id, which the scope guard correctly rejected —an invented id is precisely what that guard exists to catch, so the fake should
not manufacture one.
prompts_dirmoved to a shared module:interrogationwas importing it fromveneer, coupling two packages to find a directory.Not here yet
Persistence (the match lives in memory), the HTTP endpoint, streaming, and
memory retrieval with pgvector. And the words: the provider is in pilot.