DEV Community

Cover image for Cara Codex CLI menunggu hasil run yang dimasukkan ke antrean session osFoundry
BBT for BBT Indonesia

Posted on

Cara Codex CLI menunggu hasil run yang dimasukkan ke antrean session osFoundry

Ini satu-satunya tool di server MCP osFoundry yang memulai pekerjaan tanpa ada orang yang mengklik apa pun, persis seperti yang dikembalikan tools/list ke client mana pun, sudah login atau belum:

{"name": "request_session_run",
 "description": "Queue a bounded goal for an agent, team, or loop session where this assistant is an invited runner. The osFoundry runtime executes the task when available.",
 "inputSchema": {"properties": {
     "session_type": {"enum": ["agent_session", "team_session", "loop_session"]},
     "session_id": {"type": "string"},
     "goal": {"type": "string", "maxLength": 10000},
     "max_rounds": {"type": "integer", "minimum": 1, "maximum": 10},
     "idempotency_key": {"type": "string"}},
   "required": ["session_type", "session_id", "goal", "idempotency_key"],
   "additionalProperties": false},
 "annotations": {"readOnlyHint": false, "openWorldHint": false, "destructiveHint": true},
 "securitySchemes": [{"type": "oauth2", "scopes": ["osfoundry:run"]}]}
Enter fullscreen mode Exit fullscreen mode

Dua frasa di deskripsi itu menentukan semua yang dilakukan client dengannya. "Invited runner" adalah pengecekan izin yang terjadi di tiga tempat. "When available" berarti call-nya kembali sebelum pekerjaan dimulai, jadi client harus datang lagi untuk mengambil hasilnya. Tulisan ini membahas keduanya, ditambah mailbox yang dipakai Codex untuk berbicara dengan session setelahnya, lengkap dengan rekonstruksi sisi client dalam Python yang dijalankan di Mac ini.

Apa yang bisa diketahui client sebelum login?

Cukup banyak. initialize, tools/list, dan metadata protected resource semuanya menjawab tanpa token:

$ uv run --with tiktoken --with jsonschema python3 runner_sim.py
== Part 1: live wire, 10 calls each, ms ==
initialize             HTTP 200  median  375.1  p90  726.0
tools/list             HTTP 200  median  551.1  p90  755.5
protected-resource     HTTP 200  median  325.4  p90  578.8
tools/call (no token)  HTTP 401  median  454.2  p90  492.8
scopes_supported: ['osfoundry:read', 'osfoundry:write', 'osfoundry:run', 'osfoundry:models']
Enter fullscreen mode Exit fullscreen mode

Diukur dari Jepang di Apple M1, jadi angka milidetiknya menggambarkan rute jaringan sama banyaknya dengan server. osfoundry:run dipublikasikan sebagai scope tersendiri, terpisah dari osfoundry:write. Config Codex yang tidak mencantumkannya di scopes tidak akan pernah mendapat token yang bisa mengantrekan run, seat apa pun yang diberikan nanti.

Loop runner memakai lima dari 59 tool server: read_session_state, request_session_run, read_session_task_result, post_session_note, dan read_session_mail. Totalnya 821 dari 10,197 token di daftar (8.1%, dihitung dengan cl100k_base, jadi hanya perkiraan untuk tokenizer Codex sendiri). Dengan hanya mencantumkan kelimanya di enabled_tools, 54 tool lainnya tidak masuk ke prompt.

Siapa yang menentukan bahwa Codex adalah runner?

Tiga pihak terpisah, dan satu call butuh ketiganya:

  1. Token. request_session_run mendeklarasikan osfoundry:run. Halaman login menampilkan scope itu sebagai "Run sessions where you grant a runner role".
  2. Grant. Di web client osFoundry (ExternalAssistantsPanel.tsx), grant session membawa action dengan label View, Reply, Edit, dan "Start work". Dropdown role pada grant hanya menawarkan Contributor kalau grant itu berisi write, dan Runner kalau berisi run.
  3. Seat. Owner menyimpan role per session, yang dikirim client sebagai POST /external-assistants/{connection}/sessions/{session_type}/{session_id} dengan {"role": "observer" | "contributor" | "runner"}. Dropdown menampilkannya sebagai Viewer, Contributor, dan Runner.

