# I Turned a Telegram Group Into a Software Project

Author: Maksym Tytarenko | Date: 2026-08-04 | Category: AI & ML | Tags: ai-agents, telegram-bots, automation, ci-cd, founder-tools
Canonical: https://www.tytarenkoagency.com/blog/i-turned-a-telegram-group-into-a-software-project

> One private Telegram group = one project: an idea interview in chat, gated task cards for an agent fleet, command-only deploys, and APKs delivered as files. Built and shipped in a single working session.

# How I turned a private Telegram group into a full project workspace

I run a fleet of coding agents that build and ship my projects. But every project still started the same way: me, at a laptop, opening a terminal. Last week I removed that step. I built a Telegram bot that turns any private group chat into a full project workspace. The whole thing happened in one working session, and I can be precise about what that session contained: a structured design interview, five incremental commits, a live smoke test in a real group, and an adversarial review, all driven from a single agent conversation.

## The idea: a group chat as the project

The concept is simple. One private Telegram group equals one project. You add the bot, and from that moment the group is the project's front door:

![A visual representation of a Telegram group chat with a project management bot.](https://adltqzpaelnsrebtkzfj.supabase.co/storage/v1/object/public/blog-images/ai-generated/uploads/20260804_000428_8a121419.png)


- Describe an idea in plain text (or a voice message) and the bot interviews you about it, then turns the result into a spec and tasks for the agent fleet.
- Say "fix the login on the server" and it drafts a task, shows you a card, and files it to the right repo only after you tap confirm.
- Type /deploy and it triggers the repo's existing CI deploy, then reports back with the live service URL.
- Type /apk and a built Android package lands in the chat as a file you can install from your phone.

Before any code, the design went through the same process I use for every sizable feature: a grill interview. My coding agent asks me questions one at a time, ordered by decision weight (architecture before scope, scope before UX), and every question arrives with a recommended answer and a one-line reason, so my job is mostly to confirm or veto. The decisions get committed to the repo as a versioned spec file, which the implementation is later reviewed against.

The key architectural decision from that interview was what the bot is *not*. It is not an agent. It never writes code, never scaffolds a repo by hand, never deploys anything itself. It is a thin chat front-end over the machinery I already have: a task board, a fleet of executor agents on a VM, and GitHub Actions pipelines. The bot translates conversation into that machinery and translates the machinery's output back into chat. All the new code is routing and rendering; the heavy lifting was already built.

## Design decisions that mattered

A few decisions from the interview shaped everything downstream.

![An infographic summarizing design decisions for the Telegram bot.](https://adltqzpaelnsrebtkzfj.supabase.co/storage/v1/object/public/blog-images/ai-generated/uploads/20260804_000325_621f6525.png)


**Groups, not channels.** In Telegram, channel posts are anonymous. There is no sender ID, which kills any allowlist-based authorization, and there are no reply threads to anchor a conversation about a specific task. Private groups give you both: every message carries the sender's user ID for the allowlist, and a reply to a task card can be treated as an edit of that exact draft. That one fact decided the entire interaction model.

**Repos have roles.** A mobile project is never one repo. It's a game or app repo plus a backend repo. So a project owns a *set* of repos, each bound with a role: backend, app, game, or web. Commands and task routing address roles, not repo names. "The button is misaligned" routes to the app repo; "login fails on the server" routes to the backend. The confirmation card always shows the target repo, so a misroute gets caught by a human tap, not discovered after an agent worked in the wrong codebase.

**Nothing reaches GitHub without a tap.** Every action that creates something (a task, a spec, a new repo) goes through a confirmation card with explicit buttons. The routing is two-tier by cost: gpt-4o-mini classifies each message as a question, a task, a big vague idea, or plain chat, and Claude Sonnet writes the actual issue text in English; voice notes go through Whisper first. A false positive from the classifier costs nothing, since the worst case is a card I dismiss. A false negative is equally cheap: the message just gets treated as chat, nothing fires, and I repeat it as an explicit command.

**Deploys happen only on command.** This one I made a hard rule in the spec: the bot never deploys on its own initiative. Not after a merged pull request, not after a green build, not on a schedule, and never inferred from a vague message. If the router smells deploy intent in free text, it answers "deploys are command-only" and points at /deploy. The command itself is just a trigger for the repo's existing GitHub Actions workflow, the same pipeline a push to main runs, so the deploy logic stays versioned, auditable, and works with or without the bot.

**The database enforces the invariants, not the code.** "At most one active interview per group" is a partial unique index. "Every proactive event is reported exactly once" is a primary key on the seen-events table; the tick posts a message only when the insert succeeds. I've been burned before by invariants that lived in application code and quietly stopped being true. A constraint can't be skipped.

## The physical limits of a chat as a UI

Telegram's Bot API sets hard boundaries that shaped the delivery paths. A bot can send a file up to 50 MB and download one up to 20 MB. So the APK path works like this: the build runs in GitHub Actions (GameCI for Unity projects), the bot pulls the artifact, unzips it, and if the APK is 50 MB or less it lands in the chat as a tappable file. Bigger than that, it goes to private object storage and the chat gets a signed link that lives for seven days. Incoming files over 20 MB the bot honestly refuses and asks for a link instead.

Photos and voice messages are first-class input: a screenshot with the caption "fix this" becomes task context. The image is mirrored to storage and its link is embedded in the issue by deterministic code, not by hoping the drafting model includes it, so the executor agent can always open it. Voice is transcribed and routed exactly like typed text.

## The bot registered itself

My favorite moment of the build: the agent created its own Telegram bot. Bot registration goes through BotFather, Telegram's official bot-creation chat, which has no API. So the agent drove it through a Telethon userbot session authorized on my own account (the same session I already use to read channels): sent the newbot command, answered the name prompts, parsed the token out of BotFather's reply, and then disabled privacy mode, because without that, bots in groups only see commands and the whole free-text router would be blind.

![An illustration of the bot registration process in Telegram.](https://adltqzpaelnsrebtkzfj.supabase.co/storage/v1/object/public/blog-images/ai-generated/uploads/20260804_000216_3f9daf7d.png)


Then it smoke-tested itself the same way: created a real group with the userbot, invited the bot, sent commands, and read the replies. That live test immediately caught a real bug. The bot politely responded to the "bot was added to the group" service message, which carries no text, by asking what the idea was about. Service messages are now ignored.

A live bot with a public webhook and a GitHub write token is exactly the class of change I don't trust myself to review alone. So before calling it done, I turned another agent loose on it.

## Then I let another agent try to tear it apart

My standing rule for changes that touch authentication surfaces is a mandatory adversarial review. The setup is specific: a separate agent in a fresh context, with read access and a test runner but no ability to edit files, receives a list of security claims I make about the code and is prompted to *refute* them. This time the list had eight claims: the webhook fails closed without its secret header, non-allowlisted users can trigger nothing, a double-tap can't create two issues, the GitHub token can't leak into chat or logs, the tick reports each event exactly once, workflows are dispatched only by explicit commands, crafted input can't inject into SQL, storage paths, or HTML, and errors still return 200 to Telegram so there are no retry storms. The reviewer wrote its own reproductions to test each one.

It came back with ten findings. Two were real race conditions I had missed completely:

1. **The double-report race.** The tick that resolves deploy and build runs fires every five minutes, but delivering a large APK (download, unzip, upload) can take longer than that. An overlapping tick would see the run still unresolved and report it a second time: same deploy announced twice, same APK downloaded and delivered twice. The dedup primary key existed, but this path posted the message *before* inserting the dedup row and ignored the insert result.
2. **The double-finalize race.** Finishing an idea interview triggers a spec-writing model call that takes tens of seconds. The founder taps "finalize", sees nothing happen, and taps again, and both handlers pass the status check, producing duplicate specs and duplicate issues. The fix is the same pattern I'd already used elsewhere and failed to apply here: claim the row with an atomic conditional update first, do the slow work after.

The other eight were the characteristic shape of LLM-integration bugs, and each one teaches the same lesson from a different angle. An enum value invented by the drafting model (a task "type" outside the allowed list) crashed the flow *after* the GitHub issue was already created, leaving half-state that the double-tap guard then refused to retry: validate model output before the first irreversible side effect, not after. Attachment links reached the issue body only if the drafting model chose to include them: anything that must happen should be code, and the model only decorates. The webhook secret was compared with a plain string comparison instead of the constant-time helper the codebase already had. All ten were fixed the same day, and the hermetic test suite grew to 29 tests, including ones proving the webhook fails closed without its secret header.

The lesson I keep re-learning: tests written by the author share the author's blind spots. Both races lived in code I had written hours earlier, next to correct dedup logic I had written for the neighboring path. I didn't need more of my own tests. I needed an adversary.

One more guard earned its keep the same evening: my CI has a migration guard that fails the build if a database migration lacks a manifest entry describing how to detect it in the live schema. It failed exactly as designed on the two new migrations until they were registered.

## What this changes

The cost side is small enough to ignore. On current GitHub Actions pricing, an Android build on a Linux runner costs $0.006 per minute, roughly ten to fifteen cents per build. The expensive part of mobile CI is iOS: macOS runners at $0.062 per minute put an iOS build at a dollar or two. When I estimated a typical month for one mobile project (thirty Android and thirty iOS builds), it came out around 35 to 50 dollars, almost all of it macOS minutes. The bot's own runtime is a webhook on infrastructure I already pay for, plus fractions of a cent per message for classification.

![A bar chart comparing the costs of Android and iOS builds on GitHub Actions.](https://adltqzpaelnsrebtkzfj.supabase.co/storage/v1/object/public/blog-images/ai-generated/uploads/20260804_000113_49a1696e.png)


But the real change is where projects can start. An idea used to mean "when I'm back at the laptop." Now it means opening a group chat, typing three sentences, answering interview questions while walking, and tapping confirm on a card. The fleet takes it from there, and the deploy link or the APK comes back to the same chat where the idea was born.

One process decision closed the session. The bot makes starting a project one tap, and cheap starts are how a fleet quietly fills with half-projects nobody asked for. So the rule is written down where the agents read it: proposing is the machine's job, initiating is mine.

## FAQ

### What happens when the group has no repo at all?

That's the bootstrap path: the interview runs first, and on confirmation the bot creates the private repos (one per role for a mobile project), files bootstrap tasks for the agent fleet to scaffold and wire CI, and binds the group to the new repos automatically.

### Why not deploy automatically after a merged pull request?

Repo-level CI still does that where it's configured; the rule constrains the bot. A chat router acting on inferred intent is exactly the component you don't want holding deploy authority, so the bot's deploys need the explicit /deploy command and everything else is refused by design.

### Which models does the bot actually use?

gpt-4o-mini for message classification (cheap and fast, and a wrong guess only costs a dismissed card), Claude Sonnet for interview questions and English issue drafts, and Whisper for voice transcription.

### What limits does Telegram impose on file delivery?

A bot may upload files up to 50 MB and download incoming files up to 20 MB. Larger APKs are delivered as signed links that expire after seven days; larger incoming files have to arrive as links.

### What did the adversarial review actually catch?

Ten findings against eight security claims, including two race conditions (a deploy reported twice when five-minute ticks overlap a slow APK delivery, and a double-tap on interview finalize creating duplicate specs). All were fixed the same day the review ran.

## Related reading

- [AI Automation Architectures for Bootstrapped SaaS: Open-Source Patterns to Match Enterprise Scale](https://www.tytarenkoagency.com/blog/ai-automation-architectures-for-bootstrapped-saas-open-source-patterns-to-match-enterprise-scale)
- [Micro-SaaS Automation Architectures: AI Patterns for Solopreneur Scalability](https://www.tytarenkoagency.com/blog/micro-saas-automation-architectures-ai-patterns-for-solopreneur-scalability)
- [Graph engineering is easy. Not blowing your budget on one workflow is the hard part](https://www.tytarenkoagency.com/blog/graph-engineering-is-easy-not-blowing-your-budget-on-one-workflow-is-the-hard-part)

