t27.aiEnglish

Stop и mute

Вы узнаете

Что делают /stop и /mute и почему ни один не теряет человека или прямой вызов.

cooled говорит, прошло ли 5000 мс с последнего сообщения бота. /stop глушит разговоры ботов между собой в чате на STOP_WINDOW_MS, 10 минут, а человека по-прежнему слышно: это may_answer_after_stop. /mute заставляет агента молчать в теме MUTE_MS, час, но на свой тег он отвечает: heard_while_muted. Оба — часы, поэтому никому не нужно включать улей обратно.

Попробовать

Протяните часы /stop дальше 10 минут; затем позовите тегом заглушённого агента.

Открыть интерактивный урок →

The cooldown clock, /stop and /mute
The cooldown clock, /stop and /mute ↗

One agent answers another at most every 5 s. /stop silences bot-to-bot turns for 10 minutes and /mute an agent for an hour. Drag the clocks: a person is always heard.

specs/automation/agent-hive-group.t27

// SPDX-License-Identifier: Apache-2.0
// specs/automation/agent-hive-group.t27 -- the agents' hive: one bot per agent, one forum group.
// Host: 999-multibots-telegraf src/core/bot/agentHive/ (addressing, chain, catalogue of bots).
// Trace: owner 2026-10-05, "every agent its own bot and topic, all together in one group".
// Generated: t27c gen-ts; vendored spec and generated constants in src/spec/.
// Claim status: routing contract; a live group needs Bot-to-Bot Communication Mode
// switched on in @BotFather for every bot, which no API call can do.
// Telegram gives no loop protection between bots, so the depth and the cooldown
// below are the only thing that stops two agents from answering each other forever.
// v1 (2026-10-05): who answers, the bot chain limit, the cooldown, the claim window.
// v2 (2026-10-05): the competitor round (999-multibots-telegraf epic #3641) -- typing kept
// alive, one first speaker on the all tag, follow-ups without a tag, /stop and /mute.
// v3 (2026-10-06): the rest of that round -- the seen/done reaction, standing briefs,
// a topic a person deleted, and /summary.
// v4 (2026-10-09): agents hand off in private, bot to bot. Telegram lets a bot send a private
// message to another bot by @username when Bot-to-Bot Communication Mode is on for both
// (owner: "I switched it on exactly for A2A"). A colleague is heard in private only with the
// hand-off tag, an ask is one hop, and the answer is carried to the person, never a new turn,
// and only while its ask is fresh.
// Earlier laws are unchanged; each version only adds, so a host on an older one stays true.
// phi^2 + 1/phi^2 = 3 | TRINITY

module automation::agent_hive_group {
    pub const KIND : str = "automation";
    pub const ID : str = "agent-hive-group";
    pub const REPO : str = "999-multibots-telegraf";
    pub const VERSION : u8 = 4;

    // Bot messages in a row after one human message: human -> A -> B -> C, then silence.
    pub const MAX_BOT_DEPTH : u32 = 3;
    // One agent answers another agent at most this often.
    pub const BOT_REPLY_COOLDOWN_MS : u32 = 5000;
    // How long "create the bot for agent X" waits for Telegram's creation message.
    pub const CLAIM_TTL_MS : u32 = 900000;
    // The tag that calls every agent of the hive at once.
    pub const ALL_TAG : str = "@all";
    // Suggested username of an agent's bot: t27_<agent id>_bot.
    pub const BOT_USERNAME_PREFIX : str = "t27_";
    pub const BOT_USERNAME_SUFFIX : str = "_bot";

    // A person may call an agent by its tag, by the all tag, by replying to it,
    // or by writing in the agent's own topic. Another bot only by tag or reply:
    // a bot never triggers the whole hive, and never wakes a topic's owner by
    // merely writing there.
    fn addressed(tagged: bool, all_tag: bool, reply_to_me: bool, own_topic: bool, from_bot: bool) -> bool {
        return tagged || reply_to_me || (!from_bot && (all_tag || own_topic));
    }

    // depth = how many bot messages already stand between this one and the person.
    fn chain_open(depth: u32) -> bool {
        return depth < MAX_BOT_DEPTH;
    }

