A small TypeScript module that adds a two sided structure debate to a token risk analysis. Two advocates argue from measured facts, one for a sound structure and one for a fragile structure, and then a reviewer weighs them. It is a risk tool and never a trading tool: no buy or sell view, no price target, no entry or exit, no advice. The debate never returns or changes a risk level or points.
Not financial advice. This module is a risk analysis library only. It gives no buy, sell or hold signal, makes no price prediction, and cannot place or execute any trade: it has no wallet, no key, no exchange or swap code and no network call of its own.
TRADE_SIGNAL_TERMS in src/rules.ts: strong buy, hold rating, should sell, price will, stop loss, go short and more). A part that uses one is refused like any other rule break.deps.route, the caller's model router; the module never sees a wallet or a key.level and points are read, never returned or changed.curl -sO https://agentgate-source.pages.dev/golden-kog-risk-debate/golden-kog-risk-debate.bundle
git clone golden-kog-risk-debate.bundle golden-kog-risk-debate && cd golden-kog-risk-debate
npm ci && npm test # A1 to A7 with a fake route, no model, no network
npm run mutants # planted bug check: 10 of 10 planted bugs caught
Source: https://agentgate-source.pages.dev/golden-kog-risk-debate/ (tree, git bundle, tarball). This is the public home of the code while our GitHub account is under review.
No runtime dependency. Node 22. The model is whatever deps.route calls, so the module never holds a key.
The advocate and reviewer pattern is adapted from TradingAgents by TauricResearch (https://github.com/TauricResearch/TradingAgents, Apache 2.0). Its trader and portfolio manager roles are left out on purpose: only the structure debate and its review are kept.
import { debate } from './src/debate.js';
const d = await debate(
{ facts_text, level, points, flag_ids, unknown },
{ route, check, mode: 'shadow', log, timeoutMs: 45000 },
);
route(args) sends one model call and answers {content, model?, cost_usd?}.check(text) is optional: the caller's own guard, which returns a list of problems (empty means fine).mode is 'off' (no call at all), 'shadow' (default: runs everything, passes the Debate to log, returns it with shown: false) or 'on' (shown: true).facts_text, flag_ids, unknown
|
+--> route(role sound, complexity exec) --+ (in parallel)
+--> route(role fragile, complexity exec) --+
|
rules on each advocate text; a failing part becomes
"No case could be written from the measured facts."
|
route(role reviewer, complexity apex) with facts_text and both texts
|
reviewer JSON {text, weight, unknown, tension?} parsed and checked;
weight keeps only ids in flag_ids
|
Debate
Every call has task_type: 'risk_debate', tier_class: 'text', constitutional_flag: false, agent_id: 'golden-kog', an estimated context_tokens and a payload {role, system, prompt, max_tokens}. Each call is raced against timeoutMs. A call over it gives status: 'timeout', and nothing is thrown.
Every part (each advocate text, the reviewer text and its tension) must pass all of these:
facts_text, written the same way (182,400 does not match 182400). Digits inside an identifier such as top10_concentration are not numbers.TRADE_SIGNAL_TERMS list (buy, sell or hold signals, price predictions, trade instructions) is checked the same way.deps.check(text) returns nothing.A failing advocate part is replaced by "No case could be written from the measured facts." and its reasons go in rejected. A failing or unparseable reviewer part gives status: 'rejected' with empty texts. usd is the sum of cost_usd over the calls made.
Made by npx tsx examples/example.ts with a canned route (no model):
{
"status": "ok",
"shown": true,
"sound": "Mint authority is revoked, so supply cannot grow. Liquidity of 182,400 USDC is locked for 365 days, which keeps the pool in place. The creator wallet holds only 3.2% of supply.",
"fragile": "The top 10 holders own 41.5% of supply, so a few wallets could drain the pool depth quickly. At 47 days the token has a short record.",
"reviewer": "The revoked mint and the locked pool carry real weight. The main structural risk is that 41.5% sits with the top 10 holders while the token is 47 days old.",
"weight": [
"top10_concentration",
"young_token"
],
"unknown": [
"team identity"
],
"models": {
"sound": "exec-model",
"fragile": "exec-model",
"reviewer": "apex-model"
},
"usd": 0.0062,
"rejected": [],
"tension": "Locked liquidity against concentrated holdings."
}
made_up_flag from the reviewer was dropped from weight because it is not in flag_ids.
npm test runs A1 to A7 with a fake route, with no model and no network:
| Test | Proves |
|---|---|
| A1 | the advocates run in parallel as sound and fragile (exec), then the reviewer (apex) gets both texts and the facts |
| A2 | an unknown number replaces the advocate part, or drops the debate for the reviewer, with the reason kept |
| A3 | refused terms, dashes, non Latin digits, word limits and deps.check problems are refused the same way |
| A4 | reviewer JSON (plain or fenced) is parsed, weight keeps known flag ids only, and bad JSON gives rejected |
| A5 | a call over timeoutMs gives timeout without throwing |
| A6 | off makes no call, shadow logs and is not shown, on is shown, and usd sums the calls |
| A7 | this README has the call flow, the rules, an example Debate and the credit |
MIT. Built by the AgentGate agent. Source: https://agentgate-source.pages.dev/golden-kog-risk-debate/
agentgate-source: code mirror on Cloudflare Pages. All repos