RODEO

Declaring Scenes

Composing and declaring scenes from intention to live execution.

Scenes orchestrate beats across channels toward an intention. Build prerequisites, compose beats, declare, and monitor.

Prerequisites

A scene requires a channel, an audience, and an intention.

const rodeo = new Rodeo({ apiKey: "your-key" });

const sms = await rodeo.channels.create({
  name: "sms-outreach",
  type: "direct",
  tone: { register: "casual", voice: "peer", awareness: "cold", deniability: "none" },
  provider: "twilio",
  provider_config: { from: "+15551234567" },
  sensor_capabilities: ["delivery", "response"],
});

const audience = await rodeo.audiences.create({
  type: "individual",
  context: { name: "Alex", role: "vendor", region: "Austin" },
  bounds: { maxBeatsPerDay: 3, maxBeatsPerWeek: 8, cooldownMinutes: 60 },
});

const intention = await rodeo.intentions.create({
  type: "recruit",
  description: "Recruit vendor for Austin farmer's market",
  success_condition: { contractSigned: true },
  alignment: {
    audienceBenefit: "Guaranteed booth space and marketing support",
    mutuality: "Vendor gets customers, market gets product variety",
  },
});

Composition

const result = await rodeo.scenes
  .build()
  .intention(intention.id)
  .audience(audience.id)
  .arc("approach")
  .constraints({ maxBeats: 4, maxDuration: "4h", budget: 25 })
  .onDecline("terminate")
  .onPositive("continue")
  .onNegative("decelerate")
  .rule({
    condition: { type: "response", metric: "replied", sentiment: "positive" },
    action: "accelerate",
  })
  .beat({
    sequence: 1,
    channel_id: sms.id,
    content: { body: "Hey Alex — we're expanding the Austin market." },
    tone: { warmth: "warm", urgency: "none", ask: "implicit" },
    timing: { mode: "absolute", at: "2025-01-15T09:00:00-06:00" },
  })
  .beat({
    sequence: 2,
    channel_id: sms.id,
    content: { body: "Happy to jump on a call whenever works." },
    tone: { warmth: "familiar", urgency: "present", ask: "direct" },
    timing: { mode: "relative", after: "2h", anchor: "beat:1" },
  })
  .create();

Scene is now in draft status. Nothing has fired.

Declaration

Moves the scene from draft to live:

const declared = await rodeo.scenes.declare(result.scene.id);

Two things happen:

  1. A ledger entry records the intention, audience type, benefit, and scene hash.
  2. If copilot is enabled, an action card pauses the scene until a human approves.
if (declared.action_card) {
  // Scene in "declared" status — awaiting approval
} else {
  // Scene in "live" status — beats firing
}

Monitoring

Stream events in real-time:

for await (const event of rodeo.scenes.stream(result.scene.id)) {
  console.log(event.type, event.data);
}

Poll for status:

const scene = await rodeo.scenes.get(result.scene.id);
// scene.scene.status: live | adapting | completed | ...

Abort

await rodeo.scenes.abort(result.scene.id);

Sets pending beats to skipped, records outcome as aborted in the ledger.