    fn cooled(since_last_ms: u32) -> bool {
        return since_last_ms >= BOT_REPLY_COOLDOWN_MS;
    }

    // A person is always answered; a bot only inside the chain and after the cooldown.
    fn may_answer(from_bot: bool, depth: u32, since_last_ms: u32) -> bool {
        return !from_bot || (chain_open(depth) && cooled(since_last_ms));
    }

    fn claim_fresh(age_ms: u32) -> bool {
        return age_ms < CLAIM_TTL_MS;
    }

    // ---- v2 ----

    // Telegram shows "typing" for about five seconds; an agent turn takes up to a minute.
    // The action is sent again this long after the last one, until the answer goes out.
    pub const TYPING_REFRESH_MS : u32 = 4000;
    // On the all tag only this many agents answer at once, best fit first; the rest
    // answer only when tagged. Eight answers to one "@all hi" is a flood, not a team.
    pub const ALL_FIRST_SPEAKERS : u32 = 1;
    // A person's next message in the same topic goes to the agent that answered
    // last, without a new tag, for this long.
    pub const FOLLOW_UP_WINDOW_MS : u32 = 120000;
    // /stop: no bot answers a bot in that chat for this long. People are still answered.
    pub const STOP_WINDOW_MS : u32 = 600000;
    // /mute: the agent answers in that topic only when tagged by name, for this long.
    pub const MUTE_MS : u32 = 3600000;

    fn typing_due(since_last_ms: u32) -> bool {
        return since_last_ms >= TYPING_REFRESH_MS;
    }

    // rank = the agent's place in the best-fit order for this message, from 0.
    fn speaks_on_all(rank: u32) -> bool {
        return rank < ALL_FIRST_SPEAKERS;
    }

    // A bot never follows up: it must tag or reply, as v1 says.
    fn follows_up(from_bot: bool, same_person: bool, same_thread: bool, age_ms: u32) -> bool {
        return !from_bot && same_person && same_thread && age_ms < FOLLOW_UP_WINDOW_MS;
    }

    fn stopped(since_stop_ms: u32) -> bool {
        return since_stop_ms < STOP_WINDOW_MS;
    }

    // A stop cuts bot-to-bot turns only; a person is always heard.
    fn may_answer_after_stop(from_bot: bool, since_stop_ms: u32) -> bool {
        return !from_bot || !stopped(since_stop_ms);
    }

    fn muted(since_mute_ms: u32) -> bool {
        return since_mute_ms < MUTE_MS;
    }

    // A muted agent still answers its own tag: muting a topic must not lose a direct call.
    fn heard_while_muted(tagged: bool, since_mute_ms: u32) -> bool {
        return tagged || !muted(since_mute_ms);
    }

    // ---- v3 ----

    // A reaction is a courtesy: chats may switch reactions off, so the answer counts
    // whether or not the reaction went through. The "seen" mark stays only when the
    // turn ended in an answer; a failed turn takes it away so nobody waits for nothing.
    fn turn_ok(answer_sent: bool, reaction_ok: bool) -> bool {
        return answer_sent;
    }

    fn keeps_seen_mark(answer_sent: bool) -> bool {
        return answer_sent;
    }

    // /brief: standing instructions for a group or one topic, set by an admin only.
    pub const MAX_BRIEF_CHARS : u32 = 1000;
    // Which brief an agent turn carries: 0 none, 1 the group's, 2 the topic's.
    // The narrower one wins; the two are never glued together.
    fn brief_source(has_topic_brief: bool, has_group_brief: bool) -> u8 {
        if has_topic_brief { return 2; }
        if has_group_brief { return 1; }
        return 0;
    }

    // An empty /brief is accepted: it clears the brief, it is not refused.
    fn brief_fits(chars: u32) -> bool {
        return chars <= MAX_BRIEF_CHARS;
    }

    fn may_set_brief(is_admin: bool, from_bot: bool) -> bool {
        return is_admin && !from_bot;
    }

    // A person deleted an agent's topic: the stored id is stale, so it is recreated.
    // Without rights the recreate fails every time, so it is tried at most this often.
    pub const TOPIC_RECREATE_COOLDOWN_MS : u32 = 600000;
    fn recreate_topic(stored: bool, thread_found: bool, since_last_try_ms: u32) -> bool {
        return (!stored || !thread_found) && since_last_try_ms >= TOPIC_RECREATE_COOLDOWN_MS;
    }

