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"]}]}
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']
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:
- Token.
request_session_runmendeklarasikanosfoundry:run. Halaman login menampilkan scope itu sebagai "Run sessions where you grant a runner role". - 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 berisiwrite, dan Runner kalau berisirun. - 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
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_MSbernilai 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 mengikutiIDLE_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
runtimedengantask_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_typehanya mengembalikanOperatoruntukmanual, 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
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
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
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
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)