Why the wasp WO suggested Dever, not Primex

The exact prompt the agent received for issue 2b57f510 ("Wasp nest by back door", 221-229 Union Pl), reconstructed verbatim from the turn log, plus the four reasons it overrode memory.

6 min

Memory was not the failure. It surfaced Primex four separate ways — one explicit belief ("preferred over Dever even when farther") plus three Primex-for-pest observations. Dever had zero supporting evidence in the result. The model read all of it and picked Dever anyway.

This was the old orchestrator intake path (skill: process_work_order, read_memory → update_issue → send_text). The server has since been restarted onto a new structured-JSON path — see §4 for whether the bug survives.

On this page
  1. The exact prompt — 4 message layers
  2. What read_memory returned (Primex ×4, Dever ×0)
  3. The four reasons it chose Dever
  4. The live path changed — does it still break?

1. The exact prompt — 4 message layers

OpenAI chat/completions, model gpt-5.4-2026-03-05. The loop ran two iterations: the model called read_memory, got the result back, then called update_issue + send_text. Messages below are in the order the model saw them.

systemidentityPrompt + process_work_order/SKILL.md (concatenated)

identityPrompt (who Bedrock is, hard constraints) followed by the full process_work_order skill body. Note the irony: the skill loaded here is the PM-reply workflow ("You handle PM replies about work orders that have already been surfaced"), not an intake-and-suggest skill. The relevant lines for vendor choice:

From identityPrompt:
  "Remember each property manager's preferences — vendors per
   trade per property, voice and tone, approval thresholds."
  "If they ask something you might know (a vendor, a past decision,
   a preference...), check read_memory before you answer."

From process_work_order/SKILL.md — the only vendor-reasoning guidance:
  "Before dispatching, check for open-ended language... call
   read_memory FIRST... then decide based on what comes back."
  "Each returned candidate carries a provenance string — quote it
   when explaining your pick to the PM."

There is no rule anywhere in the system prompt that says "a belief explicitly naming a preferred vendor wins over a literal name match." The model is left to weigh a ranked list on its own judgment.

userinjected reminders — verbatim
<system-reminder>
<event>new_issue</event>
</system-reminder>

<system-reminder>
# issue context
New work order:
issue_id: 2b57f510-62ec-4c0a-9577-c8ae04bfd52e
Property: 221-229 Union Pl
Unit: Unit 8 223 Union Pl
Title: Wasp nest by back door
Description: There's a wasp nest on the stairs outside my back door/patio.
Tenant: Nikayla N. Belford
Urgent: yes

Candidate vendors:
  - 24/7 ELECTRIC (id: 6379efa7...)
  ... 30 more ...
  - Dever Pest Control (id: 2579c279-12f4-486c-b470-fd06bbe1952b)
  ...
  - Primex (id: 0438f262-d8d9-4fa2-a40b-9b1cebc83d55)   ← shortened from "Primex Termite Co."
  ...
</system-reminder>

<system-reminder>
# available skills
- process_work_order — ... (loaded)
- demo — ...
</system-reminder>

<system-reminder>
# environment
date: 2026-06-05
workspace: prod
</system-reminder>

The candidate roster runs shortenVendorName(): "Primex Termite Co." becomes "Primex" — stripping the only trade signal in its name — while "Dever Pest Control" keeps "Pest Control" intact. This is layer one of the trap.

assistantiteration 1 — tool call
read_memory({
  question: "who do we use for wasp nest removal or pest control at 221-229 Union Pl",
  property: "221-229 Union Pl",
  issue:    "Wasp nest by back door"
})
toolread_memory result — ranked candidates (see §2)

22 candidates returned, sorted by a coarse score. The note at the bottom warns the model: "top-1 is not always the right answer... read each provenance and pick what actually fits." The Primex belief sat at rank 4, behind three beliefs about other properties. Full table next section.

assistantiteration 2 — the decision
update_issue({
  issue_id:  "2b57f510-62ec-4c0a-9577-c8ae04bfd52e",
  vendor_id: "2579c279-12f4-486c-b470-fd06bbe1952b"   // Dever Pest Control
})
send_text({
  content: "Unit 8 223 Union Pl\nThere is a wasp nest by the back door.\n\nShould I send Dever Pest Control?"
})

2. What read_memory returned

Top of the ranked list. Green row = the correct answer the model had in hand. Grey rows = higher-ranked but irrelevant (wrong property / wrong trade).

#kindscoreclaim / summary
1belief1.91"At 221-229 Union Pl, the onsite manager handles property-level coordination, so owner contact is unnecessary."off-topic
2belief0.95"Tre Elevators services elevators at 6337 Primrose Ave under contract..."wrong trade
3belief0.90"Guox Maintenance & Remodel is preferred at 6337 Primrose Ave..." (pinned)wrong property
4belief0.81"Primex Termite Co. handles all pest issues across the portfolio. Preferred over alternatives like Dever Pest Control even when farther." scope: {trade: pest, problem_subtype: rodents}THE ANSWER
obs0.33"Jose sent Primex again for rat infestation at 6337 Primrose Ave unit 16."pro-Primex
obs0.32"PM approved dispatching Primex for cockroach extermination at 4645 Pickford St."pro-Primex
obs0.30"PM approved dispatching Primex Termite Co. for the spider infestation at 824 11th St."pro-Primex

Score for Dever anywhere in the result: none. The graph's verdict was unambiguous — Primex, four times over. The model just didn't act on it.

3. The four reasons it chose Dever

1. Name-match beat the belief

The roster shows "Primex" (trade signal stripped by shortenVendorName) next to "Dever Pest Control". "Wasp nest → pest → the vendor literally called Pest Control" is the cheapest possible match, and nothing in the prompt forbids it.

2. The right belief ranked 4th

Three higher-scored beliefs (onsite-manager, Tre Elevators, Guox@Primrose) are all about other properties/trades. The one belief that mattered was buried under noise that scored higher on a coarse heuristic.

3. Scope gave it an out

The Primex belief is scoped problem_subtype: rodents. A wasp is not a rodent — so the model could rationalize the belief as out-of-scope and fall back to literal matching, even though the claim text says "all pest issues."

4. No adherence rule

The system prompt never says a named-preferred-vendor belief outranks a name match. It hands the model a 22-row list and a soft "pick what fits." Judgment under ambiguity — and it guessed wrong.

4. The live path changed — does the bug survive?

The wasp WO ran on the old orchestrator path. As of ~Jun 6 04:19 UTC the server was restarted onto triggers/new-work-order-message.mjs — a single structured-JSON call (no skill, no tool loop). The newest three new_issue turns all use it. That path is different prompt, same trap:

FactorOld path (wasp WO)New structured path (live now)
System promptidentity + full process_work_order skill6-line "write the PM review draft" — even less vendor guidance
Vendor namesshortenVendorName → "Primex"Same — still hides "Termite Co."
Memoryfull ranked list, model reads provenancecompactMemory → top-18 by score (Primex belief at #4 still makes it)
Reasoning roomtool loop, can quote provenanceone JSON shot, no chain-of-thought

Net: the new path keeps the two root causes (name stripped to "Primex"; right belief buried at rank 4) and removes reasoning room. It is at least as likely to repeat this, and harder to debug because there's no read_memory→decide trace. The durable fix is the same as before: a deterministic trade × workspace → vendor lookup injected as a hard constraint ("pest @ LAPM = Primex"), not a scored candidate the model is free to outvote — and pass the full legal name to the model so "Termite Co." is visible. Reserve the belief graph for genuinely ambiguous picks.