    // /summary: the topic keeps a bounded log; a bare /summary reads the default count.
    pub const SUMMARY_LOG_SIZE : u32 = 200;
    pub const SUMMARY_DEFAULT : u32 = 50;
    fn summary_count(asked: u32) -> u32 {
        if asked == 0 { return SUMMARY_DEFAULT; }
        if asked > SUMMARY_LOG_SIZE { return SUMMARY_LOG_SIZE; }
        return asked;
    }

    // ---- v4: hand-off in private, bot to bot ----

    // Every private hand-off between two agents starts with this tag, then its kind and the
    // person it is for. Only a hive agent of the same owner can send it, so the person named in
    // it is trusted: the turn runs in that person's name, never the owner's.
    pub const DM_TAG : str = "#a2a";
    pub const DM_ASK : u8 = 1;
    pub const DM_ANSWER : u8 = 2;
    // An answer to a person hands off to at most this many colleagues.
    pub const MAX_DM_HANDOFFS : u32 = 2;

    // In private a person is always heard; a bot only when it is a colleague (an agent bot of
    // the same owner) and its message carries the hand-off tag with a known kind.
    fn dm_heard(from_bot: bool, colleague: bool, tag_ok: bool) -> bool {
        return !from_bot || (colleague && tag_ok);
    }

    // An ask runs the colleague's turn; an answer is only carried to the person. So a private
    // chain is two messages long and cannot loop, whatever the agents write.
    fn dm_runs_turn(kind: u8) -> bool {
        return kind == DM_ASK;
    }
    fn dm_carries(kind: u8) -> bool {
        return kind == DM_ANSWER;
    }

    // An ask is always a known tag; an answer only while its ask is fresh -- a colleague never
    // writes to a person nobody asked it about. Half an hour covers the longest turn.
    pub const DM_ANSWER_WINDOW_MS : u32 = 1800000;
    fn dm_tag_ok(kind: u8, since_ask_ms: u32) -> bool {
        if kind == DM_ASK { return true; }
        if kind == DM_ANSWER { return since_ask_ms < DM_ANSWER_WINDOW_MS; }
        return false;
    }

    // How many of the hand-offs an answer asked for are sent.
    fn dm_handoffs_sent(asked: u32) -> u32 {
        if asked > MAX_DM_HANDOFFS { return MAX_DM_HANDOFFS; }
        return asked;
    }

    test "a person calls an agent by tag, all tag, reply or its own topic" {
        assert(addressed(true, false, false, false, false));
        assert(addressed(false, true, false, false, false));
        assert(addressed(false, false, true, false, false));
        assert(addressed(false, false, false, true, false));
        assert(!addressed(false, false, false, false, false));
    }

    test "a bot calls an agent only by tag or reply" {
        assert(addressed(true, false, false, false, true));
        assert(addressed(false, false, true, false, true));
        assert(!addressed(false, true, false, false, true));
        assert(!addressed(false, false, false, true, true));
        assert(!addressed(false, true, false, true, true));
    }

    test "the bot chain closes after three bot messages" {
        assert(chain_open(0));
        assert(chain_open(2));
        assert(!chain_open(3));
        assert(!chain_open(4));
    }

    test "a bot waits out the cooldown before answering a bot" {
        assert(!cooled(0));
        assert(!cooled(4999));
        assert(cooled(5000));
    }

    test "a person is always answered and a bot only inside the limits" {
        assert(may_answer(false, 9, 0));
        assert(may_answer(true, 2, 5000));
        assert(!may_answer(true, 3, 5000));
        assert(!may_answer(true, 2, 4999));
    }

    test "a creation claim lives fifteen minutes" {
        assert(claim_fresh(0));
        assert(claim_fresh(899999));
        assert(!claim_fresh(900000));
    }

    test "typing is sent again four seconds after the last one" {
        assert(!typing_due(0));
        assert(!typing_due(3999));
        assert(typing_due(4000));
    }

    test "on the all tag only the best fit answers at once" {
        assert(speaks_on_all(0));
        assert(!speaks_on_all(1));
        assert(!speaks_on_all(7));
    }

