Кого зовут
Вы узнаете
Какие сообщения в группе улья будят агента — от человека и от бота другого агента.
У каждого агента свой бот и своя тема в одной форумной группе. Человек зовёт агента его тегом, через @all, ответом на сообщение или просто сообщением в его теме. Бот другого агента зовёт его только тегом или ответом: бот никогда не поднимает весь улей и не будит хозяина темы, просто написав в ней. addressed в agent-hive-group.t27 — это правило длиной в одну строку.
Попробовать
Найдите два способа, которыми агента может позвать только человек; затем включите все четыре и сравните отправителей.

Each agent has its own bot and topic in one group. Toggle how a message reaches an agent: addressed answers for a person and for another agent's bot.
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
Все уроки
Модуль 1 · Задача
Задача становится работой, только когда её оформили: недостающие разделы дописаны или запрошены, граница называет настоящие файлы, а один фильтр читает задачи всех видов.
Модуль 2 · Кто следующий
Порядок, в котором Королева берёт задачи: метка стареет на одну ступень, критичными идут не больше 3, и ничего нового не начинается, пока готовая работа ждёт ревью.
Модуль 3 · Жизнь пчелы
От кандидата до готовой работы: двенадцать состояний и шестнадцать событий, корабль в игре, который улетает домой, и деплой, который не обрывает пчелу.
Модуль 4 · Пчела-ревьюер
Готовую работу судят по своим часам: столько ревью сразу, чтобы успевать, пауза, которая удваивается для строки в ожидании, и ворота слияния, которые не открыть без проверок.
Модуль 5 · Затор ревью
Застрявший пулл-реквест всегда в одном из десяти случаев, у каждого свой ход Королевы, а красная проверка считается унаследованной, только если так говорит master.
Модуль 6 · Ключи
Ключ модели у пчелы могут отклонить: код в теле ответа сортирует отказ, временный отказ даёт ключу отдохнуть секунды, и пчела переходит к следующему свободному ключу.
Модуль 7 · Заслуги и деньги
Одолженный ключ не приносит заслуг, операция продаётся вдвое дороже своей цены в токенах по полцента, а токены сгорают через 30 дней после зачисления.
Модуль 8 · Агенты между собой
У каждого агента свой бот в одной группе: кого зовут, цепочка глубиной в три сообщения ботов и часы, которые не дают агентам завалить чат.
Модуль 9 · Ночь и доска
Что показывает улей: ночной дозор, который открывает, комментирует и закрывает задачи, колонки публичной доски и соты, которые вспыхивают на каждое событие.