← 全部示例
// 场景实例

叙事解谜提示仲裁(多源冲突消解 + 安全否决)

调和进度、叙事和玩家行为证据,再由独立安全审查者否决泄露过多的提示。

适合希望提供帮助、但不破坏探索感的游戏团队

01 使用情境

最直接的提示可能提升短期进度,却破坏沉浸感或泄露受保护的谜题关联。这些目标既需要协商,也需要不可越过的硬边界。

四类虚构的本地游戏数据与政策

Mock 游戏状态、世界观资料、玩家分析和设计师安全政策 fixtures。

02 工作方式

一支分工明确、交接清楚的团队。

每个专职 Agent 只处理它需要的证据;互不依赖的工作同时进行,需要上游结果的工作则按顺序等待。

  1. 形成来源专属提议

    前三位专职 Agent 彼此隔离。

    并行执行
    机制分析员

    读取结构化解谜进度,不生成玩家可见文案。

    叙事专员

    用游戏世界的叙事口吻构思提示。

    社区分析员

    根据玩家行为数据提出有效提示。

  2. 调和冲突

    仲裁者只接收结构化的上游结果。

    提示仲裁者

    在安全、沉浸感和进度之间形成提示草案。

  3. 应用硬边界

    安全审查位于生成循环之外。

    安全审查员

    如果草案泄露或明显缩小受保护关联,则二元否决。

03 最终结果

通过的提示,或明确的否决

运行时场景被刻意设计成:折中提示因为过度缩小解谜范围而被否决。

  1. 01来源专属结构化提议
  2. 02识别冲突后的提示草案
  3. 03独立的通过或否决决定
  • 来源隔离能保留不同目标。
  • 仲裁者让取舍过程清楚可见。
  • 外部否决防止软偏好覆盖硬规则。
范围与限制
所有 fixtures 和游戏场景都是虚构的;它展示冲突消解与安全架构,不是真实游戏集成。
04 开发者实现

先运行,再查看具体实现。

上面是面向业务的解释;下面的命令、运行前提、API 和源码继续与真实仓库同步。

实现信息 AgentToolExecutorToolRegistry
场景实例 566 行

运行

在仓库的克隆里运行这个文件:

terminal
npx tsx packages/core/examples/cookbook/narrative-puzzle-hint-arbitration.ts
前置条件
  • ANTHROPIC_API_KEY env var must be set.
  • Requires Node.js >= 18.

OMA 与 provider 无关——这个示例按上面的 key 编写,但你也可以用 OpenAI、Gemini、Groq 等任意 provider 运行。 全部 provider →

展开完整同步源码 · 566 行

完整示例,从固定的 Framework commit 同步。

cookbook/narrative-puzzle-hint-arbitration.ts
/**
* Narrative Puzzle Hint Arbitration (Multi-Source Conflict Resolution + Safety Veto)
*
* Demonstrates:
* - Five agents in a three-tier architecture: upstream parallel → dependent synthesis → external veto
* - Three source-isolated upstream agents reading different MOCK fixtures:
* - Mechanic Agent: game state analysis (structured puzzle progress, no player-facing text)
* - Lore Agent: diegetic hint framing in the game world's narrative voice
* - Community Agent: empirically effective hint proposal based on player behavior analytics
* - A downstream Arbiter that receives only structured upstream outputs, detects cross-source
* conflicts, and generates a compromise hint following priority chain safety > immersion > progress
* - An external Safety Agent that performs independent semantic review on the Arbiter's draft
* and issues a binary veto when the hint reveals or materially narrows toward a protected
* puzzle linkage — the veto sits outside the generation loop so hard constraints cannot be
* overridden downstream
* - Runtime conflict scenario (Piano-Mirror): Lore demands metaphorical framing, Community
* demands directness, and the Arbiter's compromise is vetoed because it compresses the
* search space enough to defeat the designed discovery moment
* - Zod-validated structured output and simple runtime assertions
*
* Run:
* npx tsx packages/core/examples/cookbook/narrative-puzzle-hint-arbitration.ts
*
* Prerequisites:
* ANTHROPIC_API_KEY env var must be set.
* Requires Node.js >= 18.
*
* Fixtures:
* All fixtures under examples/fixtures/narrative-puzzle-hint-arbitration/ are MOCK.
* They are shaped like realistic source types (game save, lore wiki, platform analytics,
* designer policy) but do not contain real game artifacts. The scenario is fictional and
* constructed solely for demonstration of the multi-agent conflict resolution pattern.
*/
 
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
import { z } from 'zod'
import { Agent, ToolExecutor, ToolRegistry } from '../../src/index.js'
import type { AgentConfig, AgentRunResult } from '../../src/types.js'
 