    test "a person follows up in the same topic for two minutes without a tag" {
        assert(follows_up(false, true, true, 0));
        assert(follows_up(false, true, true, 119999));
        assert(!follows_up(false, true, true, 120000));
        assert(!follows_up(false, false, true, 0));
        assert(!follows_up(false, true, false, 0));
        assert(!follows_up(true, true, true, 0));
    }

    test "a stop silences bots for ten minutes and never a person" {
        assert(!may_answer_after_stop(true, 0));
        assert(!may_answer_after_stop(true, 599999));
        assert(may_answer_after_stop(true, 600000));
        assert(may_answer_after_stop(false, 0));
    }

    test "a muted agent still answers its own tag for the hour" {
        assert(heard_while_muted(true, 0));
        assert(!heard_while_muted(false, 0));
        assert(!heard_while_muted(false, 3599999));
        assert(heard_while_muted(false, 3600000));
    }

    test "the answer counts even when the chat refuses reactions" {
        assert(turn_ok(true, false));
        assert(turn_ok(true, true));
        assert(!turn_ok(false, true));
        assert(keeps_seen_mark(true));
        assert(!keeps_seen_mark(false));
    }

    test "the topic brief wins over the group brief, and only an admin sets one" {
        assert(brief_source(true, true) == 2);
        assert(brief_source(true, false) == 2);
        assert(brief_source(false, true) == 1);
        assert(brief_source(false, false) == 0);
        assert(brief_fits(0));
        assert(brief_fits(1000));
        assert(!brief_fits(1001));
        assert(may_set_brief(true, false));
        assert(!may_set_brief(false, false));
        assert(!may_set_brief(true, true));
    }

    test "a deleted topic is recreated, at most once in ten minutes" {
        assert(recreate_topic(false, false, 600000));
        assert(recreate_topic(false, true, 600000));
        assert(recreate_topic(true, false, 600000));
        assert(!recreate_topic(true, true, 600000));
        assert(!recreate_topic(true, false, 599999));
    }

    test "a summary reads fifty by default and never past the log" {
        assert(summary_count(0) == 50);
        assert(summary_count(1) == 1);
        assert(summary_count(10) == 10);
        assert(summary_count(200) == 200);
        assert(summary_count(201) == 200);
    }

    test "in private a colleague is heard only with the hand-off tag" {
        assert(dm_heard(false, false, false));
        assert(dm_heard(true, true, true));
        assert(!dm_heard(true, false, true));
        assert(!dm_heard(true, true, false));
    }

    test "an ask runs a turn, an answer is only carried" {
        assert(DM_ASK == 1);
        assert(DM_ANSWER == 2);
        assert(dm_runs_turn(DM_ASK));
        assert(!dm_runs_turn(DM_ANSWER));
        assert(!dm_runs_turn(0));
        assert(dm_carries(DM_ANSWER));
        assert(!dm_carries(DM_ASK));
        assert(!dm_carries(0));
    }

    test "an answer is carried only while its ask is fresh" {
        assert(DM_ANSWER_WINDOW_MS == 1800000);
        assert(dm_tag_ok(DM_ASK, 4000000000));
        assert(dm_tag_ok(DM_ANSWER, 0));
        assert(dm_tag_ok(DM_ANSWER, 1799999));
        assert(!dm_tag_ok(DM_ANSWER, 1800000));
        assert(!dm_tag_ok(DM_ANSWER, 4000000000));
        assert(!dm_tag_ok(0, 0));
        assert(!dm_tag_ok(3, 0));
    }

    test "an answer hands off to at most two colleagues" {
        assert(MAX_DM_HANDOFFS == 2);
        assert(dm_handoffs_sent(0) == 0);
        assert(dm_handoffs_sent(1) == 1);
        assert(dm_handoffs_sent(2) == 2);
        assert(dm_handoffs_sent(3) == 2);
        assert(dm_handoffs_sent(100) == 2);
    }

    test "VERSION says what the header's last note says" {
        assert(VERSION == 4);
    }
}

// phi^2 + 1/phi^2 = 3 | TRINITY

Открыть спеку урока в плеере ↗

Все уроки