Polytraders Dev Guide
internal
v3 spine Phase 1 · Shared contracts 9 demo-wired · 0 shadow-ready · 0 production-live · 100 pending · 109 total 15/33 infra tasks the plan status board

Bot interface

Every bot in the library implements the same shape, even if the internals differ. This is what makes 97 bots feel like one system.

Canonical contract

The Bot<I, O> interface is parameterised by its input type I and output type O. Both must be schemas declared on the typed-schemas page.

interface Bot<I, O> {
  // Identity (immutable)
  botId: string;            // e.g. "1.2", "risk.kill_switch"
  version: string;          // semver, bumped on any decision-affecting change
  authority: "Read" | "Vote" | "Shape" | "Trade" | "Audit";
  layer: "discovery" | "risk" | "execution" | "strategy"
       | "intelligence" | "security" | "governance";

  // Lifecycle
  init(config: BotConfig): Promise<void>;
  validateInput(input: I): ValidationResult;
  decide(input: I, context: BotContext): Promise<O>;

  // Output surfaces
  explain(output: O): PlainEnglishReport;
  emit(output: O): ReportEnvelope;

  // Operational
  health(): BotHealthStatus;
  setMode(mode: LiveMode): void;
}

Why one interface

What each method must do

MethodMustMust not
initValidate the BotConfig against its schema; refuse to run on warning>hard violations.Read config from disk; everything goes through BotConfig.
validateInputReject stale, malformed, or out-of-domain input before decide is called.Mutate the input.
decideBe deterministic given (input, context, config). No clock reads except via context.now().Submit orders directly; only Trade-authority bots may emit OrderIntents, and even those go through Risk first.
explainProduce a one-paragraph plain-English description suitable for a non-engineer.Reveal model internals, secrets, or prices in a way that breaks the locked positioning rules.
emitReturn a ReportEnvelope with a stable reason_code from the reason-code registry.Free-text only. Free text is a logging anti-pattern.
healthReturn a BotHealthStatus within the bot's heartbeat budget.Take an external lock.
setModeHonour live-mode semantics — never silently upgrade itself.Persist the new mode anywhere except the audit log.

BotContext (passed to every decide)

interface BotContext {
  now(): number;                     // monotonic ms — never Date.now()
  correlationId: string;             // joins this decision to the originating event
  parentDecisionId?: string;         // for Risk bots reviewing an OrderIntent
  mode: LiveMode;
  killSwitchActive: boolean;         // checked first in every guardrail
  builderCode: string;               // V2 attribution, present on every order
  signal<T>(name: string): T | null;  // typed read-only access to upstream signals
}

Forbidden in any implementation