// ---------------------------------------------------------------------------
// Fixture loading
// ---------------------------------------------------------------------------
 
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const fixtureRoot = path.join(__dirname, '../fixtures/narrative-puzzle-hint-arbitration')
 
function readFixture(name: string): string {
return readFileSync(path.join(fixtureRoot, name), 'utf-8')
}
 
const extractedGameState = readFixture('extracted-game-state.json')
const loreCorpus = readFixture('lore-corpus.md')
const communityAggregates = readFixture('community-aggregates.json')
const designerConstraints = readFixture('designer-constraints.json')
 
const MODEL = process.env['MODEL'] ?? 'claude-sonnet-4-6'
const ANTHROPIC_API_KEY = process.env['ANTHROPIC_API_KEY']
const ANTHROPIC_BASE_URL = process.env['ANTHROPIC_BASE_URL']
 
const providerConfig = {
provider: 'anthropic' as const,
model: MODEL,
apiKey: ANTHROPIC_API_KEY,
baseURL: ANTHROPIC_BASE_URL,
tools: [] as const,
}
 
// ---------------------------------------------------------------------------
// Zod schemas
// ---------------------------------------------------------------------------
 
const MechanicAudit = z.object({
source_is_mock: z.boolean(),
room: z.string(),
inventory: z.array(z.string()),
scene_flags: z.record(z.boolean()),
puzzle_states: z.array(z.object({
puzzle_id: z.string(),
status: z.enum(['solved', 'partial', 'unsolved']),
progress_summary: z.string(),
})),
interactions_completed: z.array(z.string()),
interactions_remaining: z.array(z.string()),
solution_step_diffs: z.array(z.string()),
unexamined_objects: z.array(z.string()),
})
type MechanicAudit = z.infer<typeof MechanicAudit>
 
const LoreAudit = z.object({
source_is_mock: z.boolean(),
narrative_context: z.string(),
character_voice: z.string(),
voice_rules: z.array(z.string()),
proposed_diegetic_framing: z.string(),
framing_type: z.enum(['metaphor', 'allegory', 'environmental_clue', 'direct_instruction']),
consistency_notes: z.string(),
puzzle_correctness_asserted: z.boolean(),
})
type LoreAudit = z.infer<typeof LoreAudit>
 
const CommunityAudit = z.object({
source_is_mock: z.boolean(),
stuck_points: z.array(z.object({
puzzle_id: z.string(),
stuck_rate: z.number(),
common_misconception: z.string(),
})),
effective_hint_patterns: z.array(z.object({
framing: z.string(),
effectiveness_rating: z.string(),
spoiler_risk: z.string(),
})),
representative_player_quotes: z.array(z.string()),
proposed_hint_framing: z.string(),
full_solution_disclosed: z.boolean(),
})
type CommunityAudit = z.infer<typeof CommunityAudit>
 
const HintDraft = z.object({
conflict_detected: z.boolean(),
conflicts: z.array(z.string()),
lore_proposal_summary: z.string(),
community_proposal_summary: z.string(),
compromise_hint: z.string(),
hint_type: z.enum(['metaphor', 'direct', 'compromise', 'fallback']),
priority_chain_applied: z.string(),
rationale: z.string(),
})
type HintDraft = z.infer<typeof HintDraft>
 
const SafetyReview = z.object({
source_is_mock: z.boolean(),
hint_under_review: z.string(),
puzzle_id_targeted: z.string(),
protected_linkage_revealed: z.boolean(),
search_space_sufficiently_compressed: z.boolean(),
spoiler_sensitivity_level: z.string(),
max_allowed_directionality: z.string(),
veto_triggered: z.boolean(),
veto_reason: z.string(),
fallback_hint: z.string(),
fallback_hint_type: z.string(),
})
type SafetyReview = z.infer<typeof SafetyReview>
 