Script ini merekonstruksi pengecekan tersebut. Pemetaan role ke tool diambil dari teks bantuan panel ("Contributor to post notes and propose handoffs, or Runner to also request runs"), dan scope per tool dari schema live. Kode server osFoundry tidak publik, jadi ini rekonstruksi aturannya, bukan rekaman penolakan dari server:

grant actions ['read']                    -> roles offered ['observer']
grant actions ['read', 'write']           -> roles offered ['observer', 'contributor']
grant actions ['read', 'write', 'run']    -> roles offered ['observer', 'contributor', 'runner']
tool                      observer   contributor  runner
read_session_state        yes        yes          yes
request_session_run       -          -            yes
read_session_task_result  yes        yes          yes
post_session_note         -          yes          yes
read_session_mail         yes        yes          yes
request_session_run       seat=contributor  scopes=read+run+write  -> seat 'contributor' cannot call it
request_session_run       seat=runner       scopes=read+write      -> token lacks osfoundry:run
Enter fullscreen mode Exit fullscreen mode

Pemisahan ini penting bagi siapa pun yang menulis client. Penolakan pada request_session_run punya dua solusi berbeda: login ulang dengan scope tersebut (urusan Anda) atau minta seat ke owner (urusan mereka). Laporkan yang mana, jangan langsung retry.

Apa yang terjadi antara call dan hasilnya?

Call itu mengantrekan sebuah task lalu kembali. Type di web client menunjukkan task apa itu: sebuah AgentTask dengan task_type: "external_assistant", type yang ditolak oleh route pembuatan task di API, karena hanya server yang bisa mengaktifkannya. Status-nya bergerak melalui pending, running, input_required, completed, failed, atau cancelled. Di tab Trigger session, task ini diberi label "Requested by an outside assistant".

Siapa yang mengambilnya tergantung di mana session berjalan. Untuk session di aplikasi desktop milik owner, agent_session_runtime.rs menjabarkan timing-nya:

  • DEFAULT_POLL_INTERVAL_MS bernilai 15,000. Task yang sudah jatuh tempo diklaim pada poll berikutnya dengan lease.
  • Setelah empat poll yang tidak menemukan apa-apa (IDLE_CYCLES_BEFORE_BACKOFF), interval naik mengikuti IDLE_BACKOFF_LADDER_MS: 30 s, 60 s, lalu 300 s. Sebuah komentar di file itu menyebut konsekuensinya: trigger yang diaktifkan server dan "arriving at a fully idle desktop waits up to five minutes" (tiba di desktop yang benar-benar idle menunggu sampai lima menit). Apa pun yang dilakukan seseorang di aplikasi akan membangunkan loop dalam sekitar satu detik.
  • Session yang "already has a run in flight" menunda task-task yang jatuh tempo. Run di satu session tidak saling tumpang tindih.
  • Saat run selesai, hasilnya ditulis ke mail session dari runtime dengan task_id. Subject-nya menunjukkan apakah run selesai dengan bersih ("Task … result") atau berhenti di batas ("goal not fully met", "stopped at your token ceiling").
  • Goal diperiksa sebagai input yang tidak tepercaya. provenance_for_task_type hanya mengembalikan Operator untuk manual, yaitu run yang dimulai owner di aplikasi. Semua type lain, termasuk yang ini, melewati osGuard.

input_required layak mendapat penanganan sendiri di client mana pun. Artinya run sedang menanyakan sesuatu ke seseorang di osFoundry dan tertahan menunggu jawaban. Run-nya masih berjalan. Tetap tunggu, dan beri tahu pengguna Anda bahwa seseorang di sisi osFoundry perlu menjawab.

Seberapa sering client harus polling?

Codex tidak bisa tidur di dalam tool call, dan tool call MCP yang memblokir selama beberapa menit akan terkena timeout client. Jadi bentuk yang realistis adalah loop polling pada read_session_task_result. Pertanyaannya tinggal interval.

