यह वह tool है जिसे Claude chat osFoundry में Graph शुरू करने के लिए कॉल करता है, जैसा कि public MCP endpoint इसे लिस्ट करता है। 2026-11-08 को बिना authentication वाले tools/list से निकाला गया और छोटा किया गया:

{"name": "run_graph_session", "description": "Queue a Graph run on the owner's online desktop in a connected project. Retrieve progress and result with get_graph_run. The desktop can go offline, and local tool approvals still apply.", "inputSchema": { "properties": { "graph_session_id": {"type": "string"}, "arguments": {"type": "object"}, "idempotency_key": {"type": "string", "minLength": 8, "maxLength": 128}}, "required": ["graph_session_id", "idempotency_key"], "additionalProperties": false}, "annotations": {"readOnlyHint": false, "destructiveHint": true}, "securitySchemes": [{"type": "oauth2", "scopes": ["osfoundry:run"]}]} 

इसके तीन sibling tools हैं। list_graph_sessions (project_id, limit 1–50) और get_graph_run (command_id) osfoundry:read के तहत read-only हैं। cancel_graph_run (command_id) start call की तरह osfoundry:run के तहत है। एक connected account से दो असली responses, IDs हटाकर:

list_graph_sessions {"project_id": "…"} → {"project_id": "…", "sessions": []} get_graph_run {"command_id": "<unknown>"} → error: Graph run not found or access removed 

खाली list default है। जब तक owner desktop से कोई session share नहीं करता, वहाँ कुछ नहीं दिखता। description में verb "Queue" है: Graph शुरू होने से पहले ही tool जवाब दे देता है। इस post का बाकी हिस्सा इसी बारे में है कि ऐसा क्यों है, और दूसरी तरफ़ बैठे agent को इसकी क्या कीमत चुकानी पड़ती है।

Graph session क्या है, और server इसे सीधे क्यों नहीं चला सकता?

Graph एक saved JavaScript program है जिसे desktop का fleet engine चलाता है: conditional paths, parallel agents, सीमित retries, दूसरे Graphs को calls। apps/web/src-tauri/src/graph_config.rs में run_graph_now वह Tauri command है जिसे Graph screen इस्तेमाल करती है। यह script को run_fleet_script को सौंपता है, एक hard token ceiling के साथ, जो पहुँचते ही agents की अगली wave को मना कर देती है। Graph session किसी एक device पर एक saved Graph की स्थायी पहचान है, gs_ के बाद 32 hex characters, local graph_sessions table में।

Engine, script से spawn होने वाले agents, local models, folders और हर tool की approval settings, सब desktop process में रहते हैं। Server-side runner को इन सबकी copies चाहिए होंगी। Feature index साफ़ कहता है: Graphs पढ़ना browser में काम करता है, चलाने के लिए desktop चाहिए।

जिस desktop से कोई connect ही नहीं कर सकता, वहाँ request पहुँचती कैसे है?

पहुँचती ही नहीं। Desktop खुद काम माँगता है। apps/web/src/hooks/useConnectedGraphRelay.ts, जो desktop की main window में एक बार mount होता है, यह करता है:

  1. हर 20 सेकंड पर (और sharing बदलते ही तुरंत), अगर पिछला pass अभी busy है तो skip करता है।
  2. Local outbox की वे completions दोबारा भेजता है जो पिछली बार server तक नहीं पहुँचीं।
  3. Owner ने जो भी project enable किया है, हर एक के लिए POST /external-assistants/graph-relays/sync भेजता है, उस project के लिए tick की गई sessions के metadata के साथ, ज़्यादा से ज़्यादा 100: name, description, slug, version, status। Response में उस project के लिए queue हुए commands आते हैं।
  4. हर command के लिए दोबारा जाँचता है कि project अब भी enabled है, session अब भी ticked है, और उसका slug वही है। अगर नहीं, तो उसे cancelled के रूप में पूरा करता है, "Graph session is no longer shared with this project" के साथ।
  5. वरना उसे channel graph:external:<command id> पर run_graph_now से चलाता है, cancellation के लिए 3 सेकंड के control poll के साथ, और फिर bounded result post करता है।

तो list_graph_sessions वही दिखाता है जो पिछले sync ने upload किया था, और run_graph_session एक row लिखता है जिसे desktop अपने अगले pass में ढूँढ लेगा। Command IDs UUIDs हैं। ConnectedGraphProjectBinding बाकी सब कुछ reject कर देता है, उससे पहले कि वह channel-to-project row लिखे जो run को connected project के तहत दर्ज करती है।

दोबारा बनाया गया setup

