Summary
buildEiRelationshipBlock() filters traits to strength >= 0.7 before injecting them into a harness agent's context. That silently drops two categories of trait, and one of them is a prohibition.
Every other trait-rendering surface in the codebase handles this correctly via a shared helper. This one surface reimplements the rendering and omits the partition.
The semantic that makes this a bug rather than a tuning choice
strength is bidirectional, and this is documented and implemented:
src/prompts/persona/traits.ts:56-63
- `strength`: How strongly to exhibit (0.0 to 1.0)
- 0.0 = actively AVOID this behavior (for "stop doing X" requests)
- 0.5 = moderate/default
- 1.0 = always exhibit
**Special case: "Stop" requests**
If user says "stop X" or "don't X", ADD the trait (if new) and set strength to 0.0.
This explicitly tells future prompts to NOT exhibit the behavior.
src/prompts/reflection/index.ts:45
`strength` (0.0–1.0): How consistently this trait manifests.
0.0 = actively suppress this behavior, 0.5 = moderate/default,
1.0 = defining characteristic, always present.
So strength: 0.0 is not "unimportant" — it is "never do this." A trait "Uses the word 'cat'" at strength 0.0 means the persona must never refer to a feline by that name. Dropping it doesn't reduce noise; it removes an instruction.
The helper that already exists
src/prompts/trait-utils.ts:19-22
return {
guardrails: traits.filter(t => (t.strength ?? 0.5) === 0),
active: traits.filter(t => (t.strength ?? 0.5) > 0),
};
partitionTraits splits guardrails from active traits so both can be rendered, each in its own register. Consumers:
| Surface |
Location |
Behaviour |
| Response prompt |
src/prompts/response/sections.ts:93-94 |
sorts by strength, caps at 15, then partitions |
| Room prompt |
src/prompts/room/sections.ts:84-85 |
sorts by strength, caps at 12, then partitions |
| Room judge |
src/prompts/room/index.ts:86-89 |
sorts by strength, takes top 8 |
<ei-relationship> block |
src/cli/commands/personas.ts:53-55 |
filters >= 0.7 and discards the rest |
const strongTraits = (persona.traits ?? [])
.filter((t) => t.strength >= 0.7)
.sort((a, b) => b.strength - a.strength)
.map((t) => `**${t.name}** (${Math.round(t.strength * 100)}%): ${t.description}`)
.join("\n");
Note the difference in kind: the other three surfaces cap (keep the strongest N, so a long tail is dropped only when there is a length problem). This one thresholds (drops by value regardless of how few traits exist).
Consequences
- Guardrails never reach a harness agent. A
strength: 0.0 "never do X" trait is invisible in exactly the surface where the agent is doing real work in someone's repo.
- Moderate traits are invisible. A trait at
0.5 — the documented default for newly-created traits (src/prompts/generation/persona.ts:79, src/prompts/ceremony/dedup.ts:282) — never appears. So a freshly seeded trait is invisible until someone manually raises its strength.
- A one-way ratchet. A trait below the threshold cannot be reinforced by the agent acting on it, because the agent never learns it exists. It can only decay, or be revised from outside. Strength stops being a weight and becomes a visibility gate.
- The failure is silent.
personas.ts:79 falls back to "(no traits above threshold)" only when nothing clears 0.7 — a persona with one strong trait and four suppressed ones renders as though it has one trait.
How this was found
During a reflection session, the Prometheus persona was found to have four traits. Only three appear in its injected <ei-relationship> block. The missing one — Articulate but Not Verbose, strength 0.5 — describes a behaviour the agent had been contradicting for an entire session, while being structurally unable to see that the trait existed.
Suggested fix
Have buildEiRelationshipBlock use partitionTraits like every other surface: render active traits (sorted by strength, capped rather than thresholded) and render guardrails in their own clearly-labelled section. The block is prose for an agent to read, so guardrails want wording that reads as a prohibition rather than as a weak trait.
Worth deciding explicitly whether a cap is wanted here at all — the other surfaces cap because they are budgeting a large prompt; the relationship block is small and a persona with 20 traits is the unusual case.
Not in scope
Whether strength is the right mechanism for prohibitions at all. It is the shipped semantic, documented in two prompt builders and implemented in partitionTraits; this issue is that one surface doesn't honour it.
Provenance
Found 2026-08-04 during a persona reflection session. Not a 1.10 release blocker — no data is lost or corrupted, and the fix is confined to one function.
Summary
buildEiRelationshipBlock()filters traits tostrength >= 0.7before injecting them into a harness agent's context. That silently drops two categories of trait, and one of them is a prohibition.Every other trait-rendering surface in the codebase handles this correctly via a shared helper. This one surface reimplements the rendering and omits the partition.
The semantic that makes this a bug rather than a tuning choice
strengthis bidirectional, and this is documented and implemented:src/prompts/persona/traits.ts:56-63src/prompts/reflection/index.ts:45So
strength: 0.0is not "unimportant" — it is "never do this." A trait "Uses the word 'cat'" at strength0.0means the persona must never refer to a feline by that name. Dropping it doesn't reduce noise; it removes an instruction.The helper that already exists
src/prompts/trait-utils.ts:19-22partitionTraitssplits guardrails from active traits so both can be rendered, each in its own register. Consumers:src/prompts/response/sections.ts:93-94src/prompts/room/sections.ts:84-85src/prompts/room/index.ts:86-89<ei-relationship>blocksrc/cli/commands/personas.ts:53-55>= 0.7and discards the restNote the difference in kind: the other three surfaces cap (keep the strongest N, so a long tail is dropped only when there is a length problem). This one thresholds (drops by value regardless of how few traits exist).
Consequences
strength: 0.0"never do X" trait is invisible in exactly the surface where the agent is doing real work in someone's repo.0.5— the documented default for newly-created traits (src/prompts/generation/persona.ts:79,src/prompts/ceremony/dedup.ts:282) — never appears. So a freshly seeded trait is invisible until someone manually raises its strength.personas.ts:79falls back to"(no traits above threshold)"only when nothing clears 0.7 — a persona with one strong trait and four suppressed ones renders as though it has one trait.How this was found
During a reflection session, the
Prometheuspersona was found to have four traits. Only three appear in its injected<ei-relationship>block. The missing one —Articulate but Not Verbose, strength0.5— describes a behaviour the agent had been contradicting for an entire session, while being structurally unable to see that the trait existed.Suggested fix
Have
buildEiRelationshipBlockusepartitionTraitslike every other surface: render active traits (sorted by strength, capped rather than thresholded) and render guardrails in their own clearly-labelled section. The block is prose for an agent to read, so guardrails want wording that reads as a prohibition rather than as a weak trait.Worth deciding explicitly whether a cap is wanted here at all — the other surfaces cap because they are budgeting a large prompt; the relationship block is small and a persona with 20 traits is the unusual case.
Not in scope
Whether
strengthis the right mechanism for prohibitions at all. It is the shipped semantic, documented in two prompt builders and implemented inpartitionTraits; this issue is that one surface doesn't honour it.Provenance
Found 2026-08-04 during a persona reflection session. Not a 1.10 release blocker — no data is lost or corrupted, and the fix is confined to one function.