本卡屬 FR-181(母卡 FR-181 母案(同清單上一張)),卡 P-0,批 1。只改 主 repo。依賴:無。
本案要讓 Agent 常駐一條「長輪詢」請求掛在主系統上(最多吊 30 秒,有事立刻回)。問題是出貨的主系統走 gunicorn 預設 sync worker,一個 worker 同時只能服務一個請求,worker 數是 CPU 核數封頂 4——2 核客戶機就 2 個 worker,1 台 Agent 吊著就佔掉 1 個,使用者按「測試連線」再佔 1 個,那 2 秒主系統對所有人都不回應;第 2 台 Agent 上線就整個吊死。這張卡不做產品功能,只做一件事:用數字證明問題存在、證明解法(改 gthread 多執行緒+配額)有效。結論直接決定整案走「長輪詢」還是退回「每 10~15 秒問一次」,所以要在其他卡之前做完、數字要可信。本機 DEV 看不出這個問題(DEBUG=true 的 dev server 本來就多執行緒),一定要用 USE_GUNICORN=true 起。
new:scripts/dev/longpoll_bench.py(純驗證腳本,不進產品)——開 N 條去打一支吊 30 秒的假端點、同時用 20 並發打 GET /api/1.0/health 量 p50/p95;假端點掛在 core/plugins/_bench_longpoll.py 只在 DEBUG 或 BENCH_LONGPOLL=1 時掛。extend:main.py gunicorn options 加 worker_class/threads 兩個 env(GUNICORN_WORKER_CLASS 預設 sync 不變、GUNICORN_THREADS 預設 16)——本卡只加設定入口不改預設,E-1 才翻預設。禁:不動 installer、不動 compose
repo:~/Projects/Billows/Audit-Manager/compliance-manager-be
改既有:main.py 280(gunicorn options 加 worker_class/threads)
新開:scripts/dev/longpoll_bench.py
新開:core/plugins/_bench_longpoll.py
新開:docs/features/FR-181-2611-agent-outbound-only-channel/evidence/bench-longpoll.md
main.py gunicorn options(280 行附近)加 "worker_class": os.getenv("GUNICORN_WORKER_CLASS", "sync")、"threads": int(os.getenv("GUNICORN_THREADS", "16")),預設不變(E-1 才翻);log 那行印出 worker_class/threads。core/plugins/_bench_longpoll.py:一支 GET /api/1.0/_bench/longpoll?wait=30 純 time.sleep(wait) 後回 204,只在 DEBUG 或 env BENCH_LONGPOLL=1 時掛(照 core/plugins/ 既有 mount 形狀,不進 app_modules)。scripts/dev/longpoll_bench.py:參數 --hold N(開 N 條吊 longpoll)、--probe C(C 並發打 /api/1.0/health 60 秒)、印 health 的 p50/p95/max 與失敗數。純 httpx+threading,不裝新套件。DEBUG=false USE_GUNICORN=true GUNICORN_WORKERS=2 BENCH_LONGPOLL=1 python main.py(🔴 kill 時用核過的確切 PID,禁 pkill):①GUNICORN_WORKER_CLASS=sync、--hold 0 基準;②sync、--hold 2(預期 health 全部卡到 30 秒才回,這組數字是「問題存在」的證據);③GUNICORN_WORKER_CLASS=gthread、--hold 20(預期 p95 與①差 < 50 ms)。gthread 下跑 pytest test/ 一輪,與 sync 基準比(先跑 sync 留基準),紅的只能是基準就紅的;多執行緒下若有 DI/thread-local session 相關新紅,那就是 D2 的反證,寫進證據、不要硬修。evidence/bench-longpoll.md:三組數字表、pytest 對照、結論一句「走長輪詢」或「退定時輪詢」+理由。首腦據此定 D2,E-1 據此翻不翻預設。DEV 以 USE_GUNICORN=true GUNICORN_WORKERS=2 DEBUG=false 起:①sync:開 2 條吊著 → health p95 > 25 秒(重現卡死,寫進證據);②gthread 16 執行緒:開 20 條吊著 → health p95 與 0 條時差 < 50 ms;③gthread 下 pytest test/ 紅的只能是基準就紅的(先跑 sync 留基準);④DEBUG=true dev server 不受影響。證據檔寫三組數字+結論「走長輪詢/退定時輪詢」,首腦據此定 D2
~/Projects/Billows/Audit-Manager/compliance-manager-be/docs/features/FR-181-2611-agent-outbound-only-channel/design.md §3 決策定案(D1~D8)、§4 端到端流程兩張圖、§5 詳細設計本卡那節、§7 拆分表本卡那列;為什麼這樣定看 discussion.mdcore/plugins/_agent_commands.py(檔頭說明三個觸發點)、app/remote_agent/service/agent_command_service.py、core/plugins/_agent_config_notify.py、core/plugins/_agent_keys.py、infra/upload_file/remote_agent_adapter.py、core/plugins/remote_agent.py(plugin mount 與 handle)jedi-remote-agent/jedi_remote_agent/api/routing.py(端點表與 AUTH_AGENT)、api/routes/(心跳 route 範本)、app/service/agent_data_plane_client.py(直推出口,留著)、app/service/heartbeat_vulndb.py(_CORE_KEYS 心跳守衛);契約 jedi-agent-protocol/jedi_agent_protocol/commands.py、heartbeat.py、events.py