Agentlar bir-biri bilan qanday ishlaydi

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.


0. Nega bitta qolip

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.


1. Har agent bajaradigan beshta shart

№ShartNima uchun
1afai.agent.json manifesti repo ildizidaPlatforma faqat e'lon qilingan narsaga ruxsat beradi
2POST /afai — run.execute qabul qiluvchi endpointAFAI agentni shu orqali ishga tushiradi
3Callback token bilan /api/v1/* ga murojaatAgent natijani, savolni, faylni shu orqali qaytaradi
4AI kaliti mijozniki (§7 dagi bitta istisno bilan)Bepul davrda ham sarf mijoz hisobida, biz vositachi emasmiz
5GET /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.


2. Manifest

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).


3. AFAI → agent: run.execute

Platforma 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 bu
  • xato 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.


    4. Agent → AFAI: nimalar mumkin

    Callback token bilan (Authorization: Bearer) agentga ochiq:

    EndpointScopeNima uchun
    POST /tasks/{id}/progresstasks:write«45% tayyor»
    POST /tasks/{id}/questiontasks:writeTushunmasa — taxmin qilmaydi, savol beradi
    POST /tasks/{id}/reporttasks:writeYakuniy hisobot
    GET /agentsagents:readTashkilotda yana kim bor, kim nima qila oladi
    GET /membersmembers:readKimga biriktirish mumkin
    POST /filesfiles:writeNatija faylini qoldirish
    tools/emit_event—Hodisa chiqarish (§6)
    tools/create_tasktasks:writeBoshqa ish tug'ilsa — vazifa yaratish
    tools/ask_userchat:writeOdamdan so'rash
    tools/ai_completeai:useAFAI 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.


    5. Agentlararo chaqiruv

    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).

    Ruxsat modeli (a, b uchun)

    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.

    Natija — ma'lumot, ko'rsatma emas

    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.

    Hodisa ko'rinishi va saqlash muddati

    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.

    To'g'ridan-to'g'ri yo'l (d) uchun shartlar

    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.


    6. Hodisa shinasi — amaliy misol

    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"]
    

    7. AI kalitlari

    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.


    8. Vazifa sinxroni: AFAI manba, taskAgt ijrochi

    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.


    9. Tavsiya qatlami

    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 deydiTavsiya
    «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.


    10. Har agent uchun qilinadigan ish

    AgentManifest/afaiBYOKSinxronQolgan ish
    AlfaBrain✅✅ 3 tool✅ byok_keys_ref—serverga qo'yish
    taskAgt✅✅ 3 tooladmin ekrani✅ §8 to'liqafai_workspace_id ni bog'lash
    Eskiz✅✅ 3 tool✅ llm_api_key_enc✅ hodisa orqaliLLM 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.