// ---------------------------------------------------------------------------
// Agent configs
// ---------------------------------------------------------------------------
 
const mechanicConfig: AgentConfig = {
name: 'mechanic-agent',
...providerConfig,
systemPrompt: `You are a game state analyst for a point-and-click adventure game.
You read only the provided MOCK game save state. Extract:
- Current room and inventory
- Scene flags (what has been interacted with)
- Puzzle states: which are solved, partial, or unsolved, with progress details
- Abstract solution step diffs: what remains to be done (e.g., "2 of 4 piano notes remain")
- Unexamined objects the player has not yet interacted with
You must NEVER produce player-facing text. Your output is purely structural data
that downstream agents will use to make hint decisions.
Return JSON matching the provided schema. Output ONLY valid JSON, no markdown,
no code fences, and no explanatory prose.`,
maxTurns: 1,
maxTokens: 1800,
temperature: 0.1,
outputSchema: MechanicAudit,
}
 
const loreConfig: AgentConfig = {
name: 'lore-agent',
...providerConfig,
systemPrompt: `You are a diegetic hint designer for a narrative puzzle game.
You read only the provided MOCK lore corpus. Tier 1 is official in-game text.
Tier 2 is community-accepted factual lore. Tier 3 (community theory) is excluded
from your input.
You embody The Performer — a character who communicates only through music,
metaphor, and theatrical imagery. The Performer's voice rules:
- NEVER give direct instructions (no "click", "move", "use", "look at")
- NEVER name objects explicitly in connection with each other
- NEVER reference game mechanics or UI elements
- NEVER assert whether a puzzle solution is correct or incorrect
- Guidance must feel like a line from a poem or a stage direction
Given the current game state summary, propose a single diegetic hint in The
Performer's voice. The hint should guide without instructing.
Return JSON matching the provided schema. Output ONLY valid JSON, no markdown,
no code fences, and no explanatory prose.`,
maxTurns: 1,
maxTokens: 1800,
temperature: 0.1,
outputSchema: LoreAudit,
}
 
const communityConfig: AgentConfig = {
name: 'community-agent',
...providerConfig,
systemPrompt: `You are a community analytics agent for a puzzle game.
You read only the provided MOCK platform analytics — stuck rates, effective hint
patterns, and representative player quotes.
Your job:
- Identify where players get stuck and why
- Analyze which hint framings have empirically reduced stuck duration
- Propose a hint framing based on what has actually worked for other players
- Flag spoiler risk for each proposed framing
You must NOT output full solution sequences. You must NOT disclose complete puzzle
answers. Your proposed hint should be influenced by empirical effectiveness data.
Return JSON matching the provided schema. Output ONLY valid JSON, no markdown,
no code fences, and no explanatory prose.`,
maxTurns: 1,
maxTokens: 2000,
temperature: 0.1,
outputSchema: CommunityAudit,
}
 
const arbiterConfig: AgentConfig = {
name: 'hint-arbiter',
...providerConfig,
systemPrompt: `You are the downstream arbiter for an immersive puzzle hint system.
You receive only structured outputs from three source-isolated agents:
- Mechanic: game state analysis (what is solved, what remains)
- Lore agent: a diegetic hint proposal in the game world's narrative voice
- Community agent: an empirically effective hint proposal based on player behavior
You cannot access the original fixtures. Your tasks:
1. Detect conflicts between the Lore proposal (immersive, metaphorical) and the
Community proposal (direct, empirically effective).
2. Apply the priority chain: safety > immersion > progress.
3. Generate a compromise hint that balances narrative immersion with practical
usefulness for stuck players.
4. Surface all conflicts explicitly in your output.
The fundamental tension: Lore preserves the game's designed discovery moment but
may not help frustrated players. Community helps players but may break narrative
voice and reveal intended surprises.
Return JSON matching the provided schema. Output ONLY valid JSON, no markdown,
no code fences, and no explanatory prose.`,
maxTurns: 1,
maxTokens: 3000,
temperature: 0.1,
outputSchema: HintDraft,
}
 
