From a7db8682084bf49c0ad68628a13f7ac92b69c0c2 Mon Sep 17 00:00:00 2001 From: gHashTag Date: Sun, 13 Sep 2026 08:02:13 +0000 Subject: [PATCH] specs(automation): crm-duet -- a seller agent sells to a connected buyer in real Telegram The owner's seller agent (144022504) writes from his session to a connected seller playing a new buyer (435572800, @playom); a persona model answers from her session. Content is for her real game Leela Chakra. The spec encodes why the seller opens (business bot OWNER_TAKEOVER_MS pause), the owner-only and both-connected gates, the SOFT paid cap, the background run and status tool, and DUET_RAN_LIVE=false. Host: 999-multibots-telegraf#2362. Compiled with the t27 wasm compiler: typecheckOk, 0 errors, hirOk; ASCII only. published_figures: test-blocks pin 12734 -> 12739 (+5); --check and --self-check pass. docs/now entry added. Closes #3602 --- ...s-to-a-connected-buyer-in-real-telegram.md | 13 ++ specs/automation/crm-duet.t27 | 124 ++++++++++++++++++ tools/published_figures.py | 3 +- 3 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 docs/now/2026-09-13-crm-duet-a-seller-agent-sells-to-a-connected-buyer-in-real-telegram.md create mode 100644 specs/automation/crm-duet.t27 diff --git a/docs/now/2026-09-13-crm-duet-a-seller-agent-sells-to-a-connected-buyer-in-real-telegram.md b/docs/now/2026-09-13-crm-duet-a-seller-agent-sells-to-a-connected-buyer-in-real-telegram.md new file mode 100644 index 000000000..13ce6ee7f --- /dev/null +++ b/docs/now/2026-09-13-crm-duet-a-seller-agent-sells-to-a-connected-buyer-in-real-telegram.md @@ -0,0 +1,13 @@ +# NOW -- crm-duet: a seller agent sells to a connected buyer in real Telegram (2026-09-13) + +## What landed + +- `specs/automation/crm-duet.t27` (module `automation::crm_duet`, 5 tests): the owner's seller agent writes from his session (144022504) to a connected seller playing a NEW BUYER (435572800, @playom); a persona model answers from HER session. Content is for her real game Leela Chakra (72 planes, entry with a six, ruleset classic, t27.ai/leela, @leela_chakra_ai_bot). +- Why the seller opens: the production business bot answers any DM to the owner but pauses for OWNER_TAKEOVER_MS (30 min) after an owner-typed message; a run that opens from the owner's session keeps it silent, so the buyer is answered once. +- Gates: owner-only tool, buyer must be a connected seller (row in tg_sessions), buyer may not be the owner, background run with a status tool, dry_run sends nothing, session strings never returned. +- Coverage: every seller tool call recorded name -> ok/fail; five paid generations counted; the 1-per-reply / 3-per-run cap is SOFT (`PAID_CAP_IS_HARD = false`) -- prompt plus report, not code. +- Host: 999-multibots-telegraf PR #2362 (`crm-duet-tool.ts`, 12 unit tests with doubles). + +## Not claimed + +- `DUET_RAN_LIVE = false`: whether a run succeeded live is shown by `crm_duet_status` of a real run, not by the spec. diff --git a/specs/automation/crm-duet.t27 b/specs/automation/crm-duet.t27 new file mode 100644 index 000000000..a2bd1d428 --- /dev/null +++ b/specs/automation/crm-duet.t27 @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/automation/crm-duet.t27 -- a REAL Telegram dialogue between two agents: the owner's +// seller agent (from @t27_dev, 144022504) sells every function of the bot to a connected +// seller acting as a new buyer (@playom, 435572800, answered by a persona model from HER +// session), and the content offered is for HER real game -- Leela Chakra +// (@leela_chakra_ai_bot, mini app t27.ai/leela, 72 planes, entry with a six, ruleset classic). +// Host: 999-multibots-telegraf apps/vibee-editor/render/src/agent/crm-duet-tool.ts -- owner-only +// tools crm_duet (starts a run in the background, returns its id) and crm_duet_status +// (transcript, tool coverage, media sent, paid calls). Seller turns are runAgent with the +// owner's ToolContext and surface "business" (no button markers, sales playbook applies); +// buyer turns are one plain completion on resolveProvider() with the buyer persona. +// Sessions come from tg_sessions by id (crm-sellers.t27); sends go through the same +// address-book warm-up as owner-button proposals (tg-proposals.ts). +// WHY the seller speaks first: the production business bot answers any DM to the owner, but +// pauses for OWNER_TAKEOVER_MS (30 min) after a message typed by the owner himself. A duet that +// opens from the owner's session puts the business bot on hold, so the buyer's replies are +// answered by the duet's seller agent and not twice. A duet that let the buyer open would race it. +// WHY it is an exception to the proposal rule: owner buttons are the normal path for a send. +// Here the owner authorises the whole run in advance (confirm before each run), the tool is +// owner-only, both accounts are connected sellers (the row in tg_sessions is the consent), and +// the transcript is returned; session strings never are. +// Coverage: every tool the seller agent calls is recorded name -> ok/fail; paid generations +// (image_generate, image_edit, audio_generate, video_generate, reel_render) are counted; the +// brief asks for at most one per reply and three per run -- a SOFT cap enforced by the prompt +// and measured in the report, not a hard limit in code. +// Claim status: tool written and unit-tested with doubles. Whether a run SUCCEEDED live -- both +// sessions alive, the business bot silent, content delivered -- is shown by crm_duet_status, +// not by this file. +// phi^2 + 1/phi^2 = 3 | TRINITY + +module automation::crm_duet { + + pub const KIND : str = "automation"; + pub const ID : str = "crm-duet"; + pub const REPO : str = "999-multibots-telegraf"; + pub const VERSION : u8 = 1; + + // ---- Who ---------------------------------------------------------------------------- + pub const SELLER : str = "144022504"; // @t27_dev: the owner, sells + pub const BUYER : str = "435572800"; // @playom: connected seller, plays a new buyer + pub const BUYER_GAME : str = "Leela Chakra"; + pub const BUYER_BOT : str = "leela_chakra_ai_bot"; + pub const BUYER_APP : str = "https://t27.ai/leela/"; + pub const LEELA_PLANES : u8 = 72; + pub const LEELA_RULESET : str = "classic"; + + // ---- The tools ------------------------------------------------------------------------ + pub const TOOL_START : str = "crm_duet"; + pub const TOOL_STATUS : str = "crm_duet_status"; + pub const TOOL_CALLER : str = "owner"; // requireOwner + pub const BUYER_MUST_BE_SELLER : bool = true; // row in tg_sessions + pub const BUYER_MAY_BE_OWNER : bool = false; + pub const RUNS_IN_BACKGROUND : bool = true; // start returns at once; status reads the run + pub const TURNS_DEFAULT : u8 = 4; // seller+buyer pairs + pub const TURNS_MAX : u8 = 8; + pub const SELLER_SPEAKS_FIRST : bool = true; + pub const SELLER_SURFACE : str = "business"; + pub const BUYER_TURN_IS_PLAIN_COMPLETION : bool = true; // no tools on the buyer's side + pub const DRY_RUN_SENDS : bool = false; // dry_run: agents talk, Telegram receives nothing + pub const REPORT_TO_OWNER_SAVED : bool = true; // final report from the owner's session to "me" + pub const RETURNS_SESSION : bool = false; + + // ---- Why the seller opens ------------------------------------------------------------- + pub const BUSINESS_BOT_TAKEOVER_MIN : u8 = 30; // OWNER_TAKEOVER_MS in businessBotService.ts + + // ---- Coverage ----------------------------------------------------------------------- + pub const PAID_TOOLS : u8 = 5; // image_generate image_edit audio_generate video_generate reel_render + pub const PAID_PER_REPLY_SOFT : u8 = 1; + pub const PAID_PER_RUN_SOFT : u8 = 3; + pub const PAID_CAP_IS_HARD : bool = false; + pub const MEDIA_FORWARDED : bool = true; // a tool result with done=true and an http url is sent as a file + + // ---- Not claimed ---------------------------------------------------------------------- + pub const DUET_RAN_LIVE : bool = false; // crm_duet_status of a real run is the evidence + + struct Turn { + from_owner: bool, + index: u8, + } + + // The duet alternates and the seller holds every even index, starting at 0. + fn speaker_at(i: u8) -> Turn { + return Turn { from_owner: i % 2 == 0, index: i }; + } + + fn business_bot_silent(opened_by_owner: bool, minutes_since_owner_msg: u8) -> bool { + return opened_by_owner && minutes_since_owner_msg < BUSINESS_BOT_TAKEOVER_MIN; + } + + test "the seller opens and the business bot stays silent for the run" + assert(SELLER_SPEAKS_FIRST); + assert(speaker_at(0).from_owner); + assert(!speaker_at(1).from_owner); + assert(speaker_at(2).from_owner); + assert(business_bot_silent(true, 12)); + assert(!business_bot_silent(false, 12)); + assert(!business_bot_silent(true, 31)); + + test "the run is the owner's, needs a connected buyer who is not the owner, and returns no session" + assert(TOOL_CALLER == "owner"); + assert(BUYER_MUST_BE_SELLER); + assert(!BUYER_MAY_BE_OWNER); + assert(!RETURNS_SESSION); + assert(SELLER != BUYER); + + test "the paid cap is soft and says so" + assert(!PAID_CAP_IS_HARD); + assert(PAID_PER_REPLY_SOFT == 1); + assert(PAID_PER_RUN_SOFT == 3); + assert(PAID_TOOLS == 5); + + test "turns are bounded and the buyer side never calls tools" + assert(TURNS_DEFAULT <= TURNS_MAX); + assert(TURNS_MAX == 8); + assert(BUYER_TURN_IS_PLAIN_COMPLETION); + assert(RUNS_IN_BACKGROUND); + + test "content is for the real game and nothing live is claimed" + assert(BUYER_GAME == "Leela Chakra"); + assert(LEELA_PLANES == 72); + assert(LEELA_RULESET == "classic"); + assert(!DUET_RAN_LIVE); + assert(!DRY_RUN_SENDS); +} diff --git a/tools/published_figures.py b/tools/published_figures.py index dd92c98be..a99ff73a3 100755 --- a/tools/published_figures.py +++ b/tools/published_figures.py @@ -91,8 +91,9 @@ # #3596 added specs/automation/crm-lead-magnet.t27 with five: 12719 + 5 = 12724. # #3598 added specs/automation/crm-sellers.t27 with five: 12724 + 5 = 12729. # #3600 added specs/automation/leela-agent-link.t27 with five: 12729 + 5 = 12734. + # #3602 added specs/automation/crm-duet.t27 with five: 12734 + 5 = 12739. ("test blocks", "blocks", - r"^\s*test\s+(?:\"[^\"]*\"|[A-Za-z_][\w\-]*)\s*\{?\s*$", 12734, + r"^\s*test\s+(?:\"[^\"]*\"|[A-Za-z_][\w\-]*)\s*\{?\s*$", 12739, "#3479 pinned 12644; #3482 removed 188; #3557 added 6; #3556 added 3; #3560 added 77; #3561 added 51; #3596 added 5; #3598 added 5; #3600 added 5"), ]