Bu hujjat beshta repo uchun bitta shartnoma: AgentFather, AlfaBrain, Eskiz, Kotiba, taskAgt. Har biri alohida repo, alohida baza, alohida servis bo'lib qoladi — lekin tashqi dunyoga bir xil sirt ko'rsatadi.
Sana: 2026-08-21. Qarorlar loyiha egasi bilan kelishilgan.
Bugun to'rt agent serverda yonma-yon ishlaydi, lekin bir-birini ko'rmaydi: har birining o'z boti, o'z sozlamalar ekrani, o'z AI kaliti muomalasi bor. Mijoz uchun bu to'rtta alohida mahsulot, biz uchun esa to'rtta alohida qo'llab-quvvatlash yuki.
Qolip shuni o'zgartiradi: agent AFAI'ga bitta manifest bilan ulanadi, undan keyin marketplace, OAuth, vazifa taqsimlash, hodisa shinasi va monitoring — hammasi tekin keladi. Ichki agentga imtiyoz yo'q: biz o'z agentlarimizni tashqi dasturchi yuradigan yo'ldan o'tkazamiz, chunki o'sha yo'lni har kuni o'zimiz bosib o'tsakgina u haqiqatan qulay bo'ladi.
| № | Shart | Nima uchun |
| 1 | afai.agent.json manifesti repo ildizida | Platforma faqat e'lon qilingan narsaga ruxsat beradi |
| 2 | POST /afai — run.execute qabul qiluvchi endpoint | AFAI agentni shu orqali ishga tushiradi |
| 3 | Callback token bilan /api/v1/* ga murojaat | Agent natijani, savolni, faylni shu orqali qaytaradi |
| 4 | AI kaliti mijozniki (§7 dagi bitta istisno bilan) | Bepul davrda ham sarf mijoz hisobida, biz vositachi emasmiz |
| 5 | GET /health va tarkibida provayder holati | «Jim ishlamayotgan» agent eng qimmat nosozlik |
Beshinchi shart bo'yicha aniq talab: /health javobida agent qaysi provayderda ishlayotgani ko'rinishi shart. AlfaBrain'da aynan shu narsa nosozlikni ochib berdi — "chat_provider":"mock" bo'lib turgan edi va uni tashqaridan hech kim sezmasdi.
Minimal shakl (platform/docs/MANIFEST.md to'liq maydonlar ro'yxati):
{
"manifest_version": 1,
"id": "afai.eskiz",
"version": "1.0.0",
"name": { "uz": "Eskiz SMS", "ru": "Eskiz SMS", "en": "Eskiz SMS" },
"summary": { "uz": "Telegram guruhidan massoviy SMS kampaniyalari" },
"categories": ["marketing", "communication"],
"runtime": {
"type": "webhook",
"endpoint": "https://agentfather.uz/agents/sms-eskiz/afai",
"timeout_ms": 30000
},
"scopes": ["tasks:write", "members:read", "chat:write"],
"tools": [
{
"name": "estimate_campaign",
"description": "Kampaniya narxini hisoblaydi. Hech narsa yubormaydi.",
"risk": "read",
"confirm": "never",
"input_schema": { "type": "object", "required": ["base_id", "text"] }
},
{
"name": "schedule_campaign",
"description": "Kampaniyani jadvalga qo'yadi. Pul sarflaydi.",
"risk": "dangerous",
"confirm": "always",
"input_schema": { "type": "object", "required": ["base_id", "text", "send_at"] }
}
],
"triggers": [{ "type": "manual" }, { "type": "api" }],
"emits": ["custom.campaign.scheduled", "custom.campaign.finished"]
}
risk: "dangerous" bo'lgan tool tasdiqsiz ishlay olmaydi — validator uni confirm: "never" bilan birga qabul qilmaydi. Pul sarflaydigan yoki mijozga xabar yuboradigan har qanday amal shunday belgilanadi.
description maydoni modelga beriladi: tool qachon ishlatilishini u shu matndan tushunadi. Ya'ni bu hujjat emas, prompt'ning bir bo'lagi.
Trigger turlari cheklangan: manual, api, event, schedule, chat. event bo'lsa event nomi ham yoziladi.
Tekshirish (hisobsiz — kalit kerak emas, CI'da ham ishlaydi):
curl -s -X POST $AFAI_BASE_URL/api/v1/dev/manifest/validate \
-H "Content-Type: application/json" -d @afai.agent.json
# → { "valid": true, "errors": [], "summary": "Manifest yaroqli" }
emits — agent chiqaradigan hodisalar. Bu maydon hujjat emas, shartnoma: boshqa agent shu nomga obuna bo'ladi (§6).
run.executePlatforma agentning endpoint iga POST yuboradi:
{
"type": "run.execute",
"actor": { "id": "usr_…", "name": "Dilshod", "role": "owner", "telegram_user_id": 123 },
"caller": { "installation_id": "ins_…", "agent": "kotiba", "agent_name": "Kotiba",
"chain_id": "run_…", "chain_depth": 1, "audience": "external" },
"run": { "id": "run_…", "input": {...}, "trigger": "agent", "trigger_ref": "run_…",
"chain_id": "run_…", "chain_depth": 1, "audience": "external" },
"agent": { "id": "afai.eskiz", "version": "1.0.0" },
"installation": { "id": "ins_…", "workspace_id": "ws_…", "scopes": [...], "settings": {...},
"owner": { "id": "usr_…", "name": "Ibrohim", "role": "owner",
"telegram_user_id": 123 } },
"callback": { "base_url": "https://agentfather.uz/api/v1", "token": "…", "expires_in": 900 }
}
Ikki blok ikki savolga javob beradi va aralashmaydi:
actor — ishni boshlagan odam. Ko'p holatda null bo'ladi va buxato emas: cron hech kimning nomidan ishlamaydi, Telegram'dan kelgan mijoz xabarini odam boshlamaydi, boshqa agent bergan vazifani ham. Soxta odam o'ylab topilmaydi.
installation.owner — integratsiya kimning hisobida turibdi: uni o'rnatgan va ruxsat bergan odam (yo'q bo'lsa tashkilot egasi). Har doim bor. Agent odamni shundan topadi: actor bo'lsa o'sha, bo'lmasa installation.owner. Ikkalasida ham telegram_user_id faqat identity:read berilganda keladi.
``python odam = payload.get("actor") or payload["installation"]["owner"] tg_id = odam.get("telegram_user_id") ``
Bu huquqni kengaytirmaydi — aynan o'sha odam consent bergan. Nima oshkor bo'lishini bu emas, audience hal qiladi.
caller — agent: ishni boshqa agent boshlagan bo'lsa kim, qaysi zanjirda, necha bo'g'in chuqurlikda. Odam boshlagan ishda null. Payload imzolangani uchun bu blokka ishonsa bo'ladi — chaqiruvchi o'zini boshqa agent deb ko'rsata olmaydi.
audience — kim uchun: member (tashkilot a'zosi) yoki external (begona mijoz). Kotiba mijoz savolini uzatganda external yozadi; Telegram ko'zgusidan kelgan ish o'zi external. Bu belgi yopishqoq: zanjir bo'ylab meros bo'ladi va ichkiga qaytmaydi. Qabul qiluvchi agent external ni ko'rsa faqat tashqariga chiqsa bo'ladigan ma'lumot bilan javob beradi — bu qaror platformaniki emas, agentniki: hujjat huquqlarini u biladi.
run.input ikki xil bo'ladi: {"tool": "…", "arguments": {…}} — ask_agent orqali savol; {"task": {…}, "conversation": [{…, "untrusted": true}]} — delegate_task orqali topshirilgan vazifa. conversation dagi contact rolidagi matn begona odamniki — untrusted: true bilan belgilangan.
callback.token shu ishga bog'langan: boshqa ish nomidan tool chaqirib bo'lmaydi (run_token_mismatch), ish tugashi bilan bekor qilinadi. run_id bermasdan chaqirilgan tool ham shu ishga yoziladi.
Sarlavhalar: X-AFAI-Signature: t=<unix>,v1=<hmac>, X-AFAI-Event, X-AFAI-Delivery, X-AFAI-Attempt.
Imzo tekshiruvi majburiy. v1 = HMAC-SHA256(secret, "{t}.{xom_tana}"). Tekshirilmagan run.execute — bu «internetdagi har kim mening agentimni mijoz nomidan ishga tushira oladi» degani.
Javob ikki xil:
200 + {"status":"succeeded","output":{…}} — ish sinxron tugadi;202 — uzoq ish; agent keyin POST {callback.base_url}/runs/{run_id}/complete ni callback.token bilan chaqiradi.
SMS yuborish, indekslash, hisobot yig'ish — hammasi 202. 200 faqat soniyalar ichida tugaydigan ish uchun.
Callback token bilan (Authorization: Bearer) agentga ochiq:
| Endpoint | Scope | Nima uchun |
POST /tasks/{id}/progress | tasks:write | «45% tayyor» |
POST /tasks/{id}/question | tasks:write | Tushunmasa — taxmin qilmaydi, savol beradi |
POST /tasks/{id}/report | tasks:write | Yakuniy hisobot |
GET /agents | agents:read | Tashkilotda yana kim bor, kim nima qila oladi |
GET /members | members:read | Kimga biriktirish mumkin |
POST /files | files:write | Natija faylini qoldirish |
tools/emit_event | — | Hodisa chiqarish (§6) |
tools/create_task | tasks:write | Boshqa ish tug'ilsa — vazifa yaratish |
tools/ask_user | chat:write | Odamdan so'rash |
tools/ai_complete | ai:use | AFAI AI'dan matn javobi — o'z modeli/kaliti kerak emas ([AI.md](AI.md)) |
Qoida: agent tushunmasa taxmin qilmaydi. ask_question → vazifa discuss holatiga o'tadi → odam javob beradi → agent davom etadi. Jim noto'g'ri bajarishdan ko'ra to'xtab so'ragan yaxshi.
To'rt yo'l bor: uchtasi platforma orqali, to'rtinchisi to'g'ridan.
a) delegate_task — ish topshirish, natija keyin. Agent A vazifani agent B ga beradi, platforma uni darhol B da ishga tushiradi (run.input.task), B hisobot yozganda natija A ga task_result bo'lib qaytadi. Butun oqim kanbanda ko'rinadi, odam istalgan payt aralasha oladi.
b) ask_agent — savol, javob hozir. Agent A agent B ning manifestida e'lon qilingan tool'ini chaqiradi (run.input.tool) va javobni shu zahoti oladi. E'lon qilinmagan tool chaqirilmaydi (tool_not_offered).
c) emit_event — hodisa, javob kutilmaydi. A custom.campaign.scheduled chiqaradi; obuna bo'lganlar eshitadi. A kim eshitayotganini bilmaydi.
d) To'g'ridan-to'g'ri (platformasiz). AFAI_PEERS va AFAI_PEER_SECRET bilan, SDK'dagi peers.py. Platforma auditidan tashqarida — shuni bilib tanlang. Sukut bo'yicha yopiq (AFAI_PEERS bo'sh).
Chaqiruv shu tartibda tekshiriladi, har rad etish audit jurnaliga agent.call_denied bo'lib tushadi, har muvaffaqiyat — agent.called:
1. Tashkilot o'chirgichi. PUT /console/settings/cross-agent {"enabled": false} — agentlar bir-birini umuman chaqira olmaydi, manifest va rozilikdan qat'i nazar. 2. Nishon aniq nom bilan. agent — slug yoki aniq nom. Qism moslik yo'q: "brain" hech kimga tushmaydi, o'xshash nomli agent chaqiruvni o'g'irlay olmaydi. Faqat shu tashkilotda faol o'rnatilganlar. 3. Nishonning allowed_callers ro'yxati. Egasi PUT /console/installations/{id}/policy da allowed_callers bilan «meni faqat falonchi chaqirsin» deya oladi (slug yoki installation id). null — hamma, [] — hech kim. 4. Tezlik. Chaqiruvchi o'rnatish uchun daqiqasiga ~30 agentlararo chaqiruv (ask_agent + delegate_task + emit_event birgalikda). Bu hodisa orqali aylanadigan A→B→A halqasining yagona to'sig'i — chuqurlik hisoblagichi webhook'dan boshlangan yangi zanjirni ko'rmaydi. 5. Zanjir chuqurligi — 3. Bitta hisoblagich (run.chain_depth), yo'l almashganda ham (vazifa → savol → hisobot) davom etadi. Ish ichidan POST /runs bilan ochilgan ish ham o'sha zanjirda qoladi. 6. Zanjir devor soati — 120 s (ildiz ishdan hisoblanadi). Oshsa chain_budget_exceeded: ishni delegate_task bilan start_now: false qilib vazifaga aylantiring.
Nishon o'z scope'lari bilan ishlaydi (B — B sifatida), chaqiruvchi esa runs:write (savol) yoki tasks:write (vazifa) ga ega bo'lishi kerak; nishon vazifa qabul qilishi uchun tasks:read kerak.
ask_agent va delegate_task natijasi belgilangan holda qaytadi:
{"agent": "AlfaBrain", "source": "agent:alfabrain", "untrusted": true,
"status": "succeeded", "output": {...}, "chain_depth": 1}
output 16 000 belgidan uzun bo'lsa {"truncated": true, "text": "…"} ko'rinishida kesiladi. Nishonning xatosidan faqat kod va xabar qaytadi — uning fix/details i chaqiruvchi modelga «ko'rsatma» bo'lib tushmaydi. Chaqiruvchi agent bu matnni o'z promptida ham manba sifatida o'rasin, o'z gapi sifatida emas.
task.* hodisalari faqat tasks:read scope'i bor o'rnatishga yetkaziladi, run.* — runs:read, file.* — files:read. webhooks:manage obuna ochishga yetadi, vazifa matnini ko'rishga emas. O'zi chiqargan hodisani agent har doim ko'radi.
Ish kirishi/chiqishi va qadamlari 30 kun, hodisalar 30 kun, yetkazish yozuvlari 14 kun, bildirishnomalar 90 kun saqlanadi — keyin o'chiriladi. Mijoz xabari shu jadvallarda yotadi, muddatsiz saqlash — shaxsiy ma'lumotni muddatsiz saqlash degani.
1. Zanjir konteksti majburiy. caller bloki uzatiladi, hop oshadi, max_hops (3, qabul qiluvchi ham 3 dan oshirmaydi) ga yetganda 429 hop_limit_exceeded. 2. Alohida kirish siri — AFAI_PEER_SECRET, platformanikidan alohida. Bu sir barcha peer'lar uchun bitta, ya'ni peer yo'lidagi caller.agent o'zini o'zi e'lon qilgan nom: identifikatsiya uchun ishonmang, avtomatik tasdiq shu yo'ldan hech qachon berilmasin. 3. Ro'yxat muhitda — AFAI_PEERS="tasky=https://…/afai|sir,…".
@agent.on_run
async def handle(run):
await run.call_agent("tasky", "create_task", title="SMS: 4 200 ta")
Qachon qaysi yo'l. Hodisa — «kim eshitsa o'shanga». Vazifa — odam ko'radigan, sekin oqim, natija keyin. Savol — javob shu zahoti kerak bo'lganda. To'g'ridan-to'g'ri — platforma yo'q bo'lganda.
Foydalanuvchi Eskiz botida massoviy SMS'ni ertaga soat 10:00 ga qo'ydi.
Eskiz AFAI taskAgt
│ │ │
├─ emit_event ──────────▶│ │
│ custom.campaign. │ │
│ scheduled ├─ vazifa yaratadi │
│ {send_at, count, │ «SMS: 4 200 ta, │
│ cost, base} │ ertaga 10:00» │
│ ├─ webhook ───────────────▶│ #vazifa
│ │ task.created │ sifatida ko'rinadi
│ │ │
├─ custom.campaign. ────▶├─ vazifani yopadi ───────▶│ ✅ bajarildi
│ finished │ │
Natija: mijoz taskAgt'dagi kanbanda «ertaga 10:00 da 4 200 ta SMS» turganini ko'radi, garchi u vazifani u yerda yaratmagan bo'lsa ham.
Obuna manifestda e'lon qilinadi, kodda emas:
"subscribes": ["custom.campaign.*", "task.created", "task.updated"]
Umumiy qoida: kalit mijozniki. Har agentda «Sozlamalar → AI kaliti» ekrani bor, kalit shifrlangan holda tenant qatorida yotadi, sarf mijoz hisobiga tushadi. Bu tanlov emas, xavfsizlik chegarasi: bizning kalitimiz mijoz matnini o'qiy oladigan yagona joyga aylanmasligi kerak.
Yagona istisno — AlfaBrain'ning tanishtiruvi. Yangi foydalanuvchi ovozda o'zi haqida gapiradi va shundan birinchi «miya» quriladi. Bu ro'yxatdan o'tishning bir qismi — mijozda hali hech qanday kalit yo'q. Shuning uchun ONBOARDING_STT_PROVIDER=gemini platforma kalitida ishlaydi va faqat shu bitta oqimda.
Kalit yo'q bo'lsa nima bo'ladi: agent aniq xabar beradi — «AI kalitini ulang: Sozlamalar → AI». Soxta (mock) javob hech qachon qaytmaydi. Mock faqat testda va lokal ishlab chiqishda mavjud; prod konfiguratsiyasida *_PROVIDER=mock qiymati taqiqlanadi va servis ishga tushishda buni tekshiradi.
Sababi tajribadan: AlfaBrain prodda uch oy LLM_PROVIDER=mock bilan turdi. Bot javob berardi, yozuvlar saqlanardi, graf chizilardi — javoblar esa ma'nosiz edi. Hech qanday xato ko'rinmadi. Jim nosozlik ochiq nosozlikdan qimmatroq.
Ikkalasida ham vazifa boshqaruvi bor. Qaror: vazifa AFAI'da tug'iladi va u yerda yashaydi, taskAgt unga Telegram sirti bo'ladi.
AFAI task (yagona manba)
├─ task.created / task.updated ──webhook──▶ taskAgt (nusxa)
└─ taskAgt'dagi o'zgarish ──callback──▶ AFAI (manba yangilanadi)
Ikki nusxa bo'lgani uchun uchta himoya majburiy:
1. Tsikl himoyasi. taskAgt AFAI'dan kelgan o'zgarishni AFAI'ga qaytarmaydi. Har yozuvda origin maydoni bo'ladi (afai yoki taskagt); webhook'dan kelgan yangilanish origin=afai bilan yoziladi va callback yubormaydi.
2. Idempotentlik. Har yetkazishda X-AFAI-Delivery bor; taskAgt oxirgi qayta ishlangan delivery_id ni saqlaydi va takrorini tashlab yuboradi. Webhook «kamida bir marta» yetkazadi, ya'ni takror bo'ladi.
3. Konflikt qoidasi. Ikkala tomon bir vaqtda o'zgartirsa — updated_at kechroq bo'lgani yutadi; teng bo'lsa AFAI yutadi (u manba). Yutqazgan o'zgarish yo'qolmaydi: vazifa izohiga «taskAgt'da bekor qilingan o'zgarish» deb yoziladi. Jim yo'qolgan o'zgarish — mijoz ishonchini yo'qotadigan narsa.
taskAgt tomonida kerak bo'ladigan sxema o'zgarishi:
ALTER TABLE tasks ADD COLUMN afai_task_id text UNIQUE;
ALTER TABLE tasks ADD COLUMN origin text NOT NULL DEFAULT 'taskagt';
ALTER TABLE tasks ADD COLUMN afai_synced_at timestamptz;
CREATE TABLE afai_deliveries (delivery_id text PRIMARY KEY, seen_at timestamptz NOT NULL);
taskAgt o'rnatilmagan bo'lsa AFAI o'z kanbani bilan bugungidek ishlayveradi — sinxron kodi umuman ishga tushmaydi.
AFAI'ning PM agenti (app/services/pm.py) tashkilotda qaysi agentlar borligini ko'radi. Yangi vazifa: kerakli agent o'rnatilmagan bo'lsa, marketplace'dan tavsiya qilish.
| Mijoz nima deydi | Tavsiya |
| «massoviy SMS yuborishim kerak» | Eskiz SMS |
| «xodimlarga vazifa taqsimlamoqchiman» | taskAgt |
| «shaxsiy xabarlarimga javob bersin» | Kotiba |
| «hujjatlarimdan javob topsin», «bilim bazasi» | AlfaBrain |
Tavsiya kalitsiz ham ishlaydi — kalit so'zlar jadvali bo'yicha (_rule_based). Kalit bo'lsa tabiiy tilda. Ikkala holatda ham natija bir xil bo'ladi: agent kartasi + «Yollash» tugmasi.
Agar mos agent umuman yo'q bo'lsa — bu agent_requests ga tushadi (app/services/requests.py), ya'ni «bunday agent kerak» degan so'rov adminkada ko'rinadi. Bozorni mijoz aytadi, biz taxmin qilmaymiz.
| Agent | Manifest | /afai | BYOK | Sinxron | Qolgan ish |
| AlfaBrain | ✅ | ✅ 3 tool | ✅ byok_keys_ref | — | serverga qo'yish |
| taskAgt | ✅ | ✅ 3 tool | admin ekrani | ✅ §8 to'liq | afai_workspace_id ni bog'lash |
| Eskiz | ✅ | ✅ 3 tool | ✅ llm_api_key_enc | ✅ hodisa orqali | LLM kaliti |
| Kotiba | ✅ | ✅ 2 tool | ✅ ai_configs | — | yordamchini yoqish |
Barcha manifestlar platformaning o'z validatoridan o'tgan, har /afai endpoint imzoni tekshiradi va har repoda «manifestda e'lon qilingan tool haqiqatan bor» degan test bor.
Ulanish qanday ishlaydi. Alohida «ulash kodi» oqimi yo'q: run.execute payload'ida actor.telegram_user_id keladi (faqat identity:read berilganda), agent esa o'z foydalanuvchisini shu id bo'yicha topadi — hamma agentimiz Telegram boti. Notanish odamga «botga /start bering» deyiladi.
Hodisalar avtomatik ulanadi. Manifestdagi subscribes ro'yxati agent o'rnatilganda haqiqiy obunaga aylanadi va agentning o'z agsec_… siri bilan imzolanadi. Mijozdan qo'shimcha sozlash so'ralmaydi.
taskAgt'ning public API scope nomlari (tasks:read, tasks:write, members:read) AFAI'nikiga aynan mos tushdi — bu tasodif, lekin foydali tasodif: moslashtirish kerak emas.