इस Mac पर कोई Claude binary नहीं चलती, इसलिए sim.py simulated clock पर तीनों रोल निभाता है। Desktop वाला हिस्सा ऊपर वाले relay का port है: 20 s poll, busy होने पर skip, serial commands, चलाने से पहले re-check, 3 s control poll, outbox, result की 100,000-character सीमा। Server वाला हिस्सा एक stand-in है, क्योंकि उसका code public नहीं है। यह वही implement करता है जो tool descriptions कहती हैं: एक queue, ऐसे results जिन्हें सिर्फ़ run शुरू करने वाला agent पढ़ सके, queued और active के लिए अलग cancel। यह वह idempotency behaviour भी implement करता है जो osFoundry की CLI guide माँगती है ("reuse idempotency keys after a lost response")। Client एक scripted Claude है।

Graph run times synthetic हैं: lognormal, median 60 s, कम से कम 5 s। हर experiment 500–2,000 seeded runs इस्तेमाल करता है, और relay का phase request के हिसाब से random है। Apple M1 पर Python 3.9.6, सब कुछ मिलाकर 2.8 s wall time। Output code/run.out में है।

एक run queue में कितनी देर बैठता है?

pickup wait (queued->started), n=2000: mean 9.8 s, median 9.8 s, p95 18.9 s, max 20.0 s 

ज़्यादा से ज़्यादा एक poll interval, औसतन आधा। यह सिर्फ़ outbound connections करने वाले desktop की कीमत है। इसमें न कोई खुला port है, न कोई long-lived socket, और बंद laptop बस एक ऐसा relay है जिसने अभी poll नहीं किया।

दूसरी कीमत कम दिखती है। Relay commands एक के बाद एक चलाता है और busy रहते हुए अपना timer skip करता है। एक साथ queue किए गए पाँच runs क्रम से पूरे होते हैं:

एक साथ queue किए गए runs पहला result (median) आख़िरी result (median) आख़िरी result (p95)
1 74 s 74 s 254 s
3 73 s 233 s 483 s
5 75 s 399 s 776 s

जो agent समय बचाने के लिए पाँच Graph runs एक साथ fan out करता है, उसे सबसे धीमा schedule मिलता है। एक desktop मतलब एक worker। अगर आपको parallelism चाहिए, तो उसे Graph के अंदर रखिए, जहाँ engine के पास इसके लिए max_concurrency और token ceiling है।

Agent को get_graph_run कितनी बार कॉल करना चाहिए?

Claude तक वापस आने का कोई push channel भी नहीं है। हर strategy calls की संख्या और इस बात के बीच trade-off करती है कि agent को result कितनी देर से पता चलता है। हर row में 2,000 runs:

Client poll strategy प्रति run calls (mean) p95 Result देर से पता चलना (mean) p95
हर 2 s 47.1 118 1.0 s 1.9 s
हर 5 s 19.1 48 2.5 s 4.8 s
हर 15 s 6.7 16 7.5 s 14.2 s
हर 30 s 3.6 8 14.9 s 28.4 s
backoff 2, 4, 8 … 30 s 6.6 11 14.8 s 28.4 s
20 s रुकें, फिर हर 10 s 8.8 23 5.0 s 9.6 s

हर 2 s पर poll करने में 47 tool calls लगती हैं, यानी model के context से 47 बार गुज़रना, सिर्फ़ उस job पर कुछ सेकंड बचाने के लिए जो एक मिनट लेता है। Exponential backoff चालाक लगता है पर यहाँ हार जाता है: यह fixed 15 s poll जितनी ही calls करता है और result दोगुनी देर से पकड़ता है, क्योंकि typical Graph के ख़त्म होने तक यह अपनी 30 s cap पर पहुँच चुका होता है। आख़िरी row वह इस्तेमाल करती है जो relay आपको बताता है। पहले poll interval में वैसे भी कुछ शुरू नहीं हो सकता, इसलिए पहली जाँच से पहले लगभग एक interval रुकने की लगभग कोई कीमत नहीं है।

Cancel असल में क्या रोकता है?

cancel at a random 0-40 s after queueing, n=2000: still queued 523, already running 1370 queued : terminal after 0.0 s (graph never started) running: terminal after mean 2.0 s, p95 3.3 s, max 3.5 s 

Queued run का cancel server पर बस एक state change है। Desktop को वह command कभी दिखता ही नहीं और कोई token ख़र्च नहीं होता। Running run का cancel एक flag है जिसे desktop अपने 3 सेकंड वाले control poll पर पढ़ता है, और फिर fleet_started event से मिले run ID पर fleet_cancel_run कॉल करता है। Relay वह cancel एक ही बार भेजता है और outcome को cancelled रखता है, भले ही Graph flag और stop के बीच पूरा हो जाए।

