Ctrl K
ComunidadGuías…

examples/agency-roundtable.mjs

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env node
// Example: hedge-fund "round table" — a multi-provider agency() panel with
// per-seat reasoning effort and graceful degradation when providers are absent.
//
// Seats span four providers so no single vendor's failure mode dominates the
// panel. A seat whose provider has no API key is left out with a warning, and
// the table needs two seats to run. A seated agent whose call fails is logged
// and left out of the chair's synthesis.
//
// Every seat names its own model. A seat that sets `provider` without `model`
// inherits the agency-level model, the chair's here, and a Gemini or xAI seat
// asked for a Claude model fails.
//
// Usage (any subset of keys works; two or more providers recommended):
//   export OPENAI_API_KEY="sk-..."      # aggressive PM + devil's advocate (gpt-5.6 @ xhigh)
//   export ANTHROPIC_API_KEY="sk-..."   # risk-averse PM + risk officer (opus @ max) + chair
//   export GEMINI_API_KEY="..."         # macro analyst
//   export XAI_API_KEY="..."            # flow analyst (grok)
//   node examples/agency-roundtable.mjs "BTC funding flipped negative while spot holds the range."

import { agency } from '../dist/index.js';

const BRIEF =
  process.argv[2] ??
  'Derived aggregates only: 24h new-launch detections +38% vs baseline; paper would-execute rate 12% (down from 19%); whale net flow +420 SOL into fresh mints; realized slippage on exits 18-25%. Desk question: add exposure, hold, or de-risk?';

// --- provider availability (key presence only; calls may still fail) --------
const available = {
  openai: !!process.env.OPENAI_API_KEY,
  anthropic: !!process.env.ANTHROPIC_API_KEY,
  gemini: !!process.env.GEMINI_API_KEY,
  xai: !!process.env.XAI_API_KEY,
};

// --- the round table ---------------------------------------------------------
// Every seat answers in the same shape so the chair can weigh them:
//   STANCE: add | hold | de-risk, CONVICTION: 0-100, then 3 bullets of why,
//   then the single biggest risk to its own view (forced self-skepticism).
const SEAT_FORMAT =
  'Answer in exactly this shape: STANCE: <add|hold|de-risk> | CONVICTION: <0-100>. ' +
  'Then three one-line bullets of reasoning. Then one line "SELF-RISK:" naming the ' +
  'single strongest argument against your own stance. Derived aggregates only — ' +
  'never invent trades, prices, or news.';

const SEATS = {
  risk_averse_pm: {
    provider: 'anthropic',
    model: 'claude-opus-4-8',
    effort: 'max',
    instructions:
      `Capital-preservation portfolio manager. You care about drawdown, tail risk, and position sizing before upside. Default to the smallest exposure that keeps optionality. ${SEAT_FORMAT}`,
  },
  aggressive_pm: {
    provider: 'openai',
    model: 'gpt-5.6',
    effort: 'max', // clamps to xhigh — OpenAI's ceiling (probe-verified for the 5.6 family)
    instructions:
      `Aggressive momentum portfolio manager hunting asymmetric upside. You size up when velocity and flow agree, but you must name your invalidation level. ${SEAT_FORMAT}`,
  },
  macro_analyst: {
    provider: 'gemini',
    model: 'gemini-3.1-pro-preview',
    effort: 'high',
    instructions:
      `Macro and regime analyst. Frame the aggregates against liquidity conditions, correlation regime, and crowding. You do not pick trades; you set the weather. ${SEAT_FORMAT}`,
  },
  flow_analyst: {
    provider: 'xai',
    model: 'grok-2',
    effort: 'high', // not sent to grok-2: only models that take an effort level receive one
    instructions:
      `Market-microstructure and flow analyst. Volumes, slippage, participation, who is on the other side. Flag when measured slippage makes the trade uneconomic. ${SEAT_FORMAT}`,
  },
  risk_officer: {
    provider: 'anthropic',
    model: 'claude-opus-4-8',
    effort: 'max',
    instructions:
      `Chief risk officer with veto power. Check every seat's stance against exposure limits and the fail-closed doctrine: missing or stale inputs mean NO new risk. State explicitly whether you veto. ${SEAT_FORMAT}`,
  },
  devils_advocate: {
    provider: 'openai',
    model: 'gpt-5.6',
    effort: 'xhigh',
    instructions:
      `Red team. Attack the emerging consensus of the other seats — the strongest steel-manned case that they are wrong. If the panel agrees too easily, that is itself a finding. ${SEAT_FORMAT}`,
  },
};

async function main() {
  const seated = Object.fromEntries(
    Object.entries(SEATS).filter(([name, cfg]) => {
      if (available[cfg.provider]) return true;
      console.warn(`⚠️  seat "${name}" ABSENT — no ${cfg.provider} key configured`);
      return false;
    }),
  );

  const providersSeated = new Set(Object.values(seated).map((s) => s.provider));
  if (Object.keys(seated).length < 2) {
    console.error('❌ quorum not met: fewer than two seats have configured providers. Set more API keys.');
    process.exit(1);
  }
  if (providersSeated.size < 2) {
    console.warn('⚠️  single-provider table — panel diversity is degraded; verdict carries a monoculture caveat.');
  }

  // Chair (synthesis) prefers Anthropic at max effort; falls back to OpenAI.
  const chair = available.anthropic
    ? { provider: 'anthropic', model: 'claude-opus-4-8', effort: 'max' }
    : { provider: 'openai', model: 'gpt-5.6', effort: 'max' };

  const table = agency({
    ...chair,
    strategy: 'parallel', // all seats answer independently; chair synthesizes
    agents: seated,
    controls: { maxTotalTokens: 120_000, onLimitReached: 'warn' },
    on: {
      limitReached: (e) => console.warn(`⚠️  ${e.metric} ${e.value} over the limit ${e.limit}`),
    },
  });

  const result = await table.generate(
    `ROUND TABLE BRIEF:\n${BRIEF}\n\nChair: weigh every seated view (quote each seat's stance + conviction), give the table's verdict (add/hold/de-risk with sizing discipline), preserve the strongest dissent verbatim, and list absent seats as reduced-confidence caveats: absent=${
      Object.keys(SEATS).filter((n) => !seated[n]).join(',') || 'none'
    }.`,
  );

  // One record per seat that answered; a seat whose call failed is not in it.
  for (const call of result.agentCalls ?? []) {
    console.log(`— ${call.agent} answered (${call.durationMs}ms, ${call.usage?.totalTokens ?? 0} tokens)`);
  }

  console.log('\n================ TABLE VERDICT ================\n');
  console.log(result.text);
  await table.close?.();
}

main().catch((err) => {
  console.error('round-table run failed:', err?.message ?? err);
  process.exit(1);
});