osFoundry के MCP server में यही एक tool है जो किसी इंसान के कुछ भी click किए बिना काम शुरू कर देता है। tools/list इसे हर client को ऐसे ही लौटाता है, चाहे वह sign in हो या नहीं:
{"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"]}]} इस description के दो phrase तय करते हैं कि client इसके साथ क्या-क्या करेगा। "Invited runner" एक permission check है जो तीन जगह होता है। "When available" का मतलब है कि call काम शुरू होने से पहले ही लौट आती है, इसलिए client को result लेने दोबारा आना पड़ता है। यह post दोनों को समझाती है, साथ में वह mailbox भी जिससे Codex बाद में session से बात करता है। Client side का एक Python rebuild इसी Mac पर चलाया गया है।
Sign in से पहले client क्या-क्या जान सकता है?
काफ़ी कुछ। initialize, tools/list और protected-resource metadata, तीनों बिना 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'] यह Japan से Apple M1 पर मापा गया है, इसलिए milliseconds जितना server के बारे में बताते हैं, उतना ही network route के बारे में भी। osfoundry:run अपने अलग scope के रूप में publish है, osfoundry:write से अलग। जो Codex config इसे scopes में नहीं रखता, उसे कभी ऐसा token नहीं मिलता जो run queue कर सके, बाद में उसे कोई भी seat मिले।
Runner loop server के 59 tools में से पाँच इस्तेमाल करता है: read_session_state, request_session_run, read_session_task_result, post_session_note और read_session_mail। ये list के 10,197 tokens में से 821 हैं (8.1%, cl100k_base से गिने गए, इसलिए Codex के अपने tokenizer के लिए यह अंदाज़ा भर है)। enabled_tools में सिर्फ़ इन्हीं को रखने से बाकी 54 tools prompt से बाहर रहते हैं।
कौन तय करता है कि Codex runner है?
तीन अलग-अलग पक्ष, और एक call को तीनों चाहिए:
- Token.
request_session_runosfoundry:rundeclare करता है। Sign-in page पर यह scope "Run sessions where you grant a runner role" के रूप में दिखता है। - Grant. osFoundry के web client (
ExternalAssistantsPanel.tsx) में session grant के साथ actions होते हैं, जिनके labels हैं View, Reply, Edit और "Start work"। Grant का role dropdown Contributor तभी दिखाता है जब grant मेंwriteहो, और Runner तभी जब उसमेंrunहो। - Seat. Owner हर session के लिए एक role save करता है, जिसे client
POST /external-assistants/{connection}/sessions/{session_type}/{session_id}के रूप में{"role": "observer" | "contributor" | "runner"}के साथ भेजता है। Dropdown इन्हें Viewer, Contributor और Runner के रूप में दिखाता है।
Script इसी check को दोबारा बनाती है। Role से tool की mapping panel के help text से ली गई है ("Contributor to post notes and propose handoffs, or Runner to also request runs"), और हर tool का scope live schemas से। osFoundry का server code public नहीं है, इसलिए यह rule का reconstruction है, server के असली refusals की capture नहीं:
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 Client लिखने वाले के लिए यह फ़र्क मायने रखता है। request_session_run पर refusal के दो अलग fix हैं: scope के साथ दोबारा login करना (आपकी तरफ़), या owner से seat माँगना (उनकी तरफ़)। Retry करने की बजाय बताइए कि कौन-सा case है।
Call और result के बीच क्या होता है?
Call एक task queue करती है और लौट आती है। Web client के types बताते हैं कि यह task क्या है: task_type: "external_assistant" वाला एक AgentTask, जिसे API का task-creation route reject करता है, क्योंकि इसे सिर्फ़ server arm करता है। इसका status pending, running, input_required, completed, failed या cancelled से गुज़रता है। Session के Trigger tab में इस पर "Requested by an outside assistant" label होता है।
इसे कौन उठाता है, यह इस पर निर्भर है कि session कहाँ चल रहा है। Owner के desktop app वाले session के लिए agent_session_runtime.rs timing साफ़ लिखता है:
-
DEFAULT_POLL_INTERVAL_MS15,000 है। Due task अगले poll पर lease के साथ claim होता है। - चार खाली polls के बाद (
IDLE_CYCLES_BEFORE_BACKOFF), intervalIDLE_BACKOFF_LADDER_MSपर चढ़ता है: 30 s, 60 s, फिर 300 s। File का एक comment इसकी कीमत बताता है: server-armed trigger जो "arriving at a fully idle desktop waits up to five minutes" (पूरी तरह idle desktop पर पहुँचकर पाँच मिनट तक इंतज़ार करता है)। App में कोई इंसान कुछ भी करे, loop लगभग एक second में जाग जाता है। - जिस session में "already has a run in flight" हो, वह अपने due tasks टाल देता है। एक session के runs आपस में overlap नहीं होते।
- Run ख़त्म होने पर outcome
runtimeकी तरफ़ सेtask_idके साथ session mail में लिखा जाता है। Subject बताता है कि run ठीक से पूरा हुआ ("Task … result") या किसी ceiling पर रुक गया ("goal not fully met", "stopped at your token ceiling")। - Goal को untrusted input मानकर screen किया जाता है।
provenance_for_task_typeसिर्फ़manualके लिएOperatorलौटाता है, यानी वह run जो owner ने app में शुरू किया। बाकी हर type, यह भी, osGuard से होकर जाता है।
input_required को हर client में अलग से handle करना चाहिए। इसका मतलब है कि run ने osFoundry में किसी इंसान से सवाल पूछा है और जवाब पर अटका है। Run अभी भी चल रहा है। इंतज़ार करते रहिए, और अपने user को बताइए कि osFoundry की तरफ़ किसी को जवाब देना होगा।
Client को कितनी बार poll करना चाहिए?
Codex tool call के अंदर sleep नहीं कर सकता, और जो MCP tool call कई मिनट block रहे, वह client timeouts में फँसती है। इसलिए realistic तरीका read_session_task_result पर polling loop है। सवाल सिर्फ़ interval का है।
Script का part 4 इसे virtual clock पर simulate करता है: 2,000 runs, pick-up delay runtime के अपने intervals से, और run की लंबाई 90 s median वाले log-normal से। Run की लंबाई एक assumption है, इसलिए absolute numbers को सिर्फ़ उदाहरण समझिए। हर strategy गिनती है कि उसने कितनी calls कीं और run पूरा होने के कितनी देर बाद उसे पता चला:
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 इससे तीन बातें निकलती हैं।
यहाँ exponential backoff फ़ायदेमंद नहीं है। Polling के लिए यह आदतन पहली पसंद होता है, लेकिन ये runs घंटों नहीं, मिनटों के होते हैं। "15 s x2, cap 120 s" flat 30 s के मुकाबले एक-दो calls बचाता है, लेकिन median पर result 40 से 60 s देर से पकड़ता है।
Active runtime पर सबसे अच्छा trade-off यह है कि पहला इंतज़ार run की आम लंबाई के आसपास रखा जाए: median पर चार calls, और result 8 s के अंदर पकड़ में। Run पूरा हो सकने से पहले की हर call बेकार है।
Client की strategy से ज़्यादा runtime की अपनी स्थिति मायने रखती है। जो desktop idle हो चुका है, उस पर median run 152 s तक pending में रहता है जब तक कोई उसे claim न करे, और कोई भी polling interval इसे नहीं बदलता। अगर आप read_session_state देख रहे हैं और कुछ मिनट बाद भी task pending है, तो यह runtime का idle backoff है, अटका हुआ run नहीं।
एक ही session पर लगातार queue किए गए runs एक के पीछे एक लाइन में लगते हैं:
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 अगर Codex तीन goals queue करे और तीनों को 5 s timer पर poll करे, तो उनमें से दो कई मिनट तक pending लौटाते रहते हैं, और इसकी वजह सिर्फ़ queue है।
Note से run क्यों शुरू नहीं हो सकता?
post_session_note को सिर्फ़ osfoundry:write और Contributor seat चाहिए। अगर note session के agents को run के लिए जगा सकता, तो हर Contributor असल में दूसरे नाम से Runner होता। Description इसे साफ़ मना करता है: "Other agents can reply to the returned external sender slug; this does not trigger an automatic run."
तो note एक message है, trigger नहीं। यह connection के display name के साथ session mail में पहुँचता है। Web client Team mail में external_sender_name और "outside assistant" label दिखाता है। Agents अगली बार चलने पर इसका जवाब दे सकते हैं।
Mailbox क्या लौटाता है?
read_session_mail का description है "bounded notes addressed to this connection and notes it posted", साथ में "Internal agent analysis is excluded."। Schema एक page को limit 1 से 20 तक सीमित करता है और before_id से पीछे जाता है।
Script एक व्यस्त team session के mail को model करती है, web client के जाने-पहचाने message types (analysis, tool_call, tool_result, agent_note, broadcast) की 409 rows, और बताया गया filter लगाती है:
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 Row counts model के हैं। मायने shape का है: connection को दिखने वाला हिस्सा छोटा है, इसलिए limit: 20 आमतौर पर एक या दो calls में सब ले आता है। Run का outcome भी इस हिस्से में नहीं है। Desktop runtime के code में outcome mail बिना recipient के post होता है, इसलिए वह connection को addressed नहीं होता। Results read_session_task_result से पढ़िए और बातचीत read_session_mail से, और यह उम्मीद मत रखिए कि एक दूसरे की जगह ले लेगा।
Schemas client को कहाँ रोकते हैं?
Live inputSchemas के ख़िलाफ़ तेरह cases, jsonschema (Draft 2020-12) से validate किए गए:
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 इनमें से दो पर आसानी से ठोकर लगती है। Run में context field नहीं है, इसलिए background goal के अंदर ही जाता है, और 10,000 characters की limit दोनों पर मिलाकर लागू होती है। Note में to नहीं है, इसलिए आप उसे किसी एक agent को address नहीं कर सकते। हर note session को जाता है, और agents connection के sender slug पर जवाब देते हैं।
पूरी script M1 पर 19.3 s wall-clock में चली, 40 network calls समेत। User CPU time 0.47 s था, यानी simulation ख़ुद नगण्य है।
अगर आप इसे ख़ुद wire करें
- "start work" को अपने अलग OAuth scope के रूप में publish करें, और role को token के साथ grant पर भी gate करें, ताकि न caller और न कोई पुराना grant ख़ुद को runner बना सके।
- तुरंत task id लौटाएँ, और caller को status पढ़ने का सस्ता तरीका दें। MCP tool call को agent run का इंतज़ार न करवाएँ।
- Runtime का असली pick-up behaviour (poll interval, हर session में एक run) docs या status में दिखाएँ, ताकि clients अंदाज़ा लगाने की बजाय समझदारी वाली rate पर poll करें।
- बाहर के caller से आई हर चीज़ को untrusted input मानकर screen करें, चाहे caller के पास runner seat हो।
- Notes और runs को अलग रखें: note लिखने वाला run को जगा न सके, और read-back में session का internal analysis शामिल न हो।
हर session में runs कैसे line में लगते हैं, यह agent task queue page पर है, और session शुरू होने के बाकी तरीके agent triggers में हैं।