Idempotency key से क्या मिलता है?

idempotency_key ज़रूरी है, 8–128 characters। हर repetition में 200 runs, जिनमें 30% replies drop की गईं ताकि client retry करे:

Retry behaviour भेजी गई calls Queue हुए Graph runs
retry पर नई key 266 / 302 / 286 266 / 302 / 286
retry पर वही key 270 / 291 / 275 200 / 200 / 200

जो Graph emails draft करता है या files लिखता है, उसके लिए duplicate सिर्फ़ एक बर्बाद मिनट नहीं, बल्कि side effects का दूसरा सेट है। इसीलिए schema में key ज़रूरी है और tool पर destructiveHint: true लगा है।

अगर desktop offline हो जाए तो?

desktop offline 600 s: statuses ['queued'], executed 0 back online: first start +3 s, all 5 ['completed'] by +131 s, executed 5 completion POST lost once, outbox=on : status completed, delivered 40 s after start completion POST lost once, outbox=off: status running, never delivered 

Queue हुआ काम offline desktop के बावजूद बचा रहता है, क्योंकि queue server पर है। कमज़ोर कड़ी वापसी का रास्ता है। अगर desktop ने Graph पूरा कर लिया और उसका POST खो गया, तो जो relay result सिर्फ़ memory में रखता, वह run को हमेशा के लिए running पर अटका छोड़ देता। असली relay हर completion को post करने से पहले localStorage में लिखता है और 2xx मिलने के बाद ही हटाता है, इसलिए 20 s बाद वाला अगला pass उसे deliver कर देता है।

Result कितना बड़ा हो सकता है?

Relay {execution_id, status, result, total_tokens, reported_cost} post करता है, अगर उसका JSON.stringify ज़्यादा से ज़्यादा 100,000 characters का हो। वरना वह {execution_id, status, result_truncated: true} post करता है। Errors 2,000 characters पर काट दिए जाते हैं। सीमा characters गिनती है, bytes नहीं:

result text that still fits (ascii): 99,899 chars = 99,899 UTF-8 bytes result text that still fits (japanese): 99,899 chars = 299,697 UTF-8 bytes 

कटौती all-or-nothing है। ग़लत पढ़ने के लिए कोई आधा result नहीं होता, और पूरा run record उसी execution_id के तहत desktop की history में रहता है। जो भी हो, model के सामने रखने के लिए यह बहुत सारा text है, इसलिए जिस Graph को agent कॉल करेगा, उसे dump से नहीं, summary से ख़त्म होना चाहिए।

Run और कौन देख सकता है?

another agent reads the run: {'error': 'Graph run not found or access removed'} 

Stand-in में, Claude का command_id माँगने वाले दूसरे connection को वही error मिलता है जो live server ने एक मनगढ़ंत ID के लिए लौटाया था। get_graph_run और cancel_graph_run दोनों के description में लिखा है कि वे उन runs पर काम करते हैं जो "started by this Connected agent" हैं। "आपका नहीं है" और "मौजूद नहीं है" में फ़र्क न करने का मतलब है कि leak हुआ ID कुछ भी confirm नहीं करता।

अगर आप इसे ख़ुद wire up करें

  • जिस machine के पास tools हैं, उसे ही काम pull करने दीजिए। Outbound poll NAT, sleep और बंद होते laptops के बावजूद चलता रहता है। Interval यह ध्यान में रखकर चुनिए कि हर caller औसतन उसका आधा इंतज़ार करता है।
  • Start call से एक ticket लौटाइए, और tool description में बताइए कि result कौन-सा tool पढ़ता है। Model नाम से queue का अंदाज़ा नहीं लगा सकता।
  • पहली status check लगभग एक pickup interval बाद रखिए। जब job की अवधि एक मिनट के आसपास रहती है, तब fixed polling exponential backoff से बेहतर है।
  • Start call पर idempotency key ज़रूरी कीजिए। Side effects वाले jobs में, बिना key के retry की गई request एक दूसरा run है।
  • Completions भेजने से पहले उन्हें worker पर persist कीजिए। Round trip में result ही वह हिस्सा है जिसे आप सस्ते में दोबारा compute नहीं कर सकते।
  • Consent worker पर चलाने से ठीक पहले दोबारा जाँचिए, सिर्फ़ job queue होते समय नहीं। बीच में sharing बदल सकती है।

नीचे वाले protocol की osFoundry की छोटी परिभाषा उसकी MCP glossary entry में है, और Graphs जिस बड़े pattern का हिस्सा हैं, वह agent orchestration entry में है।