- Prepaid credit system: org wallet, debit on usage, chat gate, admin credit console - Engine UI: /manager, /tasks, /tasks/[id], /workforce, /dashboard, /traces pages - Direct task creation (HTTP POST /tasks, MCP tasks_create) at pending_manager_review - Pause/resume/cancel/answer actions in engine API and UI - Employee cap (7 per org default), review pipeline guard (active reviewer required) - Scheduler: wide window prevents org starvation; archived employees excluded from cap - Tenant isolation: project-id cross-org 404, engine never uses another tenant's sandbox - Order route fix: manager built with pipeline states + employee profiles - Contract extension: ModelGrantPort.get_org_policy with DefaultOrgPolicy - Engine event types in @agy/contracts (task.step, task.gate, run.log, run.tool, team.changed) - api-engine.ts typed client; api-credit.ts client - Alembic migration c4d5e6f7a8b9: prepaid credit + engine UI tables - 859 tests passed, 1 skipped; ruff/mypy/tsc/eslint/vitest all green Refs: PLAN-v2.md §9 (T1–T43), todo-v2-final.md
Smart Organization — پلتفرم نیروی کار هوش مصنوعی
نسخهٔ وب مستقل و چندسازمانی محصول. این پروژه کاملاً جدا از ربات تلگرام است و هیچ فایلی از ریشهٔ مخزن را در runtime خود import نمیکند.
- پلن محصول و معماری:
PLAN.md - چکلیست اجرایی کامل:
TODO.md - یادداشت زیرساخت فعلی (فقط مرجع):
docs/current-infra-notes.md
۱. محصول چیست
یک وب اپلیکیشن چندسازمانی برای «نیروی کار هوش مصنوعی سازمانی».
کاربر نهایی اینها را میبیند: سازمان، مدیر هوش مصنوعی، کارمندهای هوش مصنوعی، پروژه، وظیفه، فایل/خروجی، مصرف و پرداخت.
کاربر نهایی اینها را نمیبیند: نام مدل، provider، base_url، API key، قیمت خام
provider. اینها فقط در Admin Console و فقط برای مالک سامانه قابل مدیریتاند.
پلتفرم سه لایهٔ نقش دارد:
- مالک سامانه (platform owner) — دقیقاً یک حساب و صاحب کل سیستم. اکانتهای agy، مدلها، routing policy، تعرفهها، sandbox templateها، مشاهدهٔ کل مصرف، و ساخت/تنزل مدیران. agy تنها ارائهدهندهٔ مدل سامانه است و بخش جداگانهٔ «ارائهدهندهها» وجود ندارد.
- مدیر (platform manager) — حسابی که فقط مالک سامانه میسازد. هر مدیر میتواند سازمانهای خودش را بسازد (و مالک آنها شود) یا با دعوت صاحب سازمان، در سازمان مدیران دیگر عضو شود. مدیر به کنسول مالک سامانه دسترسی ندارد.
- کاربر عادی (platform user) — سازمان نمیسازد؛ فقط داخل سازمانهایی که با دعوت به آنها پیوسته کار میکند.
ثبتنام عمومی وجود ندارد. حسابها از دو مسیر ساخته میشوند:
- ساخت مستقیم توسط مالک سامانه در
/admin/users(برای مدیر یا کاربر عادی) که یک رمز موقت یکبارمصرف برمیگرداند؛ - پذیرش لینک دعوت: دعوتشده اگر حساب نداشته باشد، در همان صفحه رمز خودش را تعیین میکند و حساب ساخته میشود. اگر حساب داشته باشد باید اول وارد شود — یک لینک دعوت هرگز جای reset رمز را نمیگیرد.
«فقط یک مالک» یک قاعدهٔ دیتابیسی است: ایندکس یکتای جزئی روی
users.platform_role = 'owner'، پس حتی یک درج مستقیم هم نمیتواند مالک دوم بسازد.
جابهجایی صندلی مالکیت فقط با POST /admin/transfer-ownership انجام میشود که در
یک تراکنش، مالک فعلی را به مدیر تنزل میدهد و جانشین را مالک میکند.
اعتبار پیشپرداخت. سازمانها با اعتبار پیشپرداخت کار میکنند. مالک سامانه در /admin/credits به هر سازمان اعتبار میدهد و هر رکورد مصرف (مدل، sandbox و بازتاب حافظه) بهمحض ثبت، از کیف پول همان سازمان کم میشود. وقتی موجودی به صفر یا کمتر برسد، کارهای آن سازمان متوقف میشوند و گفتگوی مدیر هم مدل را صدا نمیزند. کارهای متوقفشده پس از شارژ خودکار ادامه نمییابند؛ با دکمهٔ «ادامه» دستی و از همان مرحله ادامه داده میشوند.
۲. پیشنیازها
| ابزار | نسخه |
|---|---|
| Node.js | >= 20 |
| pnpm | 9.x |
| Python | 3.13 |
| Docker + Compose | برای postgres/redis |
فعالسازی pnpm در صورت نبود:
corepack enable pnpm
corepack prepare pnpm@9.12.0 --activate
۳. راهاندازی سریع
# ۱) نصب وابستگیهای JS
pnpm install
# ۲) ساخت venv پایتون برای backend
pnpm api:venv
# ۳) کپی فایل env و پر کردن مقادیر (پیشفرضها همه mock هستند)
cp .env.example .env
# ۴) بالا آوردن postgres و redis
docker compose -f infra/docker/docker-compose.yml up -d postgres redis
# ۵) اجرای migration و seed
pnpm db:migrate
pnpm db:seed
# ۶) اجرای همزمان frontend و backend
pnpm dev
آدرسها پس از اجرا:
- Frontend: http://localhost:3000
- API: http://localhost:8000
- API docs (Swagger): http://localhost:8000/docs
- Health: http://localhost:8000/health
حسابهای نمونه (فقط محیط توسعه)
pnpm db:seed دو حساب میسازد:
| نقش | ایمیل | رمز | چه میبیند |
|---|---|---|---|
| مالک سامانه (admin console) | owner@example.com |
owner12345 |
کنسول مالک سامانه، بدون هیچ سازمانی |
| مدیر نمونه | demo@example.com |
demo12345 |
سازمان نمونه که خودش مالک آن است |
مالک سامانه عضو هیچ سازمانی نمیشود — او مستأجرها را از کنسول میبیند، نه از داخل دادهشان. برای دیدن داشبورد سازمانی با حساب مدیر نمونه وارد شوید.
اینها رمزهای توسعه هستند و در
app/seed.pyسختکد شدهاند. هیچوقت در production از اینها استفاده نکنید وJWT_SECRET/ENCRYPTION_KEYواقعی بگذارید (نگهبان config در حالت production مقادیر placeholder را رد میکند).نکته: دامنهٔ ایمیل عمداً
example.comاست. اگر.localیا.testبگذارید،email-validatorآن را رد میکند و حساب ساخته میشود ولی ورود به آن ناممکن است.tests/test_seed.pyهمین را تضمین میکند.
اجرای کل استک با Docker
docker compose -f infra/docker/docker-compose.yml up --build
۴. اسکریپتها
| دستور | کار |
|---|---|
pnpm dev |
اجرای همزمان Next.js و FastAPI |
pnpm build |
build فرانتاند |
pnpm lint |
ESLint (web) + ruff (api) |
pnpm typecheck |
tsc (web) + mypy (api) |
pnpm test |
vitest (web) + pytest (api) |
pnpm format |
Prettier (web) + ruff format (api) |
pnpm db:migrate |
alembic upgrade head |
pnpm db:seed |
دادههای نمونه (platform owner، کاربر نمونه، سازمان، templateها) |
pnpm api:venv |
ساخت venv پایتون |
pnpm worker:billing |
یک پاس worker صورتحساب |
pnpm worker:ai-runtime |
یک پاس worker اجرای taskها |
pnpm worker:sandbox-gateway |
یک پاس پاکسازی sessionهای بیاستفاده |
۵. معماری
۵.۱ سبک معماری
- Clean Architecture + Hexagonal / Ports & Adapters
- DDD برای مرزبندی bounded contextها
- Event-Driven برای billing، audit و observability (الگوی Outbox)
- Modular Monolith در فاز اول — microservice زودهنگام ممنوع
قاعدهٔ سخت: لایهٔ domain هیچ dependency به FastAPI، SQLAlchemy، Redis، HTTP یا
provider خارجی ندارد. فقط application و infrastructure به آن وابستهاند، نه برعکس.
۵.۲ Bounded Contextها
identity · billing · ai_control · workforce · sandbox · knowledge ·
audit · projects · platform
۵.۳ Ports الزامی
ModelProviderPort · PaymentProviderPort · SandboxProviderPort ·
GitProviderPort · DomainRouterPort · StoragePort · VectorStorePort ·
NotificationPort · AuditPort · UsageLedgerPort
هر port حداقل یک adapter mock دارد تا کل استک بدون هیچ credential واقعی اجرا شود.
۵.۴ جریان درخواست
Browser → Web (Next.js/BFF) → API (FastAPI)
├── Domain Modules
├── Event Bus / Outbox → Workers
├── AI Runtime → Model Providers
├── Sandbox Gateway → E2B / Daytona
└── Billing Processor → Stripe
۶. ساختار پوشه
web/
PLAN.md پلن محصول و معماری
TODO.md چکلیست اجرایی
README.md همین فایل
package.json اسکریپتهای ریشه
pnpm-workspace.yaml تعریف workspace
.env.example نمونهٔ env (هیچ secret واقعی ندارد)
apps/
web/ Next.js + TypeScript (UI)
api/ FastAPI + SQLAlchemy (backend)
services/
ai-runtime/ worker اجرای Agent
sandbox-gateway/ wrapper ایزولهسازی اجرا
billing-worker/ پردازش usage و invoice
packages/
contracts/ تایپها و قراردادهای مشترک
ui/ componentهای پایه (design system)
infra/
docker/ docker-compose
migrations/ محل migrationها
docs/
architecture/ مستندات معماری
product/ مستندات محصول
current-infra-notes.md
۷. کجا کد بنویسیم
| میخواهم... | محل |
|---|---|
| صفحه یا component UI بسازم | apps/web/src/app یا apps/web/src/features |
| component پایه/design system | packages/ui |
| endpoint جدید بسازم | apps/api/app/interfaces/http |
| use case بنویسم | apps/api/app/application |
| منطق دامنه بنویسم | apps/api/app/domain/<context> |
| adapter یا اتصال به سرویس بیرونی | apps/api/app/infrastructure |
| worker پسزمینه بنویسم | apps/api/app/workers (پوستهٔ اجرا در services/<name>) |
| خطا ثبت/پاکسازی کنم | apps/api/app/core/redact.py + apps/api/app/infrastructure/error_log.py |
| قرارداد مشترک front/back | packages/contracts |
ممنوع: نوشتن هر کد جدید در فایلهای ریشهٔ مخزن (bot.py, web_panel.py,
bot_menus.py, bot_messages.py) و هر وابستگی به telegram, chat_id,
callback_data, InlineKeyboardMarkup.
۸. سرویسهای خارجی و حالت mock
همهٔ سرویسهای بیرونی پیشفرض روی mock هستند. برای فعالسازی واقعی، این envها را
پر کنید:
| قابلیت | env | مقادیر |
|---|---|---|
| Git | GIT_PROVIDER |
mock | gitea |
| Workspace گیت | GIT_WORKSPACE |
fake (پیشفرض، برای تست) | local (git روی host) |
| دامنهٔ تست | DOMAIN_ROUTER |
mock | caddy |
| دامنهٔ سفارشی | ALLOW_CUSTOM_DOMAINS |
false (پیشفرض) | true |
| Sandbox | SANDBOX_PROVIDER |
mock | docker | daytona | e2b |
| استقرار روی شبکهٔ خصوصی | DEPLOY_ALLOW_PRIVATE_HOSTS |
false (پیشفرض)؛ true فقط برای مقصدی در شبکهٔ خصوصی که قابل اعتماد است |
| MCP gateway | MCP_GATEWAY_ENABLED |
true (پیشفرض) | false |
| پرداخت | PAYMENT_PROVIDER |
mock | stripe |
| اعتبار اولیهٔ سازمانهای جدید | BILLING_STARTING_CREDIT |
عدد؛ پیشفرض 0 (یعنی به سازمان جدید اعتباری داده نمیشود) |
اگر credential واقعی نداریم، قابلیت حذف نمیشود: مدل دیتابیس، interface، UI و adapter mock کامل ساخته میشود و adapter واقعی پشت همین flag آماده میماند.
با پیشفرضها (mock، fake و false) تستها بدون docker، git یا شبکه اجرا میشوند.
حالت واقعی (real mode): وقتی سامانه باید روی یک host واقعی با docker و caddy اجرا شود، این envها را تنظیم کنید:
SANDBOX_PROVIDER=docker GIT_WORKSPACE=local DOMAIN_ROUTER=caddy GIT_PROVIDER=gitea
نیازمندیهای host در این حالت:
dockerیاpodmanدرPATH(باSANDBOX_DOCKER_BINانتخاب میشود). کد کاربر فقط داخل کانتینرهای سختشده اجرا میشود:--read-only، کاربر غیر root،--cap-drop ALL، و محدودیت CPU، حافظه و PID.gitنسخهٔ 2.20 یا جدیدتر درPATH. git را خود پلتفرم اجرا میکند؛ hookها غیرفعالاند و اعتبارنامهها هرگز در argv یا URL نمیآیند.caddyدرPATHو این خط در Caddyfile:import /etc/caddy/agy-preview/*.caddy. بدون این خط adapter اجرا نمیشود، تا یک موفقیت کاذب گزارش نشود.- اجازهٔ بدون رمز برای
caddy reload --config <file>به کاربر سرویس (روی systemd با sudoers یا polkit، نه با root). - پوشههای قابلنوشتن با فضای کافی زیر
SANDBOX_WORKSPACES_ROOTوARTIFACTS_ROOT(پیشفرض./.data/…). پاکسازی خودکارruns/،artifacts/و فایلهای پشتیبان FTP هنوز پیاده نشده است. - هدف استقرار
ftps(TLS صریح) است. هدفftpساده پذیرفته میشود، ولی بدون رمزنگاری است. - تستهای یکپارچگی docker و استقرار با
SANDBOX_DOCKER_IT=1وDEPLOY_IT=1فعال میشوند و بدون آنها رد (skip) میشوند.
ارائهدهندهٔ agy (Google Antigravity) — تنها ارائهدهندهٔ مدل
agy کلید API ندارد؛ اکانتها هویت Google هستند و از طریق OAuth وارد میشوند. کاتالوگ مدلها در seed ساخته میشود و همان اول فعال است. برای شروع:
- با حساب مالک سامانه وارد کنسول مدیریت شوید و از
/admin/agy-accounts→ «افزودن اکانت agy» شروع کنید: لینک احراز هویت گوگل باز میشود، بعد از تأیید، URL بازگشت را از نوار آدرس مرورگر کپی و Paste کنید (صفحهٔ مقصد عمداً بارگذاری نمیشود؛ پارامترها همانجا هستند). - در
/admin/modelsزنجیرهٔ افزودن مدل کاملاً انتخابی است: ارائهدهنده (agy) → اکانت → مدل (کاتالوگ لحظهای و زندهٔ همان اکانت) → افورت استدلال (فقط برای مدلهای استدلالی). هیچ نام مدلی دستی تایپ نمیشود و مشتری نام مدل را هرگز نمیبیند.
هر تعداد اکانت Google که بخواهید اضافه کنید. پول چرخشی، بار را بین اکانتها پخش میکند و اکانتی که ۴۲۹ گرفته تا پایان پنجرهٔ سهمیهٔ آن کنار گذاشته میشود (هم در حافظه و هم در دیتابیس، تا workerها همان تصویر را ببینند). refresh tokenها فقط encrypted at rest ذخیره میشوند و هیچوقت از API برنمیگردند.
۹. Workerهای پسزمینه
پنج process پسزمینه وجود دارد: چهار worker و یک MCP gateway. همه در apps/api/app/workers/ پیاده شدهاند (MCP gateway در apps/api/app/interfaces/mcp/) و services/<name> پوستهٔ اجرای آنهاست:
| worker | کار | دستور |
|---|---|---|
billing-worker |
جمعبندی usage ledger به فاکتور draft (هر دوره یکبار، idempotent)، و در همان پاس حذف نرم درسهای حافظهای منقضیشده (بهجز موارد pin شده) | pnpm worker:billing |
ai-runtime |
برداشتن taskهای در صف و اجرای آنها از مسیر run engine، و یک Scheduler.tick() در هر پاس (فاصلهٔ پاسها با ENGINE_TICK_SECONDS) |
pnpm worker:ai-runtime |
sandbox-gateway |
متوقفکردن sessionهای بیاستفاده، حذف containerهای یتیم با label smart-org.*، و ثبت audit |
pnpm worker:sandbox-gateway |
delivery |
اجرای pipeline runهای در صف (از checkout تا route) و بررسی ریموتها برای pushهای بیرونی هر DELIVERY_POLL_SECONDS |
pnpm worker:delivery |
پیشپرداخت: پاس ai-runtime تسکهای سازمانی را که کیف پول آنها صفر، منفی یا نامشخص است، پیش از خرج کردن هر turn متوقف میکند. این تسکها پس از شارژ دستی ادامه داده میشوند.
MCP gateway یک سرویس مداوم است که با pnpm mcp:dev اجرا میشود. عاملهای بیرونی AI با توکنهای محدود به یک سازمان به آن وصل میشوند. جزئیات در services/mcp-gateway/README.md.
هر دستور worker یک پاس اجرا میکند و خارج میشود (شکل مناسب cron). برای اجرای مستمر --loop بدهید:
cd apps/api && .venv/bin/python -m app.workers billing --loop --interval 3600
اصل مهم: workerها هیچ منطق تجاری ندارند. ai-runtime، sandbox-gateway و delivery سرویسهای خود را از همان builderهای app/application/container*.py میسازند که HTTP API میسازد، پس آنها و API هرگز نمیتوانند دربارهٔ اینکه کدام adapter پشت یک port است اختلاف داشته باشند. billing-worker مستقیم از repositoryها استفاده میکند. جزئیات هر کدام در services/*/README.md.
۱۰. اصول تغییرناپذیر
- اجرای کد کاربر یا agent روی host اصلی ممنوع — فقط از مسیر Sandbox Gateway.
- هر endpoint چندسازمانی باید
organization_idرا enforce کند. - هیچ secret، token یا API key در frontend یا لاگ چاپ نمیشود.
- هر عملیات destructive باید confirmation و audit log داشته باشد.
- مدل و provider از دید کاربر عادی پنهان است.
- هر مصرفی در usage ledger داخلی ثبت میشود (Stripe فقط adapter است).
۱۱. کنسول خطا
هر خطایی که سامانه بگیرد — در درخواست HTTP، در worker پسزمینه، یا در زمان
راهاندازی — با جزئیات کامل در جدول error_events ثبت میشود و در
/admin/errors (فقط مالک سامانه) قابل مشاهده است.
چه چیزی ثبت میشود
| منبع | چه زمانی | شدت |
|---|---|---|
| استثنای مدیریتنشده و خطاهای ۵xx | همیشه | critical / error |
ValueError بدون نوع مشخص |
همیشه | warning |
| سایر پاسخهای ۴xx | فقط با ERROR_LOG_CLIENT_ERRORS=1 |
warning |
۴xx بهصورت پیشفرض ثبت نمیشود چون یک API عمومی دائماً ۴۰۴ از اسکنرها میگیرد و ثبت همهٔ آنها خطاهای واقعی را زیر خود دفن میکند. خطاهای ۵xx همیشه ثبت میشوند، مستقل از این تنظیم.
هر رکورد چه چیزی دارد
kind،error_type،messageوstack(ردِّ کامل خطا)method،path،status_codeوrequest_idorganization_idوuser_id(پس از احراز هویت)fingerprint— هش پایدار از هویت خطا، برای گروهبندیmetadata_json— جزئیات اضافی (مثلاًtask_id)
گروهبندی و رسیدگی
خطاهای یکسان بر اساس fingerprint در یک ردیف جمع میشوند، پس یک باگ که ۴۰۰۰
بار رخ داده یک خط با شمارنده است، نه ۴۰۰۰ خط. fingerprint عمداً سازمان و
کاربر را در نظر نمیگیرد: یک باگ که دو مستأجر را میزند هنوز یک باگ است.
بستن یک گروه همهٔ رخدادهای باز آن را میبندد و در audit log ثبت میشود.
پاکسازی خودکار secretها
هیچ خطایی بدون عبور از app/core/redact.py ذخیره نمیشود. سه لایه:
کلیدهایی که معنایشان secret است (password، api_key، …)، الگوهای
شناختنی (JWT، Bearer، scheme://user:pass@host، sk-…، ghp_…)، و
خودِ مقادیر secret تنظیمشده در settings. یک لاگ خطا که رمز عبور ذخیره کند
از نداشتن لاگ خطا بدتر است.
استفاده از request_id
هر پاسخ X-Request-ID دارد و هر بدنهٔ خطا هم همان شناسه را در
error.request_id برمیگرداند. کاربر میتواند فقط همین یک رشته را گزارش کند
تا دقیقاً یک رکورد پیدا شود.
# ۵۰۰ بگیر، سپس با همان شناسه رکورد را پیدا کن
curl -i http://localhost:8000/api/whatever -H "X-Request-ID: my-trace"
curl "http://localhost:8000/api/admin/errors?search=my-trace" -H "Authorization: Bearer $OWNER_TOKEN"
اجرای دستی workerها و ثبت خطا
خطای یک pass کامل با source=worker ثبت میشود و exit code غیرصفر میدهد تا
cron متوجه شود. خطای یک task مشخص نیز با task_id و سازمان مربوطه ثبت
میشود.
۱۲. مستندات
docs/architecture/overview.md— نمای کلی معماریdocs/security-checklist.md— چکلیست امنیتیdocs/current-infra-notes.md— زیرساخت فعلی (مرجع)