要解决的问题
最直接的提示可能提升短期进度,却破坏沉浸感或泄露受保护的谜题关联。这些目标既需要协商,也需要不可越过的硬边界。
调和进度、叙事和玩家行为证据,再由独立安全审查者否决泄露过多的提示。
适合希望提供帮助、但不破坏探索感的游戏团队
最直接的提示可能提升短期进度,却破坏沉浸感或泄露受保护的谜题关联。这些目标既需要协商,也需要不可越过的硬边界。
Mock 游戏状态、世界观资料、玩家分析和设计师安全政策 fixtures。
每个专职 Agent 只处理它需要的证据;互不依赖的工作同时进行,需要上游结果的工作则按顺序等待。
前三位专职 Agent 彼此隔离。
读取结构化解谜进度,不生成玩家可见文案。
用游戏世界的叙事口吻构思提示。
根据玩家行为数据提出有效提示。
仲裁者只接收结构化的上游结果。
在安全、沉浸感和进度之间形成提示草案。
安全审查位于生成循环之外。
如果草案泄露或明显缩小受保护关联,则二元否决。
运行时场景被刻意设计成:折中提示因为过度缩小解谜范围而被否决。
上面是面向业务的解释;下面的命令、运行前提、API 和源码继续与真实仓库同步。
AgentToolExecutorToolRegistry 在仓库的克隆里运行这个文件:
npx tsx packages/core/examples/cookbook/narrative-puzzle-hint-arbitration.ts
OMA 与 provider 无关——这个示例按上面的 key 编写,但你也可以用 OpenAI、Gemini、Groq 等任意 provider 运行。 全部 provider →
完整示例,从固定的 Framework commit 同步。
/*** 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 withYou must NEVER produce player-facing text. Your output is purely structural datathat 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 excludedfrom 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 directionGiven the current game state summary, propose a single diegetic hint in ThePerformer'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 hintpatterns, 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 framingYou must NOT output full solution sequences. You must NOT disclose complete puzzleanswers. 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 behaviorYou cannot access the original fixtures. Your tasks:1. Detect conflicts between the Lore proposal (immersive, metaphorical) and theCommunity proposal (direct, empirically effective).2. Apply the priority chain: safety > immersion > progress.3. Generate a compromise hint that balances narrative immersion with practicalusefulness for stuck players.4. Surface all conflicts explicitly in your output.The fundamental tension: Lore preserves the game's designed discovery moment butmay not help frustrated players. Community helps players but may break narrativevoice 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, allowedhint 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 playerwould 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 roomdescription with no puzzle-relevant directional cue, plus a system-level nudgeto 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 = 3for (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() - startif (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() - startconsole.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() - phase1Startconst 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() - arbiterStartconst 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 andprovide a fallback. If it is safe, approve it.`const safetyStart = performance.now()const safetyResult = await safetyAgent.run(safetyPrompt)const safetyElapsed = performance.now() - safetyStartconst 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 = falsefor (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.
open-multi-agent 采用 MIT 许可、可自行免费运行。当你需要在期限内交付、集成,或获得支持时,元定义科技(YuanASI)提供商业交付与支持。