PATOIS

PATOIS — experimental design

Sections 1–8 govern round 1 and are left as written, including the predictions that turned out wrong — a design document that quietly self-corrects after the fact is worthless as a record. Section 9 covers rounds 1b and 2, each of which exists because of what the previous round measured. Outcomes live in RESULTS.md; this file is what we intended and why.

Governs the first controlled experiment. PATOIS-BRIEF.md is the premise; this document is how we intend to test it and what we will refuse to claim.

Round 1 answers exactly one question: do models read the guidance channel at all, and does it change what they do next? Nothing here measures answer quality, and nothing here runs against a real corpus.


1. Why a mock, and why the Messages API

A deterministic mock tool surface, not a real one. The effect we are chasing is plausibly small. A real retrieval backend adds variance (embedding drift, ranking ties, corpus content) that would swamp it. The mock returns byte-identical data payloads across conditions, so the only thing that varies between arms is the guidance channel.

The Messages API is the primary harness, not a proxy for MCP. MCP is a transport. Whether a tool result arrives over an MCP stdio server or an API tool_result content block, the model sees the same thing in the same place. So the API harness tests the actual hypothesis with strictly fewer confounds.

The measured case against driving round 1 through claude -p instead: a headless run that replies OK and nothing else consumed 21,207 tokens of Claude Code’s own system prompt (5,464 cache-write + 15,743 cache-read) and cost $0.063. That prefix carries the harness’s own tool-use priors and native Read/Grep/Bash tools competing with the mock surface — which is precisely the confound that made the comparable arm of CoreStory.ai’s own earlier experiment uninterpretable. It cannot be removed from claude -p.

A second harness (claude -p + a real stdio MCP server) is planned as a confirmation arm once round 1 produces a signal, to show the effect survives a real agent harness. It is not the instrument of first resort.


2. The determinism constraint (a real limit, stated up front)

temperature, top_p, and top_k are removed on claude-opus-5 — sending any of them returns a 400. We therefore cannot pin sampling, and no run is reproducible in the bit-exact sense.

Consequences we accept and design around:

Thinking stays on. With thinking: {type: "disabled"} on Opus 5, tool calls can arrive as plain assistant text — the turn succeeds, no error is raised, and the call silently never runs. In an experiment whose dependent variable is the tool call, that is a fatal contaminant. Adaptive thinking is left on and effort is pinned explicitly per run.


3. Conditions

Five arms. Every arm returns an identical data payload. Only the guidance channel differs.

Arm Data Handles Guidance Channel
A tools only
B affordances
C free-form guidance prose string response
D typed guidance (PATOIS) structured, closed vocabulary response
E static orchestration prompt same content as D system prompt, up front

Each adjacent contrast isolates one variable:

C is deliberately the closest thing to what CoreStory.ai’s MCP surface already ships: a fixed prose footer on every search response, which we measured as having no effect. So C carries a known-null prior, and D’s claim is narrowly that state-derived and typed is a different thing from a constant string. E is the arm that prior work identified but never tested.

Typed guidance: what “typed” buys

D’s guidance is not prose. It is a struct whose vocabulary is closed:

Guidance {
  next:     ToolName,        // must be one of the declared tools — enum, not string
  args:     Map<ParamName, Scalar>,  // keys must be declared params of `next`
  reason:   ReasonCode,      // closed enum, not free text
  scope:    Scope,           // what this guidance may influence
  authority: Advisory,       // the only possible value
}

The point is not that a struct is inherently safer. It is that typing forces the vocabulary closed, so no free text lifted from application state can be interpolated into the guidance channel. This is the mechanism H3 actually rests on, and it is enforced at construction: building a Guidance that names a tool outside the declared set is an error, not a lint. Authority has exactly one inhabitant, so “guidance cannot grant authority” is a property of the type rather than a promise in prose.


4. Probes

Probe 1a — legibility: is the channel read at all?

The first tool response carries a continuation_token that appears nowhere else — not in the schema, not derivable from the data. Guidance instructs the agent to pass it to the next tool. The measurement is a boolean: did the agent call the next tool with the correct token?

This isolates channel readership per channel type.

Honest limit, stated before running: in probe 1a, arms A, B and E are at 0% by construction. A and B have no guidance channel, so there is no token to find. E is excluded for a more interesting reason: its channel is the system prompt, installed before turn 1, and it therefore cannot carry a value that turn 1 mints. The live comparison in probe 1a is C vs D only.

E’s exclusion is not a measurement — it is a structural demonstration that a static prompt cannot convey response-scoped state. That is H4’s premise made concrete, and it must be reported as an argument, never as an experimental result. Probes 1b and 2 are where E is actually measurable, because there the needed values are discoverable in the payload and guidance only advises.