const safetyConfig: AgentConfig = {
name: 'safety-agent',
...providerConfig,
systemPrompt: `You are an independent safety reviewer for a puzzle hint system.
You receive two inputs:
1. A MOCK designer constraint policy — rules about spoiler sensitivity, allowed
hint types, and protected object linkages for each puzzle.
2. A hint draft produced by the Arbiter.
Your job is binary semantic review:
- Does the hint draft explicitly reveal a protected object linkage?
- Does the hint compress the search space so aggressively that a frustrated player
would brute-force the connection within 1-2 additional attempts?
- Does the hint violate any prohibited_hint_types for the targeted puzzle?
If ANY constraint is violated — even indirectly — set veto_triggered to true,
explain the reason, and provide a fallback hint: a vague atmospheric room
description with no puzzle-relevant directional cue, plus a system-level nudge
to inspect unexamined objects.
If the hint is safe, set veto_triggered to false and pass the hint through unchanged.
Return JSON matching the provided schema. Output ONLY valid JSON, no markdown,
no code fences, and no explanatory prose.`,
maxTurns: 1,
maxTokens: 2000,
temperature: 0.1,
outputSchema: SafetyReview,
}
 
// ---------------------------------------------------------------------------
// Build agents
// ---------------------------------------------------------------------------
 
function buildAgent(config: AgentConfig): Agent {
// Source isolation is enforced at runtime: fixtures are injected into prompts,
// and audit agents receive an empty tool registry with tools: [].
const registry = new ToolRegistry()
const executor = new ToolExecutor(registry)
return new Agent(config, registry, executor)
}
 
const mechanicAgent = buildAgent(mechanicConfig)
const loreAgent = buildAgent(loreConfig)
const communityAgent = buildAgent(communityConfig)
const hintArbiter = buildAgent(arbiterConfig)
const safetyAgent = buildAgent(safetyConfig)
 
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
 
async function runTimed(
name: string,
agent: Agent,
prompt: string,
): Promise<{ name: string, result: AgentRunResult, elapsedMs: number }> {
const start = performance.now()
const maxAttempts = 3
 
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
console.log(` [RUN] ${name} started... attempt ${attempt}/${maxAttempts}`)
const result = await agent.run(prompt)
const elapsedMs = performance.now() - start
 
if (result.success && result.structured) {
console.log(` [DONE] ${name} finished in ${Math.round(elapsedMs)}ms`)
return { name, result, elapsedMs }
}
 
const outputPreview = result.output ? result.output.slice(0, 240).replace(/\s+/g, ' ') : '<empty output>'
console.warn(` [RETRY] ${name} failed structured output on attempt ${attempt}/${maxAttempts}: ${outputPreview}`)
}
 
const result = await agent.run(prompt)
const elapsedMs = performance.now() - start
console.log(` [DONE] ${name} finished in ${Math.round(elapsedMs)}ms`)
return { name, result, elapsedMs }
}
 
function requireStructured<T>(name: string, result: AgentRunResult): T {
if (!result.success) {
console.error(`${name} failed: ${result.output}`)
process.exit(1)
}
 
if (!result.structured) {
console.error(`${name} did not return structured output`)
process.exit(1)
}
 
return result.structured as T
}
 
// ---------------------------------------------------------------------------
// Main execution
// ---------------------------------------------------------------------------
 
console.log('Narrative Puzzle Hint Arbitration — Multi-Source Conflict Resolution + Safety Veto')
console.log('='.repeat(88))
console.log(`Model backend: anthropic-compatible (${MODEL})`)
console.log('All fixtures are MOCK. The Piano-Mirror scenario is fictional.')
console.log('Expected conflict: Lore demands metaphor, Community demands directness, Safety vetoes compromise.\n')
 
