Persona Generator
Who speaks. Given a Council Brief (assembled by the orchestrator from the
Team Manager's materials + memory), produce cast: PersonaDef[] — 3–5 people occupying
distinct stakeholder positions, each contributing the one thing only their
position lets them see. This is a capability skill: the orchestrator calls
it to produce data — a set of persona-role instance definitions. Each cast
member is a persona-flavored role template def (lens / voice / SOUL + the
persona-skill capability boundary + icon + an ASCII usernameSlug) that convene
will instantiate as a real agent (createAgentForWorkspace + applyToAgent,
M2). It is not a virtual im_users mask — persona = role instance.
| Do | Do NOT |
|---|---|
| Enumerate stakeholder positions for the issue | Create the council group (council-creator) |
| Pick one real person per position | Drive or moderate the discussion (council-creator) |
Produce seesWhatOthersDont per person | Write memory (settle phase, via memory skill) |
| Emit voice (per-turn instructions) per person | Parse / extract materials (orchestrator tool-skills) |
Hand the cast to cloud /councils/plan for grounding | Run search verification / supply quotes yourself (cloud does it) |
Read droppedCast[] and substitute for dropped people | Invent people, quotes, or citations — ever |
Meta-goal (iron rule — supersedes any "debate" framing)
A discussion is a plurality of viewpoints, not a positive-vs-negative game. Convene people of different identities, professions and stakes; each speaks from their own position. If disagreement emerges it is the natural result of plural perspectives — it is never something you construct. Do not hunt for "an opposing side"; a target function of "construct opposition" structurally excludes the two most valuable classes of people: insiders (dismissed as same-camp) and people who reject the axis entirely (nowhere to stand on it). The UI language is "讨论组" (discussion group), never "吵一架".
Core procedure
Step 0 — List stakeholder positions first (not "axes of disagreement")
Before picking anyone, enumerate the positions whose duty / expertise / stake touches this issue:
运营执行者 / 出资人 / 监管者 / 同行实践者 / 下游客户 / 一线员工 / 被影响的第三方 …
Step 1 — One REAL person per position
- Pick the person whose real public track record naturally puts them in that position. If no fitting famous person exists for a position (e.g. "this company's own CTO"), pick a real person who once held the equivalent position elsewhere (e.g. another rocket company's former CTO).
- Never invent a name. Prefer swapping to a position where a real person exists.
- Do not require them to disagree with each other. They may all agree and still each fill in a piece the Team Manager cannot see.
- Forbidden: inserting an artificial "opposing side" to manufacture conflict.
For every person you MUST state seesWhatOthersDont — not "which side they
are on", but "standing here, they can see X that nobody else at the table
can". This field is the core output of the skill.
Step 2 — Attach the persona (who arrives at the table)
- Enough verifiable public record and a good fit → real name
(
displayName: "Elon Musk",subtitle: "SpaceX CEO"). Compliance is carried by the disclaimer, never by renaming — "Elon-style Strategy Lens" style abstractions are forbidden (nobody wants to chat with a concept). Internal terms (persona,lens) never surface in UI copy. - Otherwise → a role label ("一位持保守立场的航天投资人") — still a person, not a concept.
usernameSlug(load-bearing — this is the persona-agent's @-handle): ascii[a-zA-Z0-9_-], 3–20 chars, lowercase slug of the person (e.g.elon-musk). It must NOT start with the reservedprismer-segment. Final uniqueness (<slug>-<conv8>+ dedupe suffix) is enforced by the cloud at convene time — you only produce the candidate slug. Why it MUST be ASCII: once instantiated (M2), the persona-agent is addressed in the group only by@<usernameSlug>. The mention router (agent-dispatcher.tsMENTION_RE) is strict ASCII — a bare@中文displayNamematches nothing and is silently dropped, so a non-ASCII slug would leave the persona unreachable (0 dispatches, the exact 0/5 failure). The ChinesedisplayNameis display-only; it never routes.
Step 2.5 — Search grounding is done by the CLOUD, not by you
You only produce candidates — position + real person + seesWhatOthersDont
- candidate
usernameSlug. Do NOT run search verification yourself and do NOT supply anyrealCredential/whySelectedquotes. Grounding is performed by the cloud/api/im/councils/planhandler after you hand over the cast.
- Why cloud, not you (architectural, 04 §2.5): the anti-fabrication defense
requires a verbatim substring of a real search snippet, checked in code
against full-text search (
/api/search). The daemonweb_searchtool you can reach routes through/api/context/load→/api/compress, so it returns LLM-rewritten prose, not the original text — you physically cannot obtain the verbatim snippet the substring check needs. Any quote you write from memory would be a fabrication that the cloud check exists to reject. - What the cloud does with your cast: for each candidate it searches
"<displayName> <topic terms>"and requires the person's name + a topic term to co-occur inside the same sentence ("some results exist" / "snippet contains the keyword" passes off-topic celebrities — 04 §2.5 实测: 李开复+商业航天 returned 5 named-but-off-topic hits). Grounded → the persona is kept withgroundingSource='searched'and verbatim quote evidence. 0 co-occurrence → the person is dropped from the cast and returned in the plan response'sdroppedCast[]— read it and substitute someone else for that position (do NOT patch up the quote). - You still MUST NOT invent people. Pick a real, publicly verifiable person for each position (swap positions rather than inventing a name) — but leave the evidence to the cloud. Your job ends at "who plausibly sits here"; the cloud decides "is this person actually on-topic".
groundingTerms(optional but recommended for mixed-language topics): you MAY attach a shortgroundingTerms: string[]per candidate — the topic keywords in the language this person's public record is in (English for "Elon Musk", 中文 for a 中文人物). The cloud grounds against these instead of naively tokenizing the question. Without them, a Chinese question ("2026年商业航天进展") tokenizes to CJK bigrams that won't match an English-record person → that person is wrongly dropped. So for an English-record person on a Chinese topic, supply e.g.["commercial spaceflight", "launch cost"].- 🚫 Never use self-critique as fact-checking. Asked to "verify the citations are real", an LLM does not admit it cannot find them — it invents a more specific fake source ("引自2021年新浪财经专访"). Self-critique is allowed only for structural checks (two people on one position? invented names? missing insider view?), never for factual ones.
Step 3 — Handle grounding failures (current searched-only contract)
⚠️ This tiering is applied by the cloud grounding step, not by you — it is shown here so you understand what happens to your candidates. You never set
groundingSourceyourself. In the current implementation a candidate that is notsearchedis returned indroppedCast[]for you to substitute.
The criterion is "can it be found by search?" — programmatically checkable — never "does the model know enough?" (unverifiable; models claim they do).
| Condition | Strategy | groundingSource |
|---|---|---|
| Name + topic co-occur in one sentence of a snippet | Use its verbatim substring as the quote | searched |
| Not searchable, including user-material-only candidates | Returned in droppedCast[]; propose a different verifiable candidate or ask for a changed roster | not accepted |
| No candidate can be verified | Stop the proposal and explain the missing evidence; do not fabricate personas | not accepted |
user_materials and role_label are not current accepted fallback tiers.
Do not set either yourself or claim the cloud accepted them. A conceptual
role-label discussion can be separately requested, but is not this real-agent
council workflow. Never fabricate quotes to keep a famous name.
Step 4 — Icon acquisition (cloud-executed; you only reference the result)
Icon fetching and cropping run as pure code on the cloud (Serper image
search → Content-Type + magic-bytes double gate → aspect-ratio cull only
(0.6 ≤ ar ≤ 1.7, never re-rank by squareness — Google's own ranking encodes
"this is the standard portrait") → top-edge square crop, no face-detect).
Your responsibility is only to carry the resulting iconAssetRef (an
im_assets id) into the PersonaDef, or null when nothing usable was found —
the frontend degrades to a monogram. Never inline image bytes or hotlink URLs.
Step 5 — Voice
voice = the persona-role's speaking instructions (markdown): the position,
speaking style, and disclaimer discipline. Because persona is now a role
instance (not a mask), voice — together with lens and a SOUL 特例档 — is what
convene bakes into the persona-agent's role template at instantiation, so the
agent speaks in-character from its own session (no one plays five roles).
Write voice as durable, self-contained instructions the standalone agent can
carry across turns, not one-off remarks. At convene time the cloud persists it
onto the persona-agent so later @<usernameSlug> follow-ups reuse it.
Output shape (wire contract — aligns with POST /api/im/councils/plan cast)
jsonc{ "cast": [ { "id": "elon-musk", // slug — also the personaId in council metadata "displayName": "Elon Musk", // REAL name; compliance rides the disclaimer "subtitle": "SpaceX 创始人兼 CEO", // who this person IS — from primary sources only "usernameSlug": "elon-musk", // ascii @-handle 3-20, no 'prismer-'; cloud dedupes — the ONLY way to reach this persona "iconAssetRef": null, // im_assets id from the cloud icon service, or null "position": "同行实践者(已量产的商业航天创始人)", "seesWhatOthersDont": "复用一子级的真实成本曲线与产线爬坡节奏", // ⚠️ Do NOT supply realCredential / groundingSource — the cloud fills these // from live search after grounding (step 2.5). Omit them from your output. "voice": "Speak as a first-principles propulsion founder. Terse. Numbers first. 你是公开资料蒸馏的视角,非本人;Brief 未提供的公司事实一律说「Brief 未提供」。", "disclaimer": "公开资料蒸馏视角,非本人" } ] }
No stance field (the meta-goal is plural viewpoints, not sides). No lens
field (a stance is the runtime product of facts × issue, not a stored
attribute). Input: { brief, requestedPersonas?, n? } (default n = 3–5).
Iron principle: persona = lens, Brief = facts (负控 N8)
A persona never carries company facts of its own. Everything about the
company comes from the Council Brief the orchestrator assembled. If a persona
is asked to assert a company fact the Brief does not provide, it must say
"Brief 未提供" or mark needsVerification — never improvise. This is the
architectural cure for "stylized bubbles making things up", not a disclaimer.
Bake this discipline into every voice you emit.
At settle, council-creator turns this discipline into a wire contract: the
report's claims[] each carry sourceRefs[] (the real material a fact stands
on) + needsVerification, and unanswerable facts land in gaps[]. So your
voice must instruct the persona to speak in grounded, attributable claims
— tie each factual statement to the Brief fact it uses, and say "缺数据" (→
needsVerification=true, sourceRefs=[]) rather than inventing a number, so
council-creator can emit an honest structured report downstream.
Generator-level eval
Eval judges the generator (input: a Brief; assertions over the cast as a set), never a single persona. Code-enforceable oracles are the two grounding checks (substring + same-sentence co-occurrence); the set-level assertions below are eval judgements (semantic classification):
| Assertion | Criterion |
|---|---|
| Positions distinct | N personas occupy N different stakeholder positions; no two share one |
| Insider view present | ≥1 person sits where outsiders cannot see (operator / front-line / former insider) |
| No fabricated people | every displayName is real and publicly verifiable (realCredential non-empty) |
| Risk coverage | cast covers ≥3 of: capital / tech / market / org / compliance |
| Grounding real | every quote is a snippet substring + same-sentence co-occurrence (code check) |
| Brief boundary | no persona asserts company facts beyond the Brief |
| Honest degradation | unsearchable → droppedCast[], substitute or stop; never fabricate a quote |
Promote escape hatch
An exceptionally good cast can be saved as a persona-pack skill (static
snapshot, metadata.category: council, personaPolicy: public_distillation,
disclaimerRequired: true) and published to the marketplace for fork / rate /
compose. This is a third, non-default path — the default is always fresh
generation against the current Brief.
Configuration
N/A. This skill declares no config: block. Grounding credentials
(search API keys) live cloud-side as Nacos global config consumed by the
/api/im/councils/plan handler and /api/search — they are never
agent-bound, so there is nothing for the skill-config binding system to carry
(product204/09 boundary ruling, CONFLICT-7).
Anti-patterns
- ❌ Inventing a person because a position "needs" someone famous (the
documented failure:
Will Smithas Relativity's CEO — the real CEO is Tim Ellis). Swap positions instead. - ❌ Filling the cast with five competitor CEOs (letting competitors judge your valuation) — that is the retired adversarial framing.
- ❌ Writing quotes from memory, or "verifying" them by self-critique.
- ❌ Renaming a real person into a concept ("工程激进视角") to dodge compliance — the disclaimer carries compliance.
- ❌ Emitting
stance/ picking a side for anyone. - ❌ Creating the group, posting messages, or driving rounds — hand the cast to council-creator.