Bagian 4 dari script mensimulasikannya dengan jam virtual: 2,000 run, delay pengambilan diambil dari interval runtime itu sendiri, dan durasi run dari distribusi log-normal dengan median 90 s. Durasi run itu asumsi, jadi anggap angka absolutnya sebagai ilustrasi. Setiap strategi menghitung jumlah call yang dibuat dan seberapa lama setelah run selesai ia baru menyadarinya:

runtime active    pending wait: median   7.5 s  p90  13.5 s  max  15.0 s
  poll every 5 s            calls/task median   20  p90   41   noticed late by median   2.5 s  p90   4.5 s
  poll every 30 s           calls/task median    4  p90    7   noticed late by median  15.0 s  p90  26.8 s
  poll 5 s x1.5, cap 60 s   calls/task median    6  p90    8   noticed late by median  21.5 s  p90  48.8 s
  poll 15 s x2, cap 120 s   calls/task median    3  p90    4   noticed late by median  41.8 s  p90 107.2 s
  poll first at 60 s, then 15 s calls/task median    4  p90   11   noticed late by median   7.9 s  p90  14.6 s
runtime idle-300  pending wait: median 152.4 s  p90 273.7 s  max 300.0 s
  poll every 5 s            calls/task median   51  p90   78   noticed late by median   2.5 s  p90   4.5 s
  poll every 30 s           calls/task median    9  p90   13   noticed late by median  14.8 s  p90  26.6 s
  poll 5 s x1.5, cap 60 s   calls/task median    9  p90   11   noticed late by median  28.4 s  p90  52.9 s
  poll 15 s x2, cap 120 s   calls/task median    5  p90    6   noticed late by median  59.0 s  p90 107.7 s
  poll first at 60 s, then 15 s calls/task median   14  p90   23   noticed late by median   7.5 s  p90  13.4 s
Enter fullscreen mode Exit fullscreen mode

Ada tiga hal yang bisa diambil.

Exponential backoff tidak menguntungkan di sini. Itu refleks umum untuk polling, tapi run ini berlangsung beberapa menit, bukan berjam-jam. "15 s x2, cap 120 s" menghemat satu atau dua call dibanding interval tetap 30 s, tapi baru menyadari hasilnya 40 sampai 60 s lebih lambat pada median.

Waktu tunggu pertama yang mendekati durasi run biasa adalah kompromi terbaik pada runtime yang aktif: empat call pada median, hasil disadari dalam 8 s. Setiap call sebelum run mungkin selesai adalah call yang terbuang.

Kondisi runtime itu sendiri lebih berpengaruh daripada strategi client. Di desktop yang sudah idle, run median berada di pending selama 152 s sebelum ada yang mengklaimnya, dan tidak ada interval polling yang bisa mengubah itu. Kalau Anda memantau read_session_state dan melihat task masih pending setelah beberapa menit, itu idle backoff dari runtime, bukan run yang macet.

Run yang diantrekan berturut-turut di satu session akan berbaris satu per satu:

  queued run 1: starts at   13.9 s, done at  121.9 s
  queued run 2: starts at  135.9 s, done at  346.2 s
  queued run 3: starts at  357.7 s, done at  406.6 s
Enter fullscreen mode Exit fullscreen mode

Kalau Codex mengantrekan tiga goal dan mem-poll ketiganya dengan timer 5 s, dua di antaranya menghabiskan beberapa menit mengembalikan pending hanya karena antrean.

Kenapa note tidak bisa memulai run?

post_session_note hanya butuh osfoundry:write dan seat Contributor. Kalau note bisa membangunkan agent session untuk menjalankan run, setiap Contributor pada dasarnya adalah Runner dengan nama lain. Deskripsinya menutup kemungkinan itu: "Other agents can reply to the returned external sender slug; this does not trigger an automatic run."

Jadi note adalah pesan, bukan pemicu. Note masuk ke mail session dengan display name dari connection. Web client menampilkan external_sender_name dan label "outside assistant" di Team mail. Agent bisa membalasnya saat run berikutnya.