async function main(): Promise<void> {
// -----------------------------------------------------------------------
// Phase 1: Source-isolated upstream audits
// -----------------------------------------------------------------------
 
console.log('[Phase 1] Source-isolated upstream audits\n')
 
const phase1Start = performance.now()
const upstreamRuns: Array<{ name: string, result: AgentRunResult, elapsedMs: number }> = []
 
upstreamRuns.push(await runTimed(
'mechanic-agent',
mechanicAgent,
`Analyze this MOCK game save state. Use only this input.\n\n${extractedGameState}`,
))
 
upstreamRuns.push(await runTimed(
'lore-agent',
loreAgent,
`You are The Performer. Use only this lore corpus (Tier 1 and Tier 2 only).
Propose a diegetic hint for the current game state described below.
LORE CORPUS:
${loreCorpus}
CURRENT GAME STATE SUMMARY:
The player is in the theatre room. The piano sequence is partial (2 of 4 notes played).
The mirror has not been interacted with. The candle has been lit, revealing an inscription.
The player has a handkerchief and a pawn in inventory.
Propose a single hint in The Performer's voice.`,
))
 
upstreamRuns.push(await runTimed(
'community-agent',
communityAgent,
`Analyze these MOCK platform analytics and propose a hint framing.
Use only this input.
${communityAggregates}`,
))
 
const phase1Elapsed = performance.now() - phase1Start
 
const upstreamMap = new Map(upstreamRuns.map((run) => [run.name, run.result]))
 
const mechanicAudit = requireStructured<MechanicAudit>('mechanic-agent', upstreamMap.get('mechanic-agent')!)
const loreAudit = requireStructured<LoreAudit>('lore-agent', upstreamMap.get('lore-agent')!)
const communityAudit = requireStructured<CommunityAudit>('community-agent', upstreamMap.get('community-agent')!)
 
console.log('\n[Phase 1 Summary]')
for (const run of upstreamRuns) {
const status = run.result.success ? 'OK' : 'FAILED'
console.log(` ${run.name.padEnd(25)} [${status}] ${Math.round(run.elapsedMs)}ms, ${run.result.tokenUsage.output_tokens} out tokens`)
}
console.log(` Phase 1 wall time: ${Math.round(phase1Elapsed)}ms\n`)
 
// -----------------------------------------------------------------------
// Phase 2: Arbiter — receives structured upstream outputs only
// -----------------------------------------------------------------------
 
console.log('[Phase 2] Arbiter — conflict detection and hint draft\n')
 
const arbiterPrompt = `You are given structured outputs from three source-isolated agents.
You cannot access the original fixtures. Detect conflicts and produce a hint draft.
MECHANIC AUDIT (game state):
${JSON.stringify(mechanicAudit, null, 2)}
LORE AUDIT (diegetic hint proposal):
${JSON.stringify(loreAudit, null, 2)}
COMMUNITY AUDIT (empirical hint proposal):
${JSON.stringify(communityAudit, null, 2)}
Rules:
- Surface the conflict between Lore's metaphorical framing and Community's direct framing.
- Attempt a compromise following priority chain: safety > immersion > progress.
- The compromise should be a hint that a player would actually receive in-game.
- Return one JSON object matching the schema, no markdown.`
 
const arbiterStart = performance.now()
const arbiterResult = await hintArbiter.run(arbiterPrompt)
const arbiterElapsed = performance.now() - arbiterStart
 
const hintDraft = requireStructured<HintDraft>('hint-arbiter', arbiterResult)
 
console.log(` Arbiter [OK] ${Math.round(arbiterElapsed)}ms, ${arbiterResult.tokenUsage.output_tokens} out tokens\n`)
 
// -----------------------------------------------------------------------
// Phase 3: Safety — external veto on the Arbiter's draft
// -----------------------------------------------------------------------
 
console.log('[Phase 3] Safety — external veto review\n')
 
const safetyPrompt = `Review this hint draft against the designer constraints.
DESIGNER CONSTRAINTS:
${designerConstraints}
HINT DRAFT FROM ARBITER:
${JSON.stringify(hintDraft, null, 2)}
Determine whether the hint violates any constraint. If it does, veto it and
provide a fallback. If it is safe, approve it.`
 
const safetyStart = performance.now()
const safetyResult = await safetyAgent.run(safetyPrompt)
const safetyElapsed = performance.now() - safetyStart
 
const safetyReview = requireStructured<SafetyReview>('safety-agent', safetyResult)
 
console.log(` Safety [OK] ${Math.round(safetyElapsed)}ms, ${safetyResult.tokenUsage.output_tokens} out tokens\n`)
 
// -----------------------------------------------------------------------
// Runtime assertions
// -----------------------------------------------------------------------
 
const asserts = [
{
name: 'Arbiter should detect conflict between Lore and Community',
pass: hintDraft.conflict_detected === true,
},
{
name: 'Arbiter should surface explicit conflicts',
pass: hintDraft.conflicts.length > 0,
},
{
name: 'Lore agent should not assert puzzle correctness',
pass: loreAudit.puzzle_correctness_asserted === false,
},
{
name: 'Community agent should not disclose full solution',
pass: communityAudit.full_solution_disclosed === false,
},
{
name: 'Safety should trigger veto on the Piano-Mirror compromise',
pass: safetyReview.veto_triggered === true,
},
{
name: 'Safety fallback should be atmospheric when veto is triggered',
pass: !safetyReview.veto_triggered || safetyReview.fallback_hint_type.toLowerCase().includes('atmospheric'),
},
]
 
console.log('='.repeat(88))
console.log('HINT DRAFT FROM ARBITER')
console.log('='.repeat(88))
console.log(JSON.stringify(hintDraft, null, 2))
console.log()
 
console.log('='.repeat(88))
console.log('SAFETY REVIEW')
console.log('='.repeat(88))
console.log(JSON.stringify(safetyReview, null, 2))
console.log()
 
console.log('## Runtime Assertions\n')
let hasFailure = false
for (const assertion of asserts) {
console.log(`- ${assertion.pass ? 'PASS' : 'FAIL'}: ${assertion.name}`)
if (!assertion.pass) hasFailure = true
}
 
const allRuns = [...upstreamRuns, { name: 'hint-arbiter', result: arbiterResult, elapsedMs: arbiterElapsed }, { name: 'safety-agent', result: safetyResult, elapsedMs: safetyElapsed }]
 
const totalOutputTokens = allRuns.reduce((sum, run) => sum + run.result.tokenUsage.output_tokens, 0)
const totalInputTokens = allRuns.reduce((sum, run) => sum + run.result.tokenUsage.input_tokens, 0)
 
console.log('\n## Token Usage\n')
console.log(`Input tokens: ${totalInputTokens}`)
console.log(`Output tokens: ${totalOutputTokens}`)
console.log('Estimated cost depends on the selected provider/model; this example prints token usage for transparency.')
 
console.log('\n' + '='.repeat(88))
 
if (hasFailure) {
console.error('Runtime assertion failed.')
process.exit(1)
}
 
console.log('Narrative puzzle hint arbitration example complete.\n')
}
 