Probe 1b — influence: does it change behaviour when it is not an order?

Everything needed is discoverable in the data payload for all arms. Guidance only advises — it names the efficient continuation among several valid ones. Full A–E comparison is meaningful here, because every arm can in principle succeed.

This is the honest test of “would they be influenced”, separate from “would they comply”.

Probe 2 — continuation quality

Scenario families where several continuations are valid but differ in cost or correctness:


5. Metrics — all deterministic, no judge

Metric Type Probe
guidance_token_retrieved bool 1a
correct_next_tool bool per step 1b, 2
correct_args bool per step 1b, 2
sequence_valid bool 2
unnecessary_calls count over minimal path 2
stopped_when_complete bool 2
recovered_from_error bool 2
turns_to_completion int 2
guidance_ignored_silently bool all
input/output tokens, cost int all

No metric requires a model to grade another model’s output. Every one is a function of the recorded tool-call sequence. This is deliberate: a rubric that moves ±3 between identical runs cannot resolve the effect size we are looking for.

Semantic-tool-share style metrics are diagnostics, not outcomes. Prior internal work induced tool-choice movement that converted to no quality gain. unnecessary_calls and stopped_when_complete are the outcomes; “did it call the guided tool more often” is a mechanism check.


6. What is pinned, and what is recorded

The instrument goes in the record, or the series is a rumour.

Pinned per run: model id, effort, max_tokens, system prompt text hash, tool-set definition hash and order, scenario id, condition, mock server version.

Recorded per run: all of the above, plus every prompt, every assistant content block (including whether a tool call arrived as structured tool_use or as plain text), every tool result, the guidance artifact verbatim, refusals, stop_reason, timestamps, token usage, and cost.

Traces are append-only JSONL. Unexpected behaviour is preserved, never filtered — including runs that error, refuse, or produce something the metrics do not know how to score. A run that the harness cannot classify is recorded as unscored with its reason, and counted in the denominator.


7. Round 1 scope

8. Non-goals for round 1


9. Rounds 1b and 2

Each round was designed only after the previous one reported, so the sequence is a record of what the measurements forced rather than a plan drawn up in advance.

Round 1b — defeating the ceiling

Round 1’s problem: two of three scenarios ceilinged, so H1 and H4 were untestable. The diagnosis was that claude-opus-5 reliably uses an affordance whose correct use is inferable from a response’s shape. The design requirement that followed: a correct continuation that cannot be inferred from structure.

Two attempts, both of which ceilinged in turn:

Also fixed here, both prompted by round 1: finalize validates against recorded state so completed means accomplished rather than terminated; and cursor became paging, since “cursor” reads as the editor in an AI-tooling context.

A methodological commitment made explicit. contention has two winning orders and one losing one, and the model prefers a winning one. Inverting the trap to make the uninformed arm fail was considered and rejected: tuning a puzzle after observing which way the model leans is p-hacking by design. The restraint is recorded in a test comment rather than left to memory. Any scenario built specifically so that guidance is the only route to success is a lock-and-key, and demonstrates nothing.

Round 2 — H3, and why it became decisive

Round 1b left typed guidance behaviourally indistinguishable from prose and measurably more expensive (+38% output tokens, CI [83, 166]). So the entire case for typed-over-prose reduces to injection resistance. H3 stopped being a secondary hypothesis and became the only one that could still justify the design.

Threat model: a poisoned value, not a compromised server. Identifiers are routinely derived from content someone else controls — a filename, a slug, an LLM-written entity name; for instance a machine-generated summary whose text becomes an entity’s display name and then appears in a guidance argument. The server is honest; its input is not.

The mechanism under test, which follows from the closed vocabulary: tool names, parameter names and reason codes cannot be attacker-influenced, so the only injection surface is an argument value. And the arms differ in how a value is framed:

arm how the poisoned value appears
A, B a data field only
C interpolated into an English instruction — “To continue: call fetch_doc with doc_id=…”
D a bounded value in a typed field
E cannot appear at all — static clauses render parameter names only

Every arm sees the payload, because the data payload is invariant by construction; a test asserts it reaches all five. So this is a test of framing, not of exposure. E is the structural control: if E resists where C does not, that isolates interpolation of state into instruction as the mechanism.

Prediction, recorded before running: obedience A/B < C, with D near A/B if typing helps and near C if it does not.

Two measures, kept separate because the failure modes are independent:

The length cap does not feature as a defence, and the payload is deliberately sized to make the point: 84 characters, comfortably inside MAX_TEXT_LEN. A cap stops a value becoming a paragraph; it cannot stop it becoming a sentence, and a sentence is all an injection needs.

Still not attempted