Apa yang dikembalikan mailbox?

read_session_mail dideskripsikan sebagai "bounded notes addressed to this connection and notes it posted", dengan "Internal agent analysis is excluded." Schema-nya membatasi satu halaman pada limit 1 sampai 20 dan mundur ke belakang dengan before_id.

Script ini memodelkan mail dari team session yang sibuk, 409 baris dari type pesan yang dikenal web client (analysis, tool_call, tool_result, agent_note, broadcast), lalu menerapkan filter yang dideskripsikan:

session mail rows 409; visible to the connection 22 (addressed to it 13, posted by it 9)
  not visible, by type: {'tool_call': 97, 'tool_result': 62, 'agent_note': 25, 'analysis': 176, 'broadcast': 27}
  limit 20: 2 calls to walk back through all 22 notes
  limit 10: 3 calls to walk back through all 22 notes
  limit  5: 5 calls to walk back through all 22 notes
Enter fullscreen mode Exit fullscreen mode

Jumlah barisnya berasal dari model. Yang penting adalah bentuknya: yang terlihat oleh connection hanya potongan kecil, jadi limit: 20 biasanya sudah mengambil semuanya dalam satu atau dua call. Hasil run juga tidak ada di potongan itu. Di kode runtime desktop, mail hasil diposting tanpa penerima, jadi tidak dialamatkan ke connection. Baca hasil lewat read_session_task_result dan percakapan lewat read_session_mail, dan jangan berharap yang satu bisa menggantikan yang lain.

Di mana schema menghentikan client?

Tiga belas kasus terhadap inputSchema live, divalidasi dengan jsonschema (Draft 2020-12):

request_session_run       goal 10,000 chars              ok
request_session_run       goal 10,001 chars              REJECT maxLength: goal
request_session_run       max_rounds 10                  ok
request_session_run       max_rounds 11                  REJECT maximum: max_rounds
request_session_run       extra field 'context'          REJECT additionalProperties
request_session_run       session_type 'graph_session'   REJECT enum: session_type
read_session_task_result  no task_id                     REJECT required
read_session_mail         limit 20                       ok
read_session_mail         limit 0                        REJECT minimum: limit
read_session_mail         limit 50                       REJECT maximum: limit
post_session_note         subject 200, body 10,000       ok
post_session_note         body 10,001                    REJECT maxLength: body
post_session_note         to: '@writer'                  REJECT additionalProperties
Enter fullscreen mode Exit fullscreen mode

Dua di antaranya mudah membuat tersandung. Run tidak punya field context, jadi latar belakang dimasukkan ke dalam goal, dan batas 10,000 karakter berlaku untuk keduanya. Note tidak punya to, jadi Anda tidak bisa mengalamatkannya ke satu agent. Setiap note dikirim ke session, dan agent membalas ke sender slug milik connection.

Seluruh script memakan waktu 19.3 s wall-clock di M1, termasuk 40 network call. User CPU time-nya 0.47 s, jadi simulasinya sendiri bisa diabaikan.

Kalau Anda membangunnya sendiri

  • Publikasikan "start work" sebagai OAuth scope tersendiri, dan kaitkan role ke grant sekaligus ke token, supaya baik caller maupun grant yang sudah usang tidak bisa menjadikan dirinya runner.
  • Langsung kembalikan task id, dan beri caller cara murah untuk membaca status. Jangan buat tool call MCP menunggu run agent selesai.
  • Tunjukkan perilaku pengambilan task yang sebenarnya dari runtime (interval poll, satu run per session) di dokumentasi atau di status, supaya client melakukan polling dengan laju yang masuk akal, bukan menebak-nebak.
  • Periksa semua yang datang dari caller luar sebagai input yang tidak tepercaya, meskipun caller itu memegang seat runner.
  • Pisahkan note dan run: penulis note tidak boleh bisa membangunkan run, dan hasil baca balik harus mengecualikan analisis internal session.

Cara run diantrekan per session ada di halaman agent task queue, dan cara lain memulai session ada di agent triggers.

Top comments (0)