An open-source agent factory. You type one line of English; it plans, writes
and validates a professional async Python sub-agent into subagents/<slug>/,
and delivers it as whatever the sentence asked for: a command-line agent, a
Streamlit or FastAPI dashboard, a JSON API, or an aiogram Telegram bot, each
with its Dockerfile, its free-host config, a pinned requirements.txt and a
one-command deploy. You own every line, it runs on a laptop, and inference
costs nothing.
$ python main.py new "a Solana wallet watcher that logs balance changes every 60s and plots them"
father spec 1/6 planning · provider groq/qwen/qwen3.8-27b
father spec 2/6 wallet_watcher · blockchain · httpx + pandas + matplotlib
father code 3/6 wrote agent.py: 5 classes, 10 methods, docstrings · 288 lines
father code 3/6 wrote test_agent.py · 83 lines
father validate 4/6 ast ok · compile ok · ruff ok · imports ok · consistency ok
father validate 4/6 policy ok · bundle ok · deploy ok · secrets ok · nothing was executed
father delivery 5/6 Dockerfile · .dockerignore · .env.example · bootstrap.py · requirements.txt
father write 6/6 subagents/wallet_watcher/ · 9 files
next: python -m subagents.wallet_watcher.agent --once
Ask for a bot and you get a bot:
$ python main.py new "a Telegram bot that watches a Solana wallet and DMs me on changes"
father spec 2/6 solana_bot · blockchain · interface telegram · aiogram · httpx
father code 3/6 wrote agent.py: 4 classes, 9 methods, docstrings · 253 lines
father code 3/6 wrote bot.py: telegram · aiogram · 296 lines
father code 3/6 wrote test_agent.py · 111 lines
father validate 4/6 policy ok · bundle ok · deploy ok · secrets ok · nothing was executed
father delivery 5/6 Dockerfile · .dockerignore · render.yaml · .env.example · bootstrap.py · requirements.txt · scripts/setup_bot.py
father write 6/6 subagents/solana_bot/ · 15 files
next: cd subagents/solana_bot && cp .env.example .env && python bot.py
It covers any Python specialisation: data/ML, web and APIs, scraping, automation, blockchain, plotting and so on. The planner picks the real libraries the job needs (pandas, scikit-learn, httpx, beautifulsoup4, web3, matplotlib...).
common to every interface
agent.py the async core: WalletWatcher · SolanaBalanceClient · BalanceStore · ...
test_agent.py offline tests with a fake data source: no network, no key
README.md run it · env vars · how to deploy
spec.json the plan it was built from; rebuild with --from-spec
requirements.txt pinned, complete for the bundle
.env.example every variable it reads, all empty
bootstrap.py opt-in install of missing declared packages
Dockerfile python:3.12-slim, non-root, CMD runs the entry file
.dockerignore
added by the interface
cli nothing extra
web app.py Streamlit, or FastAPI with an HTML dashboard
deploy/huggingface/ a complete Space (Docker SDK, port 7860)
deploy/render/ render.yaml blueprint (free web service)
telegram bot.py aiogram 3: polling locally, webhook on a host
scripts/setup_bot.py getMe · command menu · optional setWebhook
deploy/render/ webhook mode
api app.py FastAPI: /health · /run · /history · /latest
deploy/huggingface/ deploy/render/
Every generated sub-agent follows the same standards: asyncio for I/O, classes
and methods with docstrings, logging (never print), try/except around every
external call, configuration from environment variables, and an argparse
main() with --once.
your command ─▶ 1 Planner ─▶ 2 Coder ─────▶ 3 Validator ─▶ 4 Writer
strict JSON agent.py, nine checks subagents/<slug>/
spec + app.py/bot.py, never runs (only stage that
delivery test_agent.py code touches disk)
block ▲
▲ │ 2b Templates (exact text, no model):
│ │ Dockerfile · requirements.txt · .env.example
│ │ bootstrap.py · deploy/huggingface · deploy/render
└── provider layer (free only): Groq free tier → Hugging Face → (mock)
every step logged to logs/father-agent.log
- Planner sends a versioned prompt (
prompts/planner.md) with the JSON schema of the spec. The reply is parsed and checked against that schema (father_agent/spec.py) before any code is written. A malformed spec is sent back once with the errors. The spec carries adeliveryblock (interface,framework,deploy[]); see Interfaces. - Coder writes
agent.py, then the interface file (app.pyorbot.py, fromprompts/coder_streamlit.md,coder_fastapi.mdorcoder_telegram.md), thentest_agent.pyagainst both. Each reply goes straight into the validator; a rejected file goes back to the model with the exact problems, up toFATHER_MAX_REPAIR_ATTEMPTStimes. The plumbing (Dockerfile, host manifests, requirements,.env.example,bootstrap.py) is exact static text fromfather_agent/delivery.py, never model output. - Validator gate (
father_agent/validator.py) checks every file while it is still a string, and never imports or executes generated code:ast— it parses;compile—py_compilein a throw-away temp dir;ruff—E9,F,B(syntax, undefined names, unused imports, bugbear);imports— every import is standard library, a declared dependency, or a sibling file, resolved statically from the AST;consistency— the spec's classes, methods andmain()exist; the tests import only namesagent.pydefines;policy— docstrings,async def,logging, try/except; noprint,eval/exec,os.system,shell=True, or paid-API SDKs;bundle—requirements.txtcovers every third-party import in the bundle, and.env.examplelists every declared variable and every one the code reads;deploy— the Dockerfile'sCMDruns a file that exists; each host manifest (the Space README frontmatter,render.yaml) parses and points at files that exist; every copy underdeploy/matches the root file;secrets— no literal token, API key, private key or long hex string anywhere in the bundle, and.env.exampleholds no values.
- Writer stages the bundle in a sibling folder and renames it into place,
so you get the whole sub-agent or nothing. An existing folder is never
overwritten without
--force.
If any file fails the gate, nothing is written and the problems are printed:
father: agent.py failed the validator gate after 3 attempt(s); nothing was written. Problems:
- ast: line 1: invalid syntax
git clone <your fork of this repo> father-agent && cd father-agent
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
python main.py doctorOr run everything (venv, install, lint, tests, an end-to-end generation) in one go:
bash scripts/verify.shverify.sh needs no key, and after the first install it needs no network: the
suite runs against the offline mock provider and blocks real sockets, and the
end-to-end step builds one sub-agent per interface (cli, telegram, web, api).
Inference is free only. The config accepts exactly these hosts and refuses anything else at start-up, so a paid API cannot be reached by accident:
| Provider | Free key | Default model |
|---|---|---|
| Groq free tier | https://console.groq.com/keys → GROQ_API_KEY |
qwen/qwen3.8-27b |
| Hugging Face Serverless Inference | https://huggingface.co/settings/tokens → HF_TOKEN |
Qwen/Qwen2.5-72B-Instruct |
| Google Gemini API free tier | https://aistudio.google.com/apikey → GEMINI_API_KEY (or GOOGLE_API_KEY) |
gemini-3.8-flash |
cp .env.example .env
# edit .env and paste your key(s) after GROQ_API_KEY=, HF_TOKEN= and/or GEMINI_API_KEY=
python main.py doctor --online # checks each key with GET /models (no tokens spent).env is git-ignored; .env.example only has empty placeholders. Keys are
also scrubbed from every log line.
No key yet? Everything still works: with no key the factory uses an offline, deterministic mock provider that plans with keyword rules and writes from built-in templates (blockchain, scraping, data/ML, web API and automation sub-agents), and renders every interface: Streamlit and FastAPI apps and an aiogram bot. It is how the test suite proves the factory end to end without a network. Real models write far more tailored code, so add a key when you can.
A 429, a 5xx or a timeout is retried with backoff (honouring Retry-After);
when retries run out, or a key is rejected or a model is retired, the chain
switches to the next provider instead of ending the run:
father spec 1/6 groq/qwen/qwen3.8-27b → 429, waiting 3s (attempt 2 of 3)
father spec 1/6 groq/qwen/qwen3.8-27b → 429: ...; switching to huggingface/Qwen/Qwen2.5-72B-Instruct
Every provider call runs under an asyncio timeout (FATHER_REQUEST_TIMEOUT) and
a concurrency semaphore, so a hung free tier cannot freeze the factory. Set
FATHER_ALLOW_MOCK_FALLBACK=true to fall back to the offline mock as a last resort.
The laptop only orchestrates: the package is pure Python and needs no GPU. A
small local model is off by default (a 2 GB card like a Quadro P620 cannot
hold one that writes good code), but any llama.cpp / OpenAI-compatible server on
localhost can be added with FATHER_LOCAL_LLM_URL (see .env.example).
| Command | What it does |
|---|---|
python main.py new "<command>" |
plan, write, validate and save a sub-agent (generate is an alias) |
--out DIR |
parent folder instead of subagents/ |
--provider auto|groq|huggingface|local|mock |
force one provider |
--interface cli|web|telegram|api |
override the interface read from the command |
--framework streamlit|fastapi|aiogram |
override the framework (alone, it picks its interface) |
--dry-run |
plan only and print the spec |
--from-spec subagents/x/spec.json |
rebuild the code from an existing spec |
--force |
replace an existing sub-agent folder |
python main.py list |
list generated sub-agents |
python main.py show <slug> |
show a sub-agent's spec and re-run the gate on its files |
python main.py deploy <slug> --target docker|hf-spaces|render |
refresh that target's folder, re-run the gate, print the exact commands (docker build runs when Docker is installed; --no-build skips it) |
python main.py capabilities |
list the capability packs installed, proposable from the local catalogue, and rejected by the licence allowlist |
python main.py evolve "<task>" |
detect a missing capability and propose an additive upgrade, without generating anything; asks [y/N] |
--yes |
approve without a prompt (scripted use); still declined when CI=true |
--log |
print the evolution ledger |
python main.py bot (or python -m father_agent.bot) |
serve the factory as a Telegram bot (optional extra: pip install -r requirements-bot.txt); same flags, output and exit codes either way; see Run it in Telegram |
--check |
check the bot settings offline and exit; contacts nothing |
python main.py providers [--check] |
show the provider chain and key status |
python main.py doctor [--online] |
check Python, config, providers, prompts, ruff, paths |
Exit codes: 0 success, 1 the factory failed (for example the gate rejected a
file), 2 bad usage or configuration.
The factory never runs what it writes. Read the code first, then:
pip install -r subagents/wallet_watcher/requirements.txt
export WALLET_ADDRESS=<a Solana address>
python -m subagents.wallet_watcher.agent --once # one cycle
cd subagents/wallet_watcher && python -m pytest -q # its own offline testspython bootstrap.py inside a sub-agent reports any declared package that is
missing, and installs it only when you pass --auto-install (or set
FATHER_AUTO_INSTALL=1). It installs exactly what requirements.txt lists,
never a name the code merely imports.
The interface is read from your sentence, and --interface / --framework
override it:
| The command says | Interface | You get |
|---|---|---|
| "a Telegram bot that ..." | telegram · aiogram |
bot.py: polling on your laptop, --webhook on a host, --dry-run proves the handlers with no token; scripts/setup_bot.py checks the token with getMe, registers the command menu and can call setWebhook |
| "a dashboard that ...", "a web app ..." | web · streamlit (fastapi when you say FastAPI) |
app.py over the same agent core |
| "a service that exposes ... over HTTP", "a FastAPI microservice" | api · fastapi |
app.py with /health, /run, /history, /latest |
| anything else | cli |
today's agent, unchanged |
Consuming an API ("track the bitcoin price API") is not serving one, so it stays
cli. A spec with no delivery block (every spec.json written before this
existed) is a cli sub-agent and rebuilds as one.
Every bundle has a Dockerfile. Web and api bundles also get
deploy/huggingface/, a complete Hugging Face Space using the Docker SDK
(Hugging Face's config reference lists only gradio, docker and static as
Space SDKs, so Streamlit runs in Docker on port 7860), and deploy/render/, a
Render Blueprint for one free web service. Telegram bundles get deploy/render/
in webhook mode. Each folder is self-contained, so you can git push it as its
own repository:
python main.py deploy solana_dashboard --target hf-spaces
# prepared subagents/solana_dashboard/deploy/huggingface/ (Dockerfile, README.md, agent.py, app.py, requirements.txt)
# gate: ... bundle ok · deploy ok · secrets ok · nothing was executed
# next (hf-spaces):
# # 1. create an empty Space (SDK: Docker) at https://huggingface.co/new-space
# cd subagents/solana_dashboard/deploy/huggingface
# ...The factory prepares a deploy; it does not push one and never holds a token.
Secrets live in .env (git-ignored, docker-ignored) or in the host's own
secret settings.
The factory can also live in a Telegram chat: send one line of English, watch the six stages land in one message that is edited in place, and get the validated bundle back as a ZIP.
you a Solana wallet watcher that logs balance changes every 60s and plots them
father done · 0:04
father 1/6 planning · provider groq/qwen/qwen3.8-27b
father 2/6 wallet_watcher · blockchain · httpx + pandas + matplotlib
father 3/6 wrote agent.py: 5 classes, 10 methods, docstrings · 288 lines · wrote test_agent.py · 83 lines
father 4/6 ast ok · compile ok · ruff ok · ... · secrets ok · nothing was executed
father 5/6 Dockerfile · .dockerignore · .env.example · bootstrap.py · requirements.txt
father 6/6 subagents/wallet_watcher/ · 9 files
father wallet_watcher is written and validated. 9 files, gate green.
father 📦 wallet_watcher.zip next: python -m subagents.wallet_watcher.agent --once
Those lines are the factory's own progress feed (the same six stages the
terminal prints), not a second progress system. One generation runs at a time;
a second line queues and is told its position. When the gate says no, the bot
says at which stage it stopped and what failed, nothing is written, and
/retry replays the spec that survived.
| In the chat | What it does |
|---|---|
any line of text, or /new <line> |
plan, write, validate and send back <slug>.zip |
/again |
build your last line again |
/retry |
rebuild from the spec that survived a failed run |
/status |
what is running, the queue, and whether the poll loop is alive |
/whoami, who am I |
your Telegram id and how you got in (allowlist or password) |
/auth <password>, /login <password> |
get in with the shared password, when the operator set one |
/start, /help |
the introduction and the command list |
Set up (local, five minutes). The bot is an optional extra; the core never
imports aiogram. Install it as a second step after the core: aiogram 3.31.0
requires pydantic<2.14, so requirements-bot.txt moves pydantic from the
core's 2.14.0 to 2.13.5 (CI runs the whole suite on both).
pip install -r requirements-bot.txt # after the core install; moves pydantic to 2.13.5
# talk to @BotFather → /newbot → copy the token into .env:
# TELEGRAM_BOT_TOKEN=123456789:AA...
# TELEGRAM_ALLOWED_USERS= # leave empty for now
python main.py bot --check # offline: prints the settings, token masked
python main.py bot # or, identically: python -m father_agent.botSend the bot anything: it answers Not allowed with your numeric id. Put that
id in TELEGRAM_ALLOWED_USERS (comma-separated for more than one person) and
restart. The allowlist fails closed on purpose: an open bot would let anyone
spend your free provider quota through your key. Without a provider key the bot
runs on the offline mock, exactly like python main.py new.
Letting someone in without a restart. Set BOT_ACCESS_PASSWORD and the
refusal offers a second door: /auth <password> (or /login <password>; the
whole rest of the line is the password, spaces included) adds the sender's id
for as long as the process runs. The bot deletes the message carrying the
password before it checks it, on a right and a wrong guess alike; where Telegram
will not let it (a group without the delete right) the reply says so and asks
the sender to delete it. A wrong password is refused without saying whether
it was close, and five wrong tries in an hour pause /auth for that id for an
hour. Every successful login DMs each allowlisted id (BOT_AUTH_NOTIFY=0 turns
that off). Password access is never written to disk, so after a restart, which
on the free host is every spin-down, a guest sends /auth once more. Ids in
TELEGRAM_ALLOWED_USERS stay in permanently and never need the password. Left
unset, /auth answers that password access is not enabled and the allowlist is
the only way in. The password is redacted from every log line like the token.
The process also serves GET /healthz on $PORT (default 8080). It answers
200 with {"status": "ok", "seconds_since_poll": ...} only while a Telegram
long poll has succeeded in the last two minutes, and 503 (starting or
stale) otherwise, so whatever pings it is also checking that the bot is
really polling.
Neither Render nor Koyeb offers a free background worker, and both free tiers put a web service to sleep when no inbound traffic arrives. A polling bot only makes outbound calls. So the free recipe is a web service kept awake by a pinger, and the honest 24/7 recipe is a small paid worker.
| Recipe | Cost | What you get |
|---|---|---|
| A · Render free web service + pinger (default) | free | One always-on service fits the 750 free hours a month. Sleeps after 15 idle minutes, so the pinger is what keeps it up. Disk wiped on restart; about a minute of cold start after a gap. |
| B · Koyeb free web service + pinger | free | 512 MB, 0.1 vCPU, one free instance per organization, Frankfurt or Washington. The same keepalive trick, with less CPU. |
| C · paid background worker (the upgrade) | Render $7/month (0.5c-512mb) · Koyeb $1.61/month (eco-nano) or $2.68/month (nano) |
A real worker: no idle sleep, no pinger, no cold start, restarts cleanly. The only one of the three that is genuinely 24/7. |
Prices and limits as published on render.com/pricing, render.com/docs/free and koyeb.com/docs/reference/instances, read 2026-10-09.
Recipe A: Render, free (the default).
- Push this repository to GitHub (this one is already public), then open the
button above. Or by hand: in Render New → Blueprint, pick the
repository. Render reads
render.yaml: a free Python web service that installsrequirements.txtthenrequirements-bot.txt, startspython main.py botand health-checks/healthz..python-versionpins Python 3.12. - Fill in the secrets it asks for:
TELEGRAM_BOT_TOKEN,TELEGRAM_ALLOWED_USERS,GEMINI_API_KEY, and optionallyBOT_ACCESS_PASSWORD(leave it empty to keep/authoff). You type these into Render's own form and they are stored as secrets there. The Blueprint also setsFATHER_PROVIDER_ORDER=gemini,groq,huggingface. Gemini's free tier allows 20 requests per day per project per model, and once it answers429the factory falls through to the next provider. A provider with no key is skipped, so the form does not ask forGROQ_API_KEYorHF_TOKEN: adding either free key in the dashboard later is the only step to get a fallback. - Keep it awake. Render spins the service down after 15 minutes without inbound
traffic, so the repository ships the pinger itself:
.github/workflows/keepalive.ymlrequestshttps://father-agent.onrender.com/healthzevery 5 minutes from GitHub's scheduler. It needs nothing switched on and no account: a public repository's scheduled workflows are free, and this one proves itself on every change to it. Because/healthzis503while polling has stopped, a failing ping is also an alert: the run goes red and opens an issue on the repository, which closes by itself on the first healthy ping. Two limits worth knowing: GitHub's scheduler is best-effort, so a run can be delayed (every 5 minutes against a 15-minute idle window leaves three slots per window), and GitHub disables a scheduled workflow after 60 days without repository activity. To point the pinger at another host, set the repository variableKEEPALIVE_URL(Settings → Secrets and variables → Actions → Variables). If you also want email alerts and a status page, add a free monitor on UptimeRobot or cron-job.org for the same URL every 10 minutes and leave the repository one on as the second line of defence. - Message the bot. Every bundle comes back as a ZIP because the free disk is wiped on every restart or spin-down: the copy in your chat is the one that lasts.
If a Render deploy ever tries to build aiohttp from source ('PyLongObject' has no member named 'ob_digit', then Failed building wheel for aiohttp), an old aiohttp
is resolving: releases below 3.9 predate Python 3.12. The build command's
--only-binary guard refuses the source build and fails at the install step
instead. The fix is to keep aiohttp==3.14.4 pinned exactly in
requirements-bot.txt; it ships a prebuilt wheel for 3.12. CI runs the deployed
build command verbatim on 3.12 (the render-build job), so a widened pin fails there
first.
Recipe C: the paid worker (the upgrade). On Render, create the Blueprint
from deploy/render-worker.yaml instead (set the
Blueprint path when you create it): a worker on the 0.5c-512mb instance,
$7/month, no pinger and no /healthz to call. Delete the free web service so
two bots do not poll the same token (Telegram allows only one poller per token).
On Koyeb, bash deploy/koyeb.sh worker creates the same thing on an eco-nano
instance for $1.61/month.
Recipe B: Koyeb, free. Install the koyeb CLI
and koyeb login, then:
bash deploy/koyeb.sh secrets # stores the token, allowlist and keys as Koyeb secrets
bash deploy/koyeb.sh free # free web service from deploy/Dockerfile, health check on /healthzand point the same 10-minute pinger at https://<app>-<org>.koyeb.app/healthz.
deploy/Dockerfile builds the bot image for Koyeb or any Docker host
(docker build -f deploy/Dockerfile -t father-agent-bot .).
Webhook mode is shipped but off. Setting TELEGRAM_WEBHOOK_URL=https://...
(and optionally TELEGRAM_WEBHOOK_SECRET) makes the bot register
<url>/telegram and receive updates there instead of polling. Polling stays
the default: a free instance can take a minute to cold-start, which loses a
webhook delivery, while a poll loop simply reconnects.
father_agent.integrations.smolagents_tool.make_factory_tool() returns a
smolagents Tool, so a smolagents
agent can ask the Father Agent for a new sub-agent:
from smolagents import CodeAgent, InferenceClientModel
from father_agent.integrations.smolagents_tool import make_factory_tool
agent = CodeAgent(tools=[make_factory_tool()], model=InferenceClientModel())
agent.run("Build me a sub-agent that scrapes Hacker News headlines hourly.")The factory checks every task against a registry of capability packs
(father_agent/evolution/registry.json) before it generates anything. When
a task needs something no pack provides, it proposes an additive upgrade from a
curated, local catalogue and waits for you:
$ python main.py evolve "watch a kafka topic and alert on spikes"
capability check … gap found
need : message-queue client (kafka) (task mentions "kafka")
have : http · schedule · charts · telegram · database · api · web · scraping · ml
proposed upgrade — nothing written yet
add capability pack "kafka"
templates/consumer.py + pack.json
why the task needs a Kafka consumer; no installed pack covers message queues
(task mentions "kafka")
deps kafka-python >=2.0,<3 · Apache-2.0 · open-source, no account, no key
writes father_agent/evolution/packs/kafka/** · registry entry · requirements-packs.txt · ledger
never core modules or requirements.txt — manifest 69 files, sha256 pinned
gate ast ok · compile ok · ruff ok · licence ok (checked in memory)
diff sha256:cf1584e53711084b63da7fc36d20bb4a98d9c2b1c0e9e56edb345a50cb41d5c3
apply this upgrade? [y/N] n
declined — nothing written (answered no).
core manifest intact (69 files, hashes unchanged)
ledger ← {"decision": "declined", "pack": "kafka", "ts": "…", "task": "watch a kafka topic and alert on spikes"}
python main.py new runs the same check after it generates, and asks the same
question when it finds a gap. The guard rails are code, not promises:
- Never touches the core.
father_agent/evolution/core_manifest.jsonpins every core file by sha256. The applier refuses any path in it (and anything outsidepacks/<name>/, the registry andrequirements-packs.txt), refuses to run if the core already drifted, and re-checks every hash after applying.tests/test_evolution.pyproves a core-file write is refused. - Never applies without you. The default is No. With no terminal (a pipe,
cron, CI) it declines and says how to re-run it;
--yesexists for scripts but is still declined whenCI=true. There is no "remember my answer". - Only free, open-source components. Each catalogue dependency carries a
licence, and the validator's
licencecheck rejects anything not on the allowlist (MIT, BSD-2/3-Clause, Apache-2.0, ISC, PSF-2.0, MPL-2.0, LGPL), any paid API SDK, and anything that needs an account or a token. - Offline. Planning reads local JSON and the offline planner; it makes no network call and uses no hosted or paid model.
- On the record. Every proposal and decision is a line in
father_agent/evolution/ledger.jsonl(task, pack, dependencies, decision, diff hash);python main.py evolve --logprints it.
Pack dependencies go to requirements-packs.txt, never requirements.txt.
Changed a core file on purpose? Re-pin it with
python -m father_agent.evolution --write (the test suite fails until you do).
main.py CLI entry point
father_agent/
config.py Config: .env + environment, free-only endpoint allow-list
logging_setup.py rotating log file, progress lines, key redaction
spec.py SubAgentSpec: the planner → coder JSON contract
prompts.py PromptLibrary: versioned templates from prompts/
providers/ Provider base, OpenAI-compatible HTTP client, mock, chain
planner.py Planner: command → spec
coder.py Coder: spec → files, through the gate, with repair
validator.py Validator: ast · compile · ruff · imports · consistency · policy
· bundle · deploy · secrets
delivery.py interface inference + the static kit: requirements, .env.example,
bootstrap.py, Dockerfile, deploy/huggingface, deploy/render
factory.py Factory: the six stages + atomic writer
bot.py the Telegram bot (optional extra): allowlist, queue, ZIP
delivery, /healthz
telegram_progress.py the factory's six-stage feed as one Telegram message edited
in place (a ProgressReporter)
heuristics.py, templates.py the offline mock's planning rules and code templates
interface_templates.py the offline mock's app.py (Streamlit, FastAPI) and bot.py (aiogram)
integrations/ smolagents Tool
evolution/ self-evolution: registry.json, catalogue.json, templates/,
detector, consent gate, applier, core_manifest.json, ledger
prompts/ planner.md, coder_agent.md, coder_tests.md,
coder_streamlit.md, coder_fastapi.md, coder_telegram.md
tests/ offline test suite (sockets blocked); test_bot.py drives the
bot with real Updates and a recording Bot session
scripts/verify.sh install + lint + test + end-to-end check (CI runs it as is)
.github/workflows/ci.yml CI: ruff, verify.sh with the bot extra on Python 3.11 and 3.12,
and verify.sh without it; no secrets
render.yaml Recipe A: the bot on Render's free web service (+ a pinger)
deploy/ render-worker.yaml (Recipe C), koyeb.sh, Dockerfile for the bot
requirements-bot.txt the optional Telegram bot extra (aiogram, aiohttp)
- Better prompts: edit
prompts/*.mdand bump theprompt-versionheader; the version is logged with every run and shown bydoctor. - Stricter or looser gate:
Validator(ruff_rules=..., require_ruff=True), or add a check method invalidator.py. - Another free provider: anything OpenAI-compatible is one
OpenAICompatProvider(...); add its host toFREE_HOSTSinconfig.pyonly if it is genuinely free. - More offline templates: add a
Profileinheuristics.pyand a data source template intemplates.py;tests/test_validator.pychecks every template passes the gate.
pip install -r requirements-dev.txt
pip install -r requirements-bot.txt # optional, a second step: without it tests/test_bot.py skips
python -m pytest -q
ruff check .
bash scripts/verify.sh # what CI runs, key-free and offline after the installCI (.github/workflows/ci.yml) runs on every push and pull request: a lint
job (ruff alone); a tests job that runs scripts/verify.sh on Python 3.11
and 3.12 with the bot extra installed and FATHER_REQUIRE_BOT=1, so the
offline bot suite runs there and a missing aiogram fails instead of skipping;
and a core job that runs the same script without the extra, proving the core
install does not need it. It needs no secrets, so it runs the same on a fork.
MIT. See LICENSE.