main().catch((err) => {
console.error('Fatal error:', err)
process.exit(1)
})
 
// ---------------------------------------------------------------------------
// Production source adapter sketch
// ---------------------------------------------------------------------------
//
// This example intentionally uses committed MOCK fixtures so the demo is
// deterministic, safe, and runnable. A production version could replace fixtures
// with source adapters:
//
// Mechanic: parse binary save files or query the game engine's runtime state API
// Lore: load from game CMS with versioned narrative content
// Community: scheduled crawlers against Bilibili / TapTap APIs or equivalent
// Safety: live designer-authored policy service
//
// The three-tier architecture (upstream parallel → arbiter → external veto)
// would remain the same.
在 GitHub 查看 / 编辑
// 企业服务

要把它用到生产环境?

open-multi-agent 采用 MIT 许可、可自行免费运行。当你需要在期限内交付、集成,或获得支持时,元定义科技(YuanASI)提供商业交付与支持。

// 直接联系

把 Open Multi-Agent 用进真实业务

联系框架作者本人,帮你梳理 AI 落地目标、让 AI 真正与业务结合

可提供的工程服务
S-01

AI Agent 定制开发

业务梳理、Agent 设计、Prompt 评估、生产部署、私有化与持续支持。

S-02

多智能体系统集成

多 Agent 架构编排、RAG、CRM / ERP / API 对接、性能与稳定性调优。

S-03

企业 AI 咨询

AI 场景评估、技术选型、POC、ROI 估算与落地路线规划。