[{"data":1,"prerenderedAt":11766},["ShallowReactive",2],{"docs-nav":3,"docs-article-engineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime\u002Fexecution":797},[4,17,27,44,55,67,75,82,94,106,114,122,129,135,144,153,161,169,177,189,202,211,218,229,240,248,260,268,276,286,296,305,314,323,331,337,343,350,356,364,371,378,383,393,401,410,415,422,432,439,444,451,458,462,467,475,487,499,509,516,525,533,539,545,551,557,563,567,579,593,603,614,621,626,633,640,646,653,658,665,673,678,686,692,699,704,710,721,730,740,747,753,761,767,776,782,791],{"path":5,"title":6,"description":7,"group":8,"section":6,"order":9,"tags":10,"lastUpdated":16},"\u002Fagents\u002Fagentic-crm","Agentic CRM","Research brief and build plan for an AgencyCore agentic CRM layer, rendered as an interactive page — the core operating loop, the target architecture, the typed-tool risk gateway, the proposed-actions review queue, and the four-slice MVP.","Agents",0,[11,12,13,14,15],"crm","agents","ai","architecture","research","2026-06-12",{"path":18,"title":19,"description":20,"group":8,"section":21,"order":22,"tags":23,"lastUpdated":26},"\u002Fagents\u002Fchat","Chat agent","High-level system design of the AgencyCore chat agent — core components, data flow, and the two abstractions that hold it together.","Reference",1,[12,14,24,25],"chat","system-design","2026-05-13",{"path":28,"title":29,"description":30,"group":8,"section":31,"order":32,"tags":33,"lastUpdated":43},"\u002Fagents\u002Fcompany-enrichment","Company Enrichment","The company enrichment workflow - a cache-first read in front of the company intelligence database that fills firmographic, contact and technographic facts via a fixed-order provider waterfall, and writes every resolved fact back with provenance so the first org pays once and every later search rides free.","Enrichment",2,[12,34,35,36,37,38,39,40,41,42],"workflow","enrichment","companies","waterfall","cache","intelligence-database","firmographics","provenance","sonar","2026-06-10",{"path":45,"title":46,"description":47,"group":8,"section":48,"order":9,"tags":49,"lastUpdated":54},"\u002Fagents\u002Fcompany-sonar","Company Signals","Signal-first company discovery for marketing agencies, on the Claude Agent SDK, with a global intelligence cache and deterministic composite scoring.","Company Sonar",[12,34,42,50,51,52,35,53,14],"company-search","signals","agent-sdk","scoring","2026-06-08",{"path":56,"title":57,"description":58,"group":8,"section":48,"order":22,"tags":59,"lastUpdated":66},"\u002Fagents\u002Fcompany-sonar\u002Fsignal-monitoring","Company Signals Monitoring","Realtime signal capture layer on top of the data graph. Detects hot events, scores them with a Claude managed agent against each agency's ICP, fans out alerts.",[14,51,60,61,62,63,64,65],"intel","icp","alerts","monitoring","sse","managed-agents","2026-06-09",{"path":68,"title":69,"description":70,"group":8,"section":71,"order":22,"tags":72,"lastUpdated":74},"\u002Fagents\u002Fconcepts\u002Fchat-agent-design-principles","Designing chat agents","The 2026 playbook for production chat agents that reach into internal systems via tools — context engineering, memory, tool design, when to add complexity.","Concepts",[12,14,24,73],"context-engineering","2026-05-14",{"path":76,"title":77,"description":78,"group":8,"section":71,"order":32,"tags":79,"lastUpdated":74},"\u002Fagents\u002Fconcepts\u002Fsystem-prompt-architecture","System prompt architecture","How to structure a production chat agent system prompt — eight sections, what each one does, and the rules vendors converge on.",[12,80,81],"prompt-engineering","system-prompt",{"path":83,"title":84,"description":85,"group":8,"section":84,"order":9,"tags":86,"lastUpdated":54},"\u002Fagents\u002Fenvoy","Envoy","High-level system design for the AI outreach engine — the sequence step state machine, the human-in-the-loop draft approval gate, multi-source context enrichment, and the inbox sentiment flow, rendered as an interactive page.",[12,87,88,89,90,91,92,93,14],"envoy","outreach","sales-engagement","sequences","state-machine","human-in-the-loop","nylas",{"path":95,"title":96,"description":97,"group":8,"section":98,"order":9,"tags":99,"lastUpdated":16},"\u002Fagents\u002Fheadhunter","Headhunter","The AI talent-search pipeline on one page - the production six-step design with its current-title relevance gate, and the 2.0 system design with internal-first waterfall sourcing, a pluggable source registry, automatic entity resolution, and a people intelligence graph that compounds every run.","General Search",[12,34,100,101,14,25,102,37,103,104,105],"headhunter","recruiting","multi-source","entity-resolution","people-intelligence","flywheel",{"path":107,"title":108,"description":109,"group":8,"section":21,"order":32,"tags":110,"lastUpdated":113},"\u002Fagents\u002Fpaperclip","Paperclip","Architecture deep dive into the Paperclip orchestration system.",[12,14,111,112],"orchestration","paperclip","2026-04-20",{"path":115,"title":116,"description":117,"group":8,"section":31,"order":22,"tags":118,"lastUpdated":16},"\u002Fagents\u002Fpeople-enrichment","People Enrichment","The people enrichment workflow - a cache-first read in front of the people intelligence database that fills profile, contact and employment facts via a fixed-order provider waterfall, keyed on the LinkedIn URL, and writes every resolved fact back with provenance so the first org pays once and every later search rides free. The fill step Headhunter and People Signals both call.",[12,34,35,119,37,38,39,120,41,100,121],"people","linkedin","people-sonar",{"path":123,"title":124,"description":125,"group":8,"section":126,"order":9,"tags":127,"lastUpdated":54},"\u002Fagents\u002Fpeople-sonar","People Signals","Signal-first people discovery for marketing agencies, built on the headhunter pipeline, with a composite score weighted by signal strength, source reputation, recency, and ICP fit.","People Sonar",[12,34,121,128,51,100,35,53,14],"people-search",{"path":130,"title":131,"description":132,"group":8,"section":126,"order":22,"tags":133,"lastUpdated":54},"\u002Fagents\u002Fpeople-sonar\u002Fpeople-signal-monitoring","People Signals Monitoring","Forward-looking design for the push layer that tracks known people - champions, past contacts, target-company decision-makers - and fires a warm lead the moment they change jobs, get promoted, or their company has an event.",[14,51,60,119,63,134],"warm-leads",{"path":136,"title":137,"description":138,"group":139,"section":140,"order":22,"tags":141,"lastUpdated":143},"\u002Fengineering\u002Fguides\u002Fagent-execution-stack","The Agent Execution Stack","Durable workflows over pluggable agent backends — how AgencyCore runs AI agents on Inngest over a webhook-driven Claude Managed Agents backend.","Engineering","Guides",[12,142,14,25],"inngest","2026-06-25",{"path":145,"title":146,"description":147,"group":139,"section":140,"order":9,"tags":148,"lastUpdated":143},"\u002Fengineering\u002Fguides\u002Fagent-runtime","Agent runtime","How AgencyCore runs AI agents on a provider-neutral runtime — the abstraction layer that lets us swap the agent backend, with Claude managed agents as the current provider.",[12,149,14,150,151,152,25],"runtime","anthropic","claude","providers",{"path":154,"title":155,"description":156,"group":139,"section":21,"order":157,"tags":158,"lastUpdated":160},"\u002Fengineering\u002Freference\u002Fagno-to-agent-sdk-migration","Agno → Claude Agent SDK migration","System-design spec for moving the ac-python-api workflow engine off Agno onto Anthropic's Claude Agent SDK \u002F Managed Agents, tiered by control-flow shape.",10,[12,14,159,52,65],"migration","2026-06-06",{"path":162,"title":163,"description":164,"group":139,"section":21,"order":22,"tags":165,"lastUpdated":54},"\u002Fengineering\u002Freference\u002Fcloudflare-agent-sandbox","Cloudflare agent sandbox","Cloudflare's Workers-based agent platform, evaluated as an alternative sandbox for our Agno workflows.",[12,166,167,168,159],"sandbox","cloudflare","workers",{"path":170,"title":171,"description":172,"group":139,"section":21,"order":32,"tags":173,"lastUpdated":176},"\u002Fengineering\u002Freference\u002Fvirtual-filesystem-rag","Virtual filesystem for AI assistants","How ChromaFs provides AI agents with structured file access.",[12,174,14,175],"rag","chromafs","2026-04-18",{"path":178,"title":179,"description":180,"group":139,"section":181,"order":182,"tags":183,"lastUpdated":188},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fcapabilities\u002Fstate-and-knowledge","State and knowledge","What a run may know. One deterministic context builder over application state, knowledge and memory, one owner for every fact, and memory that is written through a tool.","Agentic platform",11,[184,185,186,11,187],"context","memory","knowledge","pgvector","2026-08-31",{"path":190,"title":191,"description":192,"group":139,"section":181,"order":157,"tags":193,"lastUpdated":201},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fcapabilities\u002Ftools-and-integrations","Tools and integrations","A tool is the one way an agent reaches the world. AgencyCore owns the model facing contract, the invoke path, the credentials and the result boundary.",[194,195,196,197,198,199,200],"tools","integrations","mcp","agno","policy","security","idempotency","2026-09-04",{"path":203,"title":204,"description":205,"group":139,"section":181,"order":22,"tags":206,"lastUpdated":210},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fcontract","Platform contract","One platform behind chat, interactive channels, triggers, approvals and background runs, with one Agno runtime, one tool layer, one state layer, and three cross-cutting planes.",[12,14,197,142,194,207,149,208,198,209],"skills","channels","observability","2026-09-02",{"path":212,"title":181,"description":213,"group":139,"section":214,"order":22,"tags":215,"lastUpdated":201},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform","The whole agentic platform on one page - who starts a run, the one boundary every run passes, how the work executes, and what comes back.","System design",[12,14,216,197,142,217,198],"overview","runs",{"path":219,"title":220,"description":221,"group":139,"section":181,"order":222,"tags":223,"lastUpdated":228},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Finterfaces\u002Fagent-access","Agent access (CLI and MCP)","How an outside AI agent reaches AgencyCore. The ac CLI works today as a user seat. An MCP server is planned and not designed.",6,[224,196,12,151,225,226,227],"cli","access","auth","todo","2026-08-18",{"path":230,"title":231,"description":232,"group":139,"section":181,"order":233,"tags":234,"lastUpdated":239},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Finterfaces\u002Fchannel-gateway","Channel gateway","The only layer that knows both an interactive channel and the platform. One message shape converges inbound, one intent shape diverges outbound, and no model call happens here.",3,[208,235,236,237,238,199],"slack","web","identity","sessions","2026-08-30",{"path":241,"title":242,"description":243,"group":139,"section":181,"order":244,"tags":245,"lastUpdated":210},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Finterfaces\u002Ffront-door","Front door","The conversational control layer. It turns a request into one structured decision, then deterministic application code answers or hands work to RunManager.",4,[246,247,197,184,198,217],"front-door","routing",{"path":249,"title":250,"description":251,"group":139,"section":181,"order":32,"tags":252,"lastUpdated":259},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Finterfaces\u002Fsurfaces","Surfaces","Every product surface and its API contract. Web chat goes through the gateway; every schema-native surface calls the domain API.",[253,254,24,255,256,257,258,217,64],"surfaces","api","approvals","prospects","saved-searches","builder","2026-09-03",{"path":261,"title":262,"description":263,"group":139,"section":181,"order":264,"tags":265,"lastUpdated":188},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Finterfaces\u002Ftriggers","Triggers","A Run with no person. Every producer emits one Event, matching is deterministic, and dispatch reuses RunManager, Policy and Inngest.",5,[266,267,142,200],"triggers","events",{"path":269,"title":270,"description":271,"group":139,"section":181,"order":272,"tags":273,"lastUpdated":259},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fplanes\u002Fidempotency","Idempotency","One durable PostgreSQL key service prevents duplicate effects and freezes mutable input before selected Run starts. A Run start is guarded by a unique index on the Run row.",14,[200,217,194,274,275],"webhooks","reliability",{"path":277,"title":278,"description":279,"group":139,"section":181,"order":280,"tags":281,"lastUpdated":285},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fplanes\u002Fobservability-and-operations","Observability and operations","One run row, one span tree and one usage meter. Sentry reports system failure; AgencyCore spans explain what the agent did.",13,[209,217,282,283,64,284],"spans","usage","sentry","2026-08-26",{"path":287,"title":288,"description":289,"group":139,"section":181,"order":290,"tags":291,"lastUpdated":295},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fplanes\u002Fpolicy-and-governance","Policy and governance","One deterministic plane answers may this happen, at three checkpoints, with one grant model, one approval model and one decision log.",12,[198,292,255,293,294],"permissions","limits","governance","2026-08-25",{"path":297,"title":6,"description":298,"group":139,"section":299,"order":22,"tags":300,"lastUpdated":210},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fproducts\u002Fagentic-crm","The AgencyCore CRM loop for turning signals and discovery into qualified organization prospects, CRM relationships and outreach.","Agentic products",[11,301,51,302,256,35,303,304,87],"lead-generation","intelligence","signals-search","email-sequence",{"path":306,"title":307,"description":308,"group":139,"section":299,"order":264,"tags":309,"lastUpdated":188},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fproducts\u002Fbuilder-chat","Front door builder chat","Conversational authoring for organization-specific Agent and Workflow definitions, entered through the normal Front Door and backed by the existing DefinitionService.",[310,311,246,12,312,313,198],"authoring","definitions","workflows","templates",{"path":315,"title":316,"description":317,"group":139,"section":181,"order":318,"tags":319,"lastUpdated":201},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fproducts\u002Fcapability-contracts","Company, People and Signals contracts","The five Phase 7 product capabilities, their bounded inputs, stable references, permissions and results.",21,[320,321,119,51,322],"capabilities","company","contracts",{"path":324,"title":325,"description":326,"group":139,"section":181,"order":327,"tags":328,"lastUpdated":330},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fproducts\u002Fcapability-scenarios","Capability design scenarios","Normal, failure and recovery cases for the Phase 7 capability contracts, with implementation owners.",22,[320,329,321,119,51],"validation","2026-09-05",{"path":332,"title":333,"description":334,"group":139,"section":299,"order":233,"tags":335,"lastUpdated":210},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fproducts\u002Femail-sequence","Email sequence workflow","Envoy durable outreach for one or many people, with fresh context, approvals, reply waits, follow-ups and Nylas transport.",[336,87,34,142,93,255],"email",{"path":338,"title":339,"description":340,"group":139,"section":299,"order":244,"tags":341,"lastUpdated":188},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fproducts\u002Fgeneral-chat","Front door general chat","The default conversational answer path for AgencyCore. It answers from supplied context, cites what it used, asks when context is insufficient, and delegates real work through the normal Front Door.",[24,246,186,184,247,342],"citations",{"path":344,"title":345,"description":346,"group":139,"section":299,"order":222,"tags":347,"lastUpdated":188},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fproducts\u002Fhuman-review","Human review inbox","One product page for every agentic action that is paused because a person must authorize an exact proposal. It is a view over the shared approval primitive, not a second review system.",[348,255,349,198,12],"human-review","inbox",{"path":351,"title":352,"description":353,"group":139,"section":299,"order":32,"tags":354,"lastUpdated":259},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fproducts\u002Fsignals-search","Signals Search","One bounded discovery workflow that finds companies, verifies signals, finds relevant people, and produces evidence-backed organization prospects without prematurely creating CRM records.",[303,355,36,119,51,302,256,11,35],"discovery",{"path":357,"title":358,"description":359,"group":139,"section":299,"order":360,"tags":361,"lastUpdated":188},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fproducts\u002Fworkflow-visualizer","Workflow visualizer","One constrained workflow graph, reused to author a draft, read a published definition, and watch a Run. Build mode edits the draft; run mode overlays Run and span state on the frozen snapshot.",7,[312,362,258,311,217,282,363,255],"visualizer","graph",{"path":365,"title":366,"description":367,"group":139,"section":181,"order":368,"tags":369,"lastUpdated":201},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime\u002Fdefinitions","Runtime definitions","Editable drafts, one published configuration per definition, template forks, deterministic validation, and the Run snapshot that keeps in flight work stable.",8,[149,311,329,370],"publishing",{"path":372,"title":373,"description":374,"group":139,"section":181,"order":375,"tags":376,"lastUpdated":259},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime\u002Fexecution","Runtime execution","The Run record, the Inngest step boundaries, agent segments, workflow nodes, approvals, cancellation, failure handling and live events.",9,[149,217,197,142,255,377,64],"cancellation",{"path":379,"title":380,"description":381,"group":139,"section":181,"order":360,"tags":382,"lastUpdated":285},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime","Agentic runtime","One Run contract, one Agno agent runtime, one deterministic workflow model, and the component boundaries that keep the framework replaceable.",[149,217,197,312,207,142],{"path":384,"title":385,"description":386,"group":139,"section":387,"order":244,"tags":388,"lastUpdated":392},"\u002Fengineering\u002Fsystem-design\u002Fmission-control\u002Fcompany-context","Company context","L3. Company state, knowledge and memory are three different things. One deterministic builder turns them into one brief.","Mission Control",[389,390,186,185,184,391,11],"mission-control","company-state","retrieval","2026-08-12",{"path":394,"title":395,"description":396,"group":139,"section":387,"order":22,"tags":397,"lastUpdated":392},"\u002Fengineering\u002Fsystem-design\u002Fmission-control\u002Fexperience","Experience","L6. Where a person observes and controls the company, and the one rule that keeps the UI out of the business.",[389,398,399,255,400],"ui","control-plane","activity",{"path":402,"title":403,"description":404,"group":139,"section":387,"order":222,"tags":405,"lastUpdated":392},"\u002Fengineering\u002Fsystem-design\u002Fmission-control\u002Ffoundation","Foundation","L1. Generic infrastructure with no business logic in it. The test is that another product could run on it unchanged.",[389,406,407,408,267,409,226,209],"infrastructure","database","queue","storage",{"path":411,"title":387,"description":412,"group":139,"section":214,"order":233,"tags":413,"lastUpdated":392},"\u002Fengineering\u002Fsystem-design\u002Fmission-control","The internal Company OS. Six layers and one policy plane put a person in control of company state and of autonomous execution.",[389,414,14,12,312,198,399],"company-os",{"path":416,"title":417,"description":418,"group":139,"section":387,"order":32,"tags":419,"lastUpdated":392},"\u002Fengineering\u002Fsystem-design\u002Fmission-control\u002Fintelligence","Intelligence","L5. The agent is the primitive. A skill is how it works, a tool is how it reaches the world, and the two are never the same thing.",[389,12,207,420,421],"planning","reasoning",{"path":423,"title":424,"description":425,"group":139,"section":387,"order":368,"tags":426,"lastUpdated":392},"\u002Fengineering\u002Fsystem-design\u002Fmission-control\u002Fmetrics-and-connectors","Metrics and connectors","A worked example across every layer. Three vendors, one metric pipeline, three views, and the rule that decides what we store.",[389,427,195,428,429,284,430,431],"metrics","stripe","posthog","ingest","dashboards",{"path":433,"title":434,"description":435,"group":139,"section":387,"order":233,"tags":436,"lastUpdated":392},"\u002Fengineering\u002Fsystem-design\u002Fmission-control\u002Forchestration","Orchestration","L4. Workflow, run, step, trigger and event. Five nouns that turn a decision into durable execution.",[389,312,217,266,267,437,438],"durability","retry",{"path":440,"title":288,"description":441,"group":139,"section":387,"order":360,"tags":442,"lastUpdated":392},"\u002Fengineering\u002Fsystem-design\u002Fmission-control\u002Fpolicy-and-governance","A plane, not a layer. One place decides what an agent may do, under what conditions, and how much. Human approval is one of its three answers.",[389,198,294,255,292,293,443],"audit",{"path":445,"title":191,"description":446,"group":139,"section":387,"order":264,"tags":447,"lastUpdated":392},"\u002Fengineering\u002Fsystem-design\u002Fmission-control\u002Ftools-and-integrations","L2. One contract for every capability. The tool is the only route to the world, and it is where policy, audit and tenancy meet.",[389,194,195,448,449,450],"adapters","registry","credentials",{"path":452,"title":453,"description":454,"group":139,"section":455,"order":22,"tags":456,"lastUpdated":210},"\u002Fengineering\u002Fsystem-design\u002Fworkflows\u002Fcompany-search","Company search","Implementation notes for company.search. Search resolves and gates company identities; enrichment is a separate capability.","Workflows",[321,457,142,42],"search",{"path":459,"title":31,"description":460,"group":139,"section":455,"order":233,"tags":461,"lastUpdated":210},"\u002Fengineering\u002Fsystem-design\u002Fworkflows\u002Fenrichment","Reusable company and people enrichment workflows with canonical Intelligence write-back, existing tier freshness and bounded asynchronous email.",[35,321,119,142],{"path":463,"title":464,"description":465,"group":139,"section":455,"order":32,"tags":466,"lastUpdated":259},"\u002Fengineering\u002Fsystem-design\u002Fworkflows\u002Fpeople-search","People search","Implementation notes for people.search. Bounded company scope and persona gates return selectable person identities without enrichment.",[119,457,142,100],{"path":468,"title":469,"description":470,"group":139,"section":455,"order":244,"tags":471,"lastUpdated":474},"\u002Fengineering\u002Fsystem-design\u002Fworkflows\u002Fsignals-search","Signals search","Superseded. The earlier on-demand buying-signal search component, kept as a record of the design that the agentic platform Signals Search workflow replaces.",[51,12,142,472,473],"intelligence-databases","superseded","2026-08-28",{"path":476,"title":477,"description":478,"group":479,"section":480,"order":481,"tags":482,"lastUpdated":66},"\u002Flearnings\u002Fagentic-sdlc","The agentic SDLC","How AI agents move from autocomplete to owning the loop across the software lifecycle, and why that shifts the bottleneck from coding to verification.","Learnings",null,30,[12,483,484,485,486],"sdlc","engineering","verification","review",{"path":488,"title":489,"description":490,"group":479,"section":480,"order":491,"tags":492,"lastUpdated":498},"\u002Flearnings\u002Fagi-to-asi","From AGI to ASI","What lies beyond human-level AI. The four technological pathways from AGI to artificial superintelligence, the formal ceiling that bounds them, and the six bottlenecks that could stall the climb - distilled from the DeepMind report.",50,[493,494,495,496,497],"ai-futures","asi","agi","scaling","recursive-self-improvement","2026-06-19",{"path":500,"title":501,"description":502,"group":479,"section":480,"order":503,"tags":504,"lastUpdated":66},"\u002Flearnings\u002Fai-native-company-playbook","AI native company playbook","Why AI should be the operating system your company runs on, not a tool it uses, and the concrete practices that follow - closed loops, a queryable org, software factories, and token maxing.",40,[505,506,12,507,508],"ai-native","company-building","gtm","founders",{"path":510,"title":511,"description":512,"group":479,"section":480,"order":157,"tags":513,"lastUpdated":54},"\u002Flearnings\u002Fbuying-intent-signals","Buying intent signals","How buyers leak their intent before they ever fill in a form, and how to read those signals before the window closes.",[514,51,507,515],"intent","sales",{"path":517,"title":518,"description":519,"group":479,"section":480,"order":520,"tags":521,"lastUpdated":54},"\u002Flearnings\u002Fcold-outbound-system","Cold outbound system","A high-level study of an open-source 29-skill cold email system, organized into five sequential tracks from ICP to iteration.",20,[522,523,507,524],"outbound","cold-email","systems",{"path":526,"title":527,"description":528,"group":479,"section":480,"order":529,"tags":530,"lastUpdated":532},"\u002Flearnings\u002Fswan-gtm-skills-architecture","Swan GTM skills architecture","A research note on Swan AI's foundations and maps model for GTM agents, with ASCII diagrams and ideas AgencyCore can borrow.",60,[507,12,73,531,14],"swan","2026-07-01",{"path":534,"title":535,"description":536,"group":387,"section":480,"order":272,"tags":537,"lastUpdated":43},"\u002Fmission-control\u002Fciops-agent","CIOps agent","High-level system architecture and design notes for the Mission Control CIOps agent.",[389,12,538,14],"ciops",{"path":540,"title":541,"description":542,"group":387,"section":480,"order":182,"tags":543,"lastUpdated":43},"\u002Fmission-control\u002Fcostops-agent","CostOps agent","High-level system architecture and design notes for the Mission Control CostOps agent.",[389,12,544,14],"finops",{"path":546,"title":547,"description":548,"group":387,"section":480,"order":520,"tags":549,"lastUpdated":54},"\u002Fmission-control\u002Fdashboard","Dashboard","The Mission Control product UI - a dark cockpit with a fleet-nav rail, company-state grid, a working escalation queue, live ledger and a global kill switch.",[389,12,550,398],"dashboard",{"path":552,"title":553,"description":554,"group":387,"section":480,"order":280,"tags":555,"lastUpdated":43},"\u002Fmission-control\u002Fproduct-analytics-agent","ProductAnalytics agent","High-level system architecture and design notes for the Mission Control ProductAnalytics agent.",[389,12,556,14],"product-analytics",{"path":558,"title":559,"description":560,"group":387,"section":480,"order":290,"tags":561,"lastUpdated":43},"\u002Fmission-control\u002Frevenueops-agent","RevenueOps agent","High-level system architecture and design notes for the Mission Control RevenueOps agent.",[389,12,562,14],"revops",{"path":564,"title":214,"description":565,"group":387,"section":480,"order":157,"tags":566,"lastUpdated":54},"\u002Fmission-control\u002Fsystem-design","One screen for the whole company, watched by a guardrailed fleet of ops agents that explain, propose, act and learn overnight.",[389,12,544,14],{"path":568,"title":569,"description":570,"group":571,"section":480,"order":32,"tags":572,"lastUpdated":578},"\u002Fproduct-design\u002Fonboarding-flow","Onboarding flow","Product design for the signup wizard and how TAM building folds into it. Analyzes the flow today (account, profile, company), the gap (no ICP, empty dashboard), and the integration of a new \"who you sell to\" ICP step plus a build-and-reveal screen that lands the user on a populated, ranked list.","Product Design",[573,61,574,575,576,577],"onboarding","tam","activation","ux","user-journey","2026-06-11",{"path":580,"title":581,"description":582,"group":571,"section":480,"order":233,"tags":583,"lastUpdated":592},"\u002Fproduct-design\u002Fpricing-entitlements","Pricing tiers, entitlements and usage credits","Specification for subscription tiers with gated platform access: composable plan entitlements, a unified usage-credit currency, plan-sourced limits, per-module trials and a two-ticket delivery plan built on the Stripe billing foundation. Written for discussion; the Linear document is the canonical copy with ticket links.",[584,585,586,587,588,589,590,591],"pricing","entitlements","billing","credits","subscriptions","plans","seats","trials","2026-07-06",{"path":594,"title":595,"description":596,"group":571,"section":480,"order":233,"tags":597,"lastUpdated":578},"\u002Fproduct-design\u002Fsales-signals-ux","Designing Signals","Product design for the sales-signals experience in ac-frontend: the 14-type taxonomy and its color system, the anatomy of a signal card across four densities, the 0-10 lead score scale, the origin tag (sonar pull vs proactive push), the seven surfaces where signals render (launchpad, sonar app, company detail, timeline, activities, data layer, Envoy), and the interaction rules that keep them consistent.",[51,576,598,11,42,599,600,601,602],"design-system","lead-score","origin","pull","push",{"path":604,"title":605,"description":606,"group":607,"section":608,"order":244,"tags":609,"lastUpdated":43},"\u002Fproprietary-data\u002Fcrm\u002Factivities","Activities","Deep dive on crm_activities, the interaction + task log of the CRM — where it is served from, how a row is born and read, and its full schema, relationships and rules.","Proprietary data","CRM",[11,610,611,612,613],"activities","tasks","data-model","schema",{"path":615,"title":616,"description":617,"group":607,"section":608,"order":264,"tags":618,"lastUpdated":43},"\u002Fproprietary-data\u002Fcrm\u002Fcommunications","Communications","Deep dive on crm_communications and crm_communication_events, the unified email\u002Fcall\u002Fmessage log and its per-message engagement tracking — where it is served from, the outbound message lifecycle, and the full schema, relationships and rules.",[11,619,336,620,612],"communications","engagement",{"path":622,"title":623,"description":624,"group":607,"section":608,"order":22,"tags":625,"lastUpdated":43},"\u002Fproprietary-data\u002Fcrm\u002Fcompanies","Companies","Deep dive on crm_companies, the account record at the centre of the CRM — where it is served from, how a row is born and read, and its full schema, relationships and rules.",[11,36,612,613,14],{"path":627,"title":628,"description":629,"group":607,"section":608,"order":233,"tags":630,"lastUpdated":43},"\u002Fproprietary-data\u002Fcrm\u002Fdeals","Deals","Deep dive on the deal pipeline — crm_deals, crm_pipeline_stages and crm_pipeline_config. Where it is served from, the life of a deal, and its full schema, relationships and rules.",[11,631,632,612,613],"deals","pipeline",{"path":634,"title":635,"description":636,"group":607,"section":608,"order":222,"tags":637,"lastUpdated":43},"\u002Fproprietary-data\u002Fcrm\u002Flists","Lists","Deep dive on crm_lists and crm_list_members, the static or dynamic member collections of the CRM — where they are served from, how a list and its members come to be and are read, and their schema, relationships and rules.",[11,638,639,612,613],"lists","segments",{"path":641,"title":642,"description":643,"group":607,"section":608,"order":32,"tags":644,"lastUpdated":43},"\u002Fproprietary-data\u002Fcrm\u002Fpeople","People","Deep dive on crm_people, the contact record of the CRM — where it is served from, how a row is born and read, and its full schema, relationships and rules.",[11,119,645,612,613],"contacts",{"path":647,"title":648,"description":649,"group":607,"section":608,"order":368,"tags":650,"lastUpdated":43},"\u002Fproprietary-data\u002Fcrm\u002Fsaved-filters","Saved filters","Deep dive on crm_saved_filters, the named reusable filter snapshots over the company, person and signal list views — where it is served from, how a saved view is born and applied, and its full schema, relationships and rules.",[11,651,652,612,613],"saved-filters","views",{"path":654,"title":655,"description":656,"group":607,"section":608,"order":360,"tags":657,"lastUpdated":578},"\u002Fproprietary-data\u002Fcrm\u002Fsignals","Signals","Deep dive on the signals tables - signals, company_signals and person_signals, the CRM's sales-intelligence layer. Where signals are served from, how one is born and attached, and the full schema, relationships and rules.",[11,51,302,612,613],{"path":659,"title":660,"description":661,"group":607,"section":662,"order":22,"tags":663,"lastUpdated":43},"\u002Fproprietary-data\u002Fintelligence-databases\u002Fcompany-intelligence-database","Company Intelligence Database","Decided architecture for ENG-669, the cross-org company intelligence layer that acts as a read-through cache in front of enrichment providers, with public-facts-only privacy and provenance-tracked write-back.","Intelligence databases",[14,60,36,51,38,664],"eng-669",{"path":666,"title":667,"description":668,"group":607,"section":662,"order":244,"tags":669,"lastUpdated":578},"\u002Fproprietary-data\u002Fintelligence-databases\u002Forg-signal-feed","Org Signal Feed","The per-org activation layer on top of the shared signals store. One immutable intel_signals row fans out to many orgs through scoring (signal-type weight times ICP fit times recency decay) and materializes as ranked, tiered rows in intel_org_signal_feed - the only org-scoped, RLS-per-org table of the signal stack, the door the launchpad, inbox and digest all read through. Signals enter by two ingest classes - a user's sonar pull (ungated) or an automated push (gated by threshold plus an optional competitor-ICP check) - logged in intel_signal_ingests, and each feed row records its origin.",[14,60,51,670,53,671,672,575,430,601,602,600],"feed","decay","rls",{"path":674,"title":675,"description":676,"group":607,"section":662,"order":32,"tags":677,"lastUpdated":578},"\u002Fproprietary-data\u002Fintelligence-databases\u002Fpeople-intelligence-database","People Intelligence Database","Decided architecture for the cross-org people intelligence layer - a read-through cache in front of headhunter research and Hunter email lookups, with LinkedIn-URL identity, append-only employment edges, per-tier freshness stamps on the flat profile, shared intel_sources provenance, unified intel_signals, and a GDPR erasure path.",[14,60,119,51,38,100],{"path":679,"title":680,"description":681,"group":607,"section":662,"order":233,"tags":682,"lastUpdated":578},"\u002Fproprietary-data\u002Fintelligence-databases\u002Fsignals-intelligence-database","Signals Intelligence Database","Decided v1 architecture for the unified signal store - one polymorphic append-only intel_signals table that holds both company and person signals, with a shared taxonomy, source-ranked provenance, an intel_signal_ingests log that records which pipeline found each signal, decay at read time, and a person-to-company rollup so a champion job change surfaces on the company feed.",[14,60,51,683,671,684,670,685,41,601,602],"polymorphic","taxonomy","ingests",{"path":687,"title":688,"description":689,"group":607,"section":480,"order":9,"tags":690,"lastUpdated":16},"\u002Fproprietary-data\u002Foverview","Data Layer Overview","The AgencyCore data layer in one map - the org-scoped CRM plane in production today and the global intelligence plane designed to sit in front of it, with interactive diagrams of both, the end-to-end data flow, freshness and precedence rules, the privacy seam, and the rollout path.",[691,14,60,11,51,38,25,216],"data-layer",{"path":693,"title":694,"description":695,"group":696,"section":480,"order":9,"tags":697,"lastUpdated":54},"\u002Froadmap","Roadmap - June 2026","June 2026 product plan across four themes. The spine is moving our agents onto an isolated sandbox runtime and rebuilding the core agents and workflows on it, then standing up a read-through intelligence data store and shipping the Stripe billing system. Knowledge base, assistant, and credit tracking carry into the July roadmap.","Roadmap",[698,420],"roadmap",{"path":700,"title":701,"description":702,"group":696,"section":480,"order":22,"tags":703,"lastUpdated":54},"\u002Froadmap\u002Fjuly-2026","Roadmap - July 2026","July 2026 product plan across three themes, all carried over from June. Building on June's sandbox runtime, July grounds the agents in a knowledge base, launches the AI chat assistant, and meters every action with per-action credit tracking that reconciles into the Stripe billing system shipped in June.",[698,420],{"path":705,"title":706,"description":707,"group":696,"section":480,"order":32,"tags":708,"lastUpdated":532},"\u002Froadmap\u002Fjune-2026-slides","Roadmap slides - June 2026","Board-review slide deck for the June 2026 product roadmap, rendered directly from the original PPTX in the docs site.",[698,420,709],"slides",{"path":711,"title":712,"description":713,"group":714,"section":8,"order":520,"tags":715,"lastUpdated":16},"\u002Fsymphony\u002Fagents\u002Fdevops-agent","DevOps agent","Interactive design for a Slack-first Symphony DevOps agent that wraps production promotion, rollback, audit, and operational jobs behind policy gates, typed runbooks, and an auditable ledger.","Symphony",[716,235,717,718,719,720],"symphony","devops","production","runbooks","operations",{"path":722,"title":723,"description":724,"group":714,"section":8,"order":157,"tags":725,"lastUpdated":16},"\u002Fsymphony\u002Fagents\u002Foncall-agent","Oncall agent","Interactive design for a Symphony oncall agent that turns Sentry incidents into rich Linear tickets, investigates with Codex, opens fix PRs, and resolves Sentry after merge.",[716,284,726,727,728,729],"linear","oncall","incident-response","codex",{"path":731,"title":732,"description":733,"group":714,"section":734,"order":157,"tags":735,"lastUpdated":16},"\u002Fsymphony\u002Fhousekeeping\u002Fcodex-vacuum","Codex vacuum","Interactive design for the Symphony housekeeping timer that checkpoints and vacuums Codex sqlite stores on the VPS.","Housekeeping",[716,736,737,729,738,739],"timed-jobs","housekeeping","sqlite","vps",{"path":741,"title":742,"description":743,"group":714,"section":734,"order":481,"tags":744,"lastUpdated":16},"\u002Fsymphony\u002Fhousekeeping\u002Fhost-cleanup","Host cleanup","Interactive design for the Symphony housekeeping timer that removes stale \u002Ftmp debris, vacuums the journal, and optionally cleans the apt package cache.",[716,736,737,739,745,746],"disk","cleanup",{"path":748,"title":749,"description":750,"group":714,"section":734,"order":520,"tags":751,"lastUpdated":16},"\u002Fsymphony\u002Fhousekeeping\u002Fworkspace-cleanup","Workspace cleanup","Interactive design for the Symphony housekeeping timer that prunes idle per-issue workspaces after their TTL.",[716,736,737,752,746,739],"workspaces",{"path":754,"title":755,"description":756,"group":714,"section":480,"order":9,"tags":757,"lastUpdated":66},"\u002Fsymphony","Symphony orchestration","How AgencyCore runs OpenAI Symphony as a long-running daemon that turns Linear tickets into isolated, autonomous Codex runs, reviewed by Claude and merged by humans. High-level workflow, system architecture, and the engineer playbook.",[716,729,726,758,111,739,759,760],"claude-review","qa","automation",{"path":762,"title":763,"description":764,"group":714,"section":214,"order":22,"tags":765,"lastUpdated":392},"\u002Fsymphony\u002Fsystem-design\u002Fhigh-level-design","High-level design","The Symphony daemon end to end — the standing agent workforce and its label-routed workflows, then the runtime that polls, dispatches, runs and writes back.",[716,14,111,12,729,726,766],"systemd",{"path":768,"title":769,"description":770,"group":714,"section":771,"order":503,"tags":772,"lastUpdated":16},"\u002Fsymphony\u002Ftimed-jobs\u002Fdaily-security-agent","Daily security agent","Interactive design for a report-only Symphony timed job that reviews the last 24h of commits, scans the system for vulnerabilities, and opens focused follow-up tickets.","Timed jobs",[716,199,736,729,773,774,775],"semgrep","threat-model","ownership",{"path":777,"title":778,"description":779,"group":714,"section":771,"order":481,"tags":780,"lastUpdated":16},"\u002Fsymphony\u002Ftimed-jobs\u002Fdaily-sentry-triage","Daily Sentry triage","Interactive design for the Symphony timed job that performs read-only Sentry triage, deduplicates existing tracked clusters, and creates focused ENG bugs for new actionable errors.",[716,736,284,209,781,726],"triage",{"path":783,"title":784,"description":785,"group":714,"section":771,"order":157,"tags":786,"lastUpdated":16},"\u002Fsymphony\u002Ftimed-jobs\u002Fnightly-local-staging-e2e","Nightly local staging E2E","Interactive design for the Symphony timed job that seeds local Supabase, runs ac-frontend Playwright E2E against the local staging stack, uploads evidence, and cleans artifacts.",[716,736,787,788,789,790],"e2e","playwright","staging","frontend",{"path":792,"title":793,"description":794,"group":714,"section":771,"order":520,"tags":795,"lastUpdated":16},"\u002Fsymphony\u002Ftimed-jobs\u002Fnightly-staging-qa","Nightly staging QA","Interactive design for the Symphony timed job that seeds a staging QA Linear issue, runs an agent-browser crawl, validates feature-map coverage, and files focused follow-up work.",[716,736,789,759,796,726],"agent-browser",{"id":798,"title":373,"body":799,"customComponent":480,"description":374,"extension":11756,"group":139,"lastUpdated":259,"meta":11757,"navigation":1180,"order":375,"path":372,"related":11758,"section":181,"seo":11762,"stem":11763,"tags":11764,"__hash__":11765},"docs\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime\u002Fexecution.md",{"type":800,"value":801,"toc":11704},"minimark",[802,806,816,827,832,838,844,862,872,878,884,891,910,919,954,966,988,1030,1050,1064,1067,1073,1086,1112,1118,1122,1213,1224,1242,1269,1275,1306,1319,1343,1369,1376,1413,1443,1471,1488,1514,1518,1521,1570,1584,1594,1609,1614,1731,1737,1767,1783,1840,1852,1896,1905,1935,1951,1979,2008,2034,2039,2051,2057,2086,2128,2143,2148,2153,2213,2234,2239,2252,2263,2269,2495,2512,2528,2592,2599,2609,2615,2649,2666,2683,2688,2738,2764,2777,2789,2793,2799,2812,2816,2822,2828,2843,2869,2891,2910,2914,2917,2959,2962,2991,2999,3042,3045,3048,3051,3057,3064,3069,3089,3099,3102,3147,3165,3189,3192,3210,3216,3222,3229,3235,3257,3282,3296,3310,3325,3334,3338,3348,3354,3367,3392,3410,3422,3428,3458,3463,3466,3470,3479,3499,3534,3562,3575,3586,3589,3599,3603,3606,3611,3666,3671,3677,3685,3706,3712,3737,3744,3761,3795,3837,3871,3877,3898,3904,3915,3921,3940,3947,3950,3967,3978,3984,3998,4008,4014,4024,4027,4033,4043,4063,4085,4089,4095,4102,4108,4114,4127,4133,4142,4145,4151,4160,4168,4174,4199,4209,4216,4229,4235,4249,4270,4280,4289,4298,4302,4308,4314,4331,4352,4362,4387,4396,4419,4426,4438,4457,4484,4504,4518,4537,4543,4561,4578,4587,4597,4618,4641,4670,4678,4768,4778,4854,4860,4869,4887,4901,4921,4927,4945,4967,4984,5009,5044,5071,5088,5135,5147,5155,5170,5196,5205,5210,5268,5279,5326,5336,5355,5361,5376,5397,5412,5417,5499,5508,5542,5569,5587,5602,5608,5629,5632,5636,5639,5644,5650,5659,5662,5708,5726,5732,5738,5757,5782,5793,5796,5832,5841,5847,5861,5864,5871,5877,5901,5907,5911,6069,6092,6114,6133,6149,6168,6184,6190,6202,6243,6266,6272,6287,6300,6309,6318,6322,6328,6331,6339,6345,6351,6354,6368,6373,6417,6442,6449,6453,6466,6478,6484,6493,6499,6503,6531,6541,6550,6570,6576,6593,6596,6647,6658,6664,6670,6677,6681,6684,6805,6824,6837,6846,6871,6877,6886,6907,6916,6936,6940,6949,7022,7030,7033,7043,7053,7071,7079,7083,7095,7108,7117,7130,7375,7388,7403,7409,7467,7498,7512,7531,7537,7555,7572,7585,7598,7607,7683,7696,7700,7705,7755,7758,7773,7786,7789,7793,7796,7825,7843,7856,7862,7882,7889,7895,7899,8081,8085,8088,8098,8102,8105,8114,8120,8123,8168,8178,8194,8215,8219,8225,8231,8240,8254,8264,8277,8289,8292,8297,8303,8318,8345,8370,8380,8410,8435,8458,8494,8507,8520,8528,8546,8593,8599,8623,8640,8646,8655,8693,8709,8729,8755,8770,8783,8789,8795,8801,8804,8827,8836,8839,8859,8865,8868,8871,8877,8882,8891,8897,8904,8907,8920,8924,8938,8943,8949,8962,8971,8987,9005,9016,9028,9040,9043,9051,9055,9152,9164,9187,9204,9207,9222,9225,9238,9283,9286,9292,9301,9311,9322,9337,9343,9366,9389,9410,9426,9429,9433,9444,9455,9463,9469,9482,9488,9491,9507,9521,9533,9555,9561,9576,9579,9591,9605,9650,9657,9674,9681,9690,9702,9713,9735,9741,9753,9773,9781,9806,9812,9822,9844,9864,9875,9881,9885,9888,9894,9897,9903,9927,9933,10011,10029,10040,10049,10055,10061,10070,10076,10082,10085,10106,10121,10157,10166,10171,10175,10178,10594,10598,11036,11040,11700],[803,804,373],"h1",{"id":805},"runtime-execution",[807,808,809,812,813,815],"p",{},[810,811,366],"a",{"href":365}," says what may run. ",[810,814,380],{"href":379}," says which component owns what. This page says how one Run progresses, and how it survives a crash.",[817,818,824],"pre",{"className":819,"code":821,"language":822,"meta":823},[820],"language-text","StartRunCommand\n   -> RunManager           mint Principal, freeze snapshot, create Run\n   -> Policy admission\n   -> Inngest              function run.execute\n   -> RunExecutor\n        -> AgentExecutor    -> AgentRuntime -> Agno\n        -> WorkflowExecutor -> WorkflowStepExecutor\n   -> RunManager           outcome\n","text","",[825,826,821],"code",{"__ignoreMap":823},[828,829,831],"h2",{"id":830},"the-run-record","The Run record",[807,833,834,837],{},[825,835,836],{},"agent.runs"," is the one product Run table.",[817,839,842],{"className":840,"code":841,"language":822,"meta":823},[820],"agent.runs\n  id\n  organization_id\n  root_run_id          the top Run of the tree; a top level Run points at itself\n  parent_run_id        null for a top level Run\n  conversation_id      null for a machine Run\n  definition_id\n  idempotency_key      the start key; unique per organization\n  kind                 agent | workflow\n  source               front_door | trigger | api | workflow_step\n  parent_span_id       the node span that started this Run; null unless workflow_step\n  status               queued | running | waiting | succeeded | failed | cancelled\n  waiting_on           approval | event | delay; null unless status is waiting\n  waiting_ref_id       the approval or the wait node; null otherwise\n  waiting_expires_at   when the wait dies; the wait sweep reads it\n  snapshot   jsonb     frozen execution snapshot\n  principal  jsonb     minted effective authority\n  input      jsonb\n  result     jsonb     RunResult; bounded\n  error      jsonb     RunError\n  execution_ref jsonb  Inngest correlation only: function run ID, attempt\n  segment_index        the next agent segment to run; 0 for a workflow\n  resumed_from_wait    true when the last transition was a wait resolving\n  heartbeat_at         last progress write; the reaper reads it\n  created_at \u002F started_at \u002F ended_at\n",[825,843,841],{"__ignoreMap":823},[807,845,846,857,858,861],{},[847,848,849,852,853,856],"strong",{},[825,850,851],{},"parent_span_id"," is the other half of ",[825,854,855],{},"parent_run_id",", and the span tree needs it."," A child Run executes in another process, and its ",[825,859,860],{},"run"," span takes the node span as its parent so the tree keeps one root. The child worker cannot be handed that value at call time, because it reads it after a crash as well, so the Run row carries it. It takes no foreign key: the Run is written before any span of it exists.",[807,863,864,867,868,871],{},[825,865,866],{},"root_run_id"," is set once, at creation. A child copies it from its parent. The live event publisher and the run explorer both read it, so a whole tree needs one subscription and one query. A top level Run points at itself in the same ",[825,869,870],{},"INSERT",", which a self referencing foreign key accepts, so creation stays one statement.",[807,873,874,877],{},[847,875,876],{},"Seven invariants are constraints, not conventions."," Each one guards a reader that would otherwise be wrong, and none of them is business logic: they are the shape of a row.",[817,879,882],{"className":880,"code":881,"language":822,"meta":823},[820],"status         one of the six product values\nwaiting shape  (status = 'waiting') = (waiting_on IS NOT NULL)\nwaiting ref    waiting_ref_id IS NULL OR status = 'waiting'\nwait deadline  (status = 'waiting') = (waiting_expires_at IS NOT NULL)\nended shape    (ended_at IS NOT NULL) = (status IN ('succeeded','failed','cancelled'))\ntree shape     (parent_run_id IS NULL) = (root_run_id = id)\nstart key      unique (organization_id, idempotency_key)\n",[825,883,881],{"__ignoreMap":823},[807,885,886,887,890],{},"The wait deadline is an equivalence and the waiting reference is not, and the difference is deliberate. A ",[825,888,889],{},"waiting"," Run holding no deadline is a Run the wait sweep can never read, so the weaker rule would let one park for ever. The waiting reference stays weak because a wait node and the first admission mark both file no row.",[807,892,893,894,897,898,901,902,905,906,909],{},"The waiting reference and the ended shape are the two a review misses. A resume that clears ",[825,895,896],{},"waiting_on"," and forgets ",[825,899,900],{},"waiting_ref_id"," leaves the Run pointing at a resolved approval. And the orphan span closer keys on ",[825,903,904],{},"ended_at IS NOT NULL",", so a Run with an ",[825,907,908],{},"ended_at"," and a live status either strands its open spans forever or closes the spans of work still running.",[807,911,912,913,915,916,918],{},"The tree shape costs nothing and catches the bug that is hardest to see later: a child whose ",[825,914,866],{}," was copied from the wrong place. Every tree query, every live event channel and the cost ceiling all key on ",[825,917,866],{},", so one wrong copy splits a tree in three places at once.",[807,920,921,924,925,928,929,928,932,928,935,937,938,940,941,943,944,947,948,950,951,953],{},[847,922,923],{},"Five columns are frozen once the Run starts."," ",[825,926,927],{},"id",", ",[825,930,931],{},"organization_id",[825,933,934],{},"definition_id",[825,936,855],{}," and ",[825,939,866],{}," never change after the ",[825,942,870],{}," that proves them. A ",[825,945,946],{},"CHECK"," cannot express this, because each proof reads a second row, so a trigger holds it. The ",[825,949,870],{}," validates the Run against its definition and against its parent, and freezing the columns stops a later write undoing that proof. It also closes the parent side: a parent cannot move, so it cannot strand a child in another organization, and no descendant scan is needed. ",[825,952,866],{}," is set once at creation for this reason.",[807,955,956,957,961,962,965],{},"The start key is the whole duplicate guard. ",[810,958,960],{"href":959},"#the-insert-is-the-claim","The insert is the claim"," says why it lives on this row rather than in ",[825,963,964],{},"agent.idempotency_keys",".",[807,967,968,924,971,937,974,977,978,980,981,984,985,987],{},[847,969,970],{},"The tenancy column is tied to the Run, not merely copied from it.",[825,972,973],{},"agent.spans",[825,975,976],{},"agent.sessions"," carry their own ",[825,979,931],{},", because RLS reads it there. Nothing about a copy keeps it true, and a span holds tool arguments, so a wrong copy shows one tenant's data to another. A composite foreign key on ",[825,982,983],{},"(run_id, organization_id)"," against a matching unique key on the Run makes the mismatch impossible to write. It costs one extra unique index on ",[825,986,836],{},", which measured at roughly 50 MB per million Runs, and it removes a class of leak that no code review can catch reliably.",[807,989,990,924,993,937,996,998,999,1002,1003,1005,1006,1009,1010,1012,1013,1016,1017,1020,1021,937,1023,1026,1027,1029],{},[847,991,992],{},"Two references on this row stay soft, and one restricts.",[825,994,995],{},"conversation_id",[825,997,900],{}," point at tables that arrive in later phases, so they are plain UUIDs with no foreign key. ",[825,1000,1001],{},"agent.spans.usage_id"," keeps its soft reference permanently: a real key from ",[825,1004,973],{}," into ",[825,1007,1008],{},"public.ai_usage_log"," would tie the isolated schema back to a live table, which is the coupling the schema exists to prevent. ",[825,1011,934],{}," is the opposite case and takes ",[825,1014,1015],{},"ON DELETE RESTRICT",", because the design already gives an admin ",[825,1018,1019],{},"state: disabled"," and a Run's audit trail must not be deletable from under it. That key carries ",[825,1022,931],{},[825,1024,1025],{},"kind"," as well, so a Run and its definition always agree on both. A definition with Runs behind it therefore cannot change its ",[825,1028,1025],{}," either.",[807,1031,1032,1038,1039,1042,1043,1046,1047,1049],{},[847,1033,1034,1037],{},[825,1035,1036],{},"agent.definitions"," carries a second unique key."," The pair ",[825,1040,1041],{},"(id, organization_id)"," sits beside the triple above. ",[825,1044,1045],{},"agent.policies"," narrows a rule to one definition, and a policy holds no ",[825,1048,1025],{},", so it cannot use the triple. The pair is what stops one organization naming another organization's definition.",[807,1051,1052,1055,1056,1059,1060,1063],{},[825,1053,1054],{},"execution_ref"," is telemetry, not lifecycle. It carries the Inngest function run ID and the current attempt, so an operator can jump from a Run to its Inngest trace. Nothing branches on it. There is no ",[825,1057,1058],{},"retrying"," status: a Run under retry stays ",[825,1061,1062],{},"running",", and the failed attempt is already durable as a failed span.",[807,1065,1066],{},"One small companion table carries the control state.",[817,1068,1071],{"className":1069,"code":1070,"language":822,"meta":823},[820],"agent.run_control\n  run_id (pk)\n  cancel_requested_at\n  cancel_requested_by\n  cancel_reason\n",[825,1072,1070],{"__ignoreMap":823},[807,1074,1075,1081,1082,1085],{},[847,1076,1077,1078,1080],{},"Three columns on ",[825,1079,836],{}," would be simpler. Do not merge this table."," The executor reads it before and after every tool call, about forty times in a normal Run. A Run that nobody cancelled has ",[847,1083,1084],{},"no row here",", so the check is an index only scan that never touches the heap: two buffers, and zero heap fetches. The same check as a column on the Run row is a heap read of the widest table in the schema. The sparseness is the whole point, and it is invisible from the column list, which is why it is written down here.",[807,1087,1088,924,1091,1094,1095,1098,1099,1102,1103,1106,1107,1111],{},[847,1089,1090],{},"The buffer count is not the cost this design pays, and reading it as one leads somewhere wrong.",[825,1092,1093],{},"ac-python-api"," reaches Postgres through PostgREST, so each of those forty checks is an HTTP request: five to twenty milliseconds, against a fraction of a millisecond of buffer work either way. The table split is therefore free rather than fast, and it stays because free plus a cancel record that never touches the Run row is better than merged. ",[847,1096,1097],{},"The number to watch is forty round trips per Run, and the safe boundary rule is what bounds it",": before a tool call, after a tool call, and between workflow nodes, and nowhere else. ",[810,1100,1101],{"href":277},"Observability"," makes the same correction for ",[825,1104,1105],{},"heartbeat_at",": it suppresses the ",[1108,1109,1110],"em",{},"request",", not only the write, and for the same reason.",[807,1113,1114,1115,1117],{},"There is no second table for the replay journal. ",[825,1116,964],{}," already stores the claim, the argument hash and the stored response, which is exactly what a restarted segment reads back. The next section explains how.",[828,1119,1121],{"id":1120},"result-and-error","Result and error",[817,1123,1127],{"className":1124,"code":1125,"language":1126,"meta":823,"style":823},"language-python shiki shiki-themes github-dark","@dataclass(frozen=True)\nclass RunResult:\n    summary: str\n    output: dict                  # bounded; large payloads stay in product tables\n    refs: list[ResourceRef]       # rows the Run produced\n    truncated: bool = False\n    partial_reason: str | None = None   # budget_exhausted | limit_reached\n                                        #  | approval_expired\n                                        #  | approval_not_actionable\n\n@dataclass(frozen=True)\nclass RunError:\n    code: str\n    message: str\n    retryable: bool\n    span_id: UUID | None\n","python",[825,1128,1129,1136,1141,1146,1151,1156,1161,1166,1171,1176,1182,1186,1191,1196,1201,1207],{"__ignoreMap":823},[1130,1131,1133],"span",{"class":1132,"line":22},"line",[1130,1134,1135],{},"@dataclass(frozen=True)\n",[1130,1137,1138],{"class":1132,"line":32},[1130,1139,1140],{},"class RunResult:\n",[1130,1142,1143],{"class":1132,"line":233},[1130,1144,1145],{},"    summary: str\n",[1130,1147,1148],{"class":1132,"line":244},[1130,1149,1150],{},"    output: dict                  # bounded; large payloads stay in product tables\n",[1130,1152,1153],{"class":1132,"line":264},[1130,1154,1155],{},"    refs: list[ResourceRef]       # rows the Run produced\n",[1130,1157,1158],{"class":1132,"line":222},[1130,1159,1160],{},"    truncated: bool = False\n",[1130,1162,1163],{"class":1132,"line":360},[1130,1164,1165],{},"    partial_reason: str | None = None   # budget_exhausted | limit_reached\n",[1130,1167,1168],{"class":1132,"line":368},[1130,1169,1170],{},"                                        #  | approval_expired\n",[1130,1172,1173],{"class":1132,"line":375},[1130,1174,1175],{},"                                        #  | approval_not_actionable\n",[1130,1177,1178],{"class":1132,"line":157},[1130,1179,1181],{"emptyLinePlaceholder":1180},true,"\n",[1130,1183,1184],{"class":1132,"line":182},[1130,1185,1135],{},[1130,1187,1188],{"class":1132,"line":290},[1130,1189,1190],{},"class RunError:\n",[1130,1192,1193],{"class":1132,"line":280},[1130,1194,1195],{},"    code: str\n",[1130,1197,1198],{"class":1132,"line":272},[1130,1199,1200],{},"    message: str\n",[1130,1202,1204],{"class":1132,"line":1203},15,[1130,1205,1206],{},"    retryable: bool\n",[1130,1208,1210],{"class":1132,"line":1209},16,[1130,1211,1212],{},"    span_id: UUID | None\n",[807,1214,1215,1216,1223],{},"A Run that stops on a budget or a ceiling ",[847,1217,1218,1219,1222],{},"succeeds with a ",[825,1220,1221],{},"partial_reason",", when the Run produced output",". It does not fail. Signals Search returns the companies it did qualify. The email sequence keeps the messages it did send.",[807,1225,1226,1229,1230,1233,1234,1237,1238,1241],{},[847,1227,1228],{},"One rule decides the status of every stop: nothing done is a failure, and something done is a partial success."," A stop that produced nothing reports ",[825,1231,1232],{},"failed",". The error carries the clock on ",[825,1235,1236],{},"RunError.partial_reason",", so the record survives the failure and a parent node still reads it. A ",[825,1239,1240],{},"succeeded"," outcome with an empty result tells every surface the work was done. This rule prevents that report.",[807,1243,1244,1245,1248,1249,1252,1253,1256,1257,1260,1261,1264,1265,1268],{},"Each executor reads \"nothing done\" from the record it owns. ",[825,1246,1247],{},"WorkflowExecutor._to_outcome"," reads an empty ",[825,1250,1251],{},"outputs"," map, because a node that ran leaves a key in it. ",[825,1254,1255],{},"AgentExecutor._stop_short"," reads ",[825,1258,1259],{},"segment_index == 0",", because a later segment means an earlier one already worked. Four ceilings reach that second branch: the money ceiling, the turn count, the tool call count and the wall clock. ",[825,1262,1263],{},"max_segments"," is the fifth, and it reaches neither. It bounds the loop ",[847,1266,1267],{},"above"," the segment, and the function refuses a frozen bound under one. So a Run that spends its segments already ran one, and that ending is always a partial success. An admission approval settles under the same rule from the other side: nothing ran before it, so it ends no Run as a partial success.",[807,1270,1271,1274],{},[847,1272,1273],{},"The rule is stated here for all of them, and no later section restates it."," A section that needs the rule names this one.",[807,1276,1277,1282,1285,1286,1289,1290,1293,1294,1297,1298,1301,1302,1305],{},[847,1278,1279,1281],{},[825,1280,1221],{}," has four members, and each one names a different reason.",[825,1283,1284],{},"budget_exhausted"," is the money ceiling or the organization day, read from the\nusage meter. ",[825,1287,1288],{},"limit_reached"," is one of the four ceilings a Run owns: turns, tool\ncalls, wall clock or segments. ",[825,1291,1292],{},"approval_expired"," is an action approval nobody\nanswered, and the wait writes it rather than the executor.\n",[825,1295,1296],{},"approval_not_actionable"," is a decision the Run holds and cannot act on. It has\ntwo writers, and neither is the executor: the wait, when a person decided after\nthe clock ran out, and the segment loop, when a segment parks again on the\ndecision it was given. ",[847,1299,1300],{},"The second writes no wait step at all",", so a Run that\nreports this reason may carry no ",[825,1303,1304],{},"wait.*"," span. A fifth member would have to\nanswer one of those four questions again.",[807,1307,1308,1309,1311,1312,1314,1315,1318],{},"The last two are not one member, because they say opposite things about a\nperson. ",[825,1310,1292],{}," says nobody answered. ",[825,1313,1296],{}," says\nsomebody did, and the Run could not use their answer. A person whose inbox card\nreads ",[825,1316,1317],{},"approved"," must never read that nobody answered.",[807,1320,1321,1326,1327,1330,1331,1334,1335,1338,1339,1342],{},[847,1322,1323,1324,965],{},"A cancellation is not a ",[825,1325,1221],{}," A cancelled Run ends ",[825,1328,1329],{},"cancelled",", and ",[825,1332,1333],{},"succeed()"," is a dash from a terminal status, so a ",[825,1336,1337],{},"RunResult"," carrying ",[825,1340,1341],{},"partial_reason='cancelled'"," can never be written. It read as a third member of the set and it was unreachable, which is worse than absent: it invites an executor to build a result nothing will store.",[807,1344,1345,1348,1349,1352,1353,1356,1357,1360,1361,1363,1364,1368],{},[825,1346,1347],{},"output"," never holds a large payload, and that is enforced rather than asked for. It passes ",[825,1350,1351],{},"bound(output, 32 KB)",", the one payload boundary, and sets ",[825,1354,1355],{},"truncated"," from what that returns. ",[825,1358,1359],{},"RunManager"," is the caller, not the executor, so it rebuilds the frozen ",[825,1362,1337],{}," with the bounded value and the flag it got back. The algorithm is defined once in ",[810,1365,1367],{"href":1366},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fcontract#the-payload-boundary","the platform contract"," and restated nowhere.",[807,1370,1371,1372,1375],{},"Discovery writes intelligence rows and prospect rows, and ",[825,1373,1374],{},"refs"," points at them.",[807,1377,1378,1386,1387,1389,1390,1393,1394,1396,1397,1400,1401,1404,1405,1408,1409,1412],{},[847,1379,1380,1382,1383,1385],{},[825,1381,1374],{}," passes the same boundary, because it grows with the work exactly as ",[825,1384,1347],{}," does."," Bounding ",[825,1388,1347],{}," alone bounds nothing: a five hundred person email sequence writes five hundred refs beside it, in the same ",[825,1391,1392],{},"result"," column, on the widest table in the schema. ",[825,1395,1374],{}," is a list and ",[825,1398,1399],{},"bound()"," takes a list, so it is the same call at the same 32 KB. The head survives, the tail becomes a ",[825,1402,1403],{},"Dropped"," marker, and ",[825,1406,1407],{},"RunResult.truncated"," is true when either half changed. ",[825,1410,1411],{},"summary"," needs no ceiling: it is one sentence the executor writes, not a set that grows.",[807,1414,1415,1422,1423,1425,1426,1428,1429,1431,1432,1434,1435,1438,1439,1442],{},[847,1416,1417,1418,1421],{},"The Run's ",[825,1419,1420],{},"input"," is refused rather than bounded, and that changed on 2026-08-21."," It was a ",[825,1424,1399],{}," caller at 32 KB, to keep a 5 MB trigger payload out of a row that is kept for thirteen months. Trimming it answers the storage question and creates a worse one: ",[825,1427,1420],{}," is the caller's own instructions, so a drop makes the agent act on part of a request and report success, and no field on the row records the loss, because ",[825,1430,1355],{}," describes the output. So ",[825,1433,1359],{}," refuses the start with ",[825,1436,1437],{},"input_too_large",". The rule for a caller is unchanged and now enforced: a large payload passes a ",[825,1440,1441],{},"ResourceRef"," and leaves the body in the product table that owns it.",[807,1444,1445,1455,1456,1459,1460,1463,1464,1467,1468,965],{},[847,1446,1447,1448,1451,1452,1454],{},"The complete ",[825,1449,1450],{},"snapshot"," has a separate 256 KiB limit. A check refuses it; ",[825,1453,1399],{}," never trims it."," The helper drops whole top level items, and a dropped item here is the frozen tool contracts or the rendered skill text. The Run would then execute an agent that lost a tool it was published with, with nothing raised and no way for a reader to tell. Validation refuses an oversized snapshot at publish, and ",[825,1457,1458],{},"SnapshotBuilder.freeze()"," fails the start with ",[825,1461,1462],{},"snapshot_too_large",", which is a member of the closed ",[825,1465,1466],{},"StartRunResult"," set like every other refusal. Publish validation makes it rare rather than impossible: a definition published before the size rule existed still reaches freeze, and so does one whose rendered skill text grew after it was published. See ",[810,1469,1470],{"href":365},"runtime definitions",[807,1472,1473,1483,1484,1487],{},[847,1474,1475,1476,928,1478,1480,1481,965],{},"No list query selects ",[825,1477,1450],{},[825,1479,1420],{}," or ",[825,1482,1392],{}," The run explorer, the reaper and the tree query all read narrow column sets. One ",[825,1485,1486],{},"SELECT *"," on a list endpoint pulls three jsonb columns per row out of TOAST, and the page that was fast in testing is slow on a real tenant.",[807,1489,1490,924,1500,1503,1504,1506,1507,1510,1511,1513],{},[847,1491,1492,1495,1496,1499],{},[825,1493,1494],{},"Run"," therefore has two read shapes, and ",[825,1497,1498],{},"StartRunResult.run"," is the narrow one.",[825,1501,1502],{},"claim()"," hands its Run to the executor, which needs ",[825,1505,1450],{}," to rebuild the agent, so that read is wide by necessity. ",[825,1508,1509],{},"start()"," hands its Run to an API response, a trigger log and a workflow node, and none of the three opens ",[825,1512,1450],{},". Return one shape for both and every start pulls three jsonb columns out of TOAST for a caller that reads six scalars. Every duplicate does too, and that is the common answer on a redelivering source. One model with an optional wide half, loaded only where it is named.",[828,1515,1517],{"id":1516},"shared-start-contract","Shared start contract",[807,1519,1520],{},"The front door and the triggers use exactly one contract.",[817,1522,1524],{"className":1124,"code":1523,"language":1126,"meta":823,"style":823},"@dataclass(frozen=True)\nclass StartRunCommand:\n    definition_id: UUID\n    input: dict\n    actor: ActorIdentity          # user or service identity, not a Principal\n    source: RunSource\n    idempotency_key: str\n    conversation_id: UUID | None = None\n    parent_run_id: UUID | None = None\n",[825,1525,1526,1530,1535,1540,1545,1550,1555,1560,1565],{"__ignoreMap":823},[1130,1527,1528],{"class":1132,"line":22},[1130,1529,1135],{},[1130,1531,1532],{"class":1132,"line":32},[1130,1533,1534],{},"class StartRunCommand:\n",[1130,1536,1537],{"class":1132,"line":233},[1130,1538,1539],{},"    definition_id: UUID\n",[1130,1541,1542],{"class":1132,"line":244},[1130,1543,1544],{},"    input: dict\n",[1130,1546,1547],{"class":1132,"line":264},[1130,1548,1549],{},"    actor: ActorIdentity          # user or service identity, not a Principal\n",[1130,1551,1552],{"class":1132,"line":222},[1130,1553,1554],{},"    source: RunSource\n",[1130,1556,1557],{"class":1132,"line":360},[1130,1558,1559],{},"    idempotency_key: str\n",[1130,1561,1562],{"class":1132,"line":368},[1130,1563,1564],{},"    conversation_id: UUID | None = None\n",[1130,1566,1567],{"class":1132,"line":375},[1130,1568,1569],{},"    parent_run_id: UUID | None = None\n",[807,1571,1572,1574,1575,1577,1578,1580,1581,1583],{},[825,1573,1420],{}," is a product mapping with two optional shared keys. ",[825,1576,822],{}," supplies the agent text. ",[825,1579,1374],{}," supplies a list of ",[825,1582,1441],{}," shaped objects that are already in scope. Other keys remain product input.",[807,1585,1586,1587,1589,1590,1593],{},"The executor copies valid ",[825,1588,1374],{}," entries into ",[825,1591,1592],{},"ContextRequest.entities",". It drops a malformed entry from the context request and leaves the stored input unchanged. A source decides which registered ref kinds it supports.",[807,1595,1596,924,1599,1602,1603,1605,1606,965],{},[847,1597,1598],{},"The organization comes from the actor, not from the command.",[825,1600,1601],{},"ActorIdentity"," carries ",[825,1604,931],{},", because the day cap gate and the start key both need it before a Principal exists, and the Principal is minted three steps later. Putting it on the command as well would give one fact two writers. See ",[810,1607,1608],{"href":287},"policy and governance",[807,1610,1611,1613],{},[825,1612,1509],{}," answers with a closed set, never an exception, because four different callers branch on it: the front door, a trigger, a workflow node and the API route.",[817,1615,1617],{"className":1124,"code":1616,"language":1126,"meta":823,"style":823},"@dataclass(frozen=True)\nclass StartRunResult:\n    outcome: Literal[\n        'started',                        # a new Run; dispatched unless it is a child\n        'duplicate',                      # this key already started a Run; run is that Run\n        'parent_cancelled',\n        'organization_budget_exhausted',\n        'definition_not_found',           # missing, or another organization's\n        'definition_not_published',       # a draft; a disabled definition with no\n                                          # parent; or a skill, which never runs\n        'snapshot_too_large',             # published before the size rule, or a skill grew\n        'snapshot_unbuildable',           # the deploy moved out from under a published\n                                          # definition: a tool the registry no longer holds\n        'input_too_large',                # the caller's input is over 32 KB\n        'idempotency_conflict',           # capability key reused with changed input\n        'capability_unavailable',         # selected capability binding changed\n        'capability_unauthorized',        # required capability scope is absent\n        'policy_unavailable',             # the day cap gate could not decide: the meter did\n                                          # not answer, or the rule set did not read\n    ]\n    run: Run | None = None                # set for 'started' and 'duplicate'\n    reason: str | None = None             # set for every refusal\n",[825,1618,1619,1623,1628,1633,1638,1643,1648,1653,1658,1663,1668,1673,1678,1683,1688,1693,1698,1704,1710,1716,1721,1726],{"__ignoreMap":823},[1130,1620,1621],{"class":1132,"line":22},[1130,1622,1135],{},[1130,1624,1625],{"class":1132,"line":32},[1130,1626,1627],{},"class StartRunResult:\n",[1130,1629,1630],{"class":1132,"line":233},[1130,1631,1632],{},"    outcome: Literal[\n",[1130,1634,1635],{"class":1132,"line":244},[1130,1636,1637],{},"        'started',                        # a new Run; dispatched unless it is a child\n",[1130,1639,1640],{"class":1132,"line":264},[1130,1641,1642],{},"        'duplicate',                      # this key already started a Run; run is that Run\n",[1130,1644,1645],{"class":1132,"line":222},[1130,1646,1647],{},"        'parent_cancelled',\n",[1130,1649,1650],{"class":1132,"line":360},[1130,1651,1652],{},"        'organization_budget_exhausted',\n",[1130,1654,1655],{"class":1132,"line":368},[1130,1656,1657],{},"        'definition_not_found',           # missing, or another organization's\n",[1130,1659,1660],{"class":1132,"line":375},[1130,1661,1662],{},"        'definition_not_published',       # a draft; a disabled definition with no\n",[1130,1664,1665],{"class":1132,"line":157},[1130,1666,1667],{},"                                          # parent; or a skill, which never runs\n",[1130,1669,1670],{"class":1132,"line":182},[1130,1671,1672],{},"        'snapshot_too_large',             # published before the size rule, or a skill grew\n",[1130,1674,1675],{"class":1132,"line":290},[1130,1676,1677],{},"        'snapshot_unbuildable',           # the deploy moved out from under a published\n",[1130,1679,1680],{"class":1132,"line":280},[1130,1681,1682],{},"                                          # definition: a tool the registry no longer holds\n",[1130,1684,1685],{"class":1132,"line":272},[1130,1686,1687],{},"        'input_too_large',                # the caller's input is over 32 KB\n",[1130,1689,1690],{"class":1132,"line":1203},[1130,1691,1692],{},"        'idempotency_conflict',           # capability key reused with changed input\n",[1130,1694,1695],{"class":1132,"line":1209},[1130,1696,1697],{},"        'capability_unavailable',         # selected capability binding changed\n",[1130,1699,1701],{"class":1132,"line":1700},17,[1130,1702,1703],{},"        'capability_unauthorized',        # required capability scope is absent\n",[1130,1705,1707],{"class":1132,"line":1706},18,[1130,1708,1709],{},"        'policy_unavailable',             # the day cap gate could not decide: the meter did\n",[1130,1711,1713],{"class":1132,"line":1712},19,[1130,1714,1715],{},"                                          # not answer, or the rule set did not read\n",[1130,1717,1718],{"class":1132,"line":520},[1130,1719,1720],{},"    ]\n",[1130,1722,1723],{"class":1132,"line":318},[1130,1724,1725],{},"    run: Run | None = None                # set for 'started' and 'duplicate'\n",[1130,1727,1728],{"class":1132,"line":327},[1130,1729,1730],{},"    reason: str | None = None             # set for every refusal\n",[807,1732,1733,1736],{},[825,1734,1735],{},"duplicate"," is a success for every caller. It carries the Run the first delivery created, so a trigger that fires twice reports one Run rather than an error.",[807,1738,1739,1745,1746,1748,1749,1752,1753,1755,1756,1758,1759,1762,1763,1766],{},[847,1740,1741,1744],{},[825,1742,1743],{},"started"," means the Run row exists. It does not mean the Run executes."," Policy admission runs after the insert, so a ",[825,1747,1743],{}," Run reads ",[825,1750,1751],{},"queued",", or ",[825,1754,889],{}," on an admission approval, or ",[825,1757,1232],{}," on a policy denial. The set holds no ",[825,1760,1761],{},"policy_denied"," member for that reason: the denial has a Run row, and the Run carries the answer. Only the day cap refuses before a Run exists. The caller reads ",[825,1764,1765],{},"run.status",", never the outcome alone.",[807,1768,1769,1772,1773,1775,1776,1779,1780,965],{},[847,1770,1771],{},"An unavailable definition is two outcomes, because it needs two statuses."," Another organization's definition must answer exactly as a missing one does, or the response tells the caller which identifiers exist. The caller's own draft or disabled definition is the opposite case: the Builder lists it on the next tab, so one answer that means \"no such definition\" reads as data loss. ",[810,1774,250],{"href":249}," maps the first to ",[825,1777,1778],{},"404"," and the second to ",[825,1781,1782],{},"409",[807,1784,1785,924,1795,1798,1799,928,1802,937,1804,1806,1807,1810,1811,1814,1815,1817,1818,1820,1821,1480,1823,1825,1826,1829,1830,1833,1834,1836,1837,1839],{},[847,1786,1787,1788,1791,1792,1794],{},"A ",[825,1789,1790],{},"skill"," takes the second answer, and ",[825,1793,1509],{}," is what refuses it.",[825,1796,1797],{},"agent.definitions.kind"," holds ",[825,1800,1801],{},"agent",[825,1803,34],{},[825,1805,1790],{},"; ",[825,1808,1809],{},"agent.runs.kind"," holds the first two. So a start against a published skill reaches the insert and raises ",[825,1812,1813],{},"23514"," from a method that promises a value and never an exception. ",[825,1816,1509],{}," refuses a resolved definition whose ",[825,1819,1025],{}," is not ",[825,1822,1801],{},[825,1824,34],{},", and answers ",[825,1827,1828],{},"definition_not_published",", with the ",[825,1831,1832],{},"reason"," naming the kind. ",[825,1835,1782],{}," is right for the same reason a draft takes it: the Builder lists the skill, so ",[825,1838,1778],{}," would read as data loss. The name is loose here and the status is not, and one more member in a set that four callers branch on costs more than the looseness.",[807,1841,1842,1843,1846,1847,1849,1850,965],{},"The caller supplies an ",[847,1844,1845],{},"actor",", not a Principal. ",[825,1848,1359],{}," mints the Principal, because the intersection needs the definition and the Run ID, and the caller has neither. See ",[810,1851,1608],{"href":287},[807,1853,1854,1863,1864,1867,1868,1871,1872,1874,1875,1878,1879,1882,1883,1886,1887,937,1889,1892,1893,965],{},[847,1855,1856,1859,1860,1862],{},[825,1857,1858],{},"PrincipalFactory"," is a seam of ",[825,1861,1359],{},", exactly as the resolver and the snapshot builder are."," It is named in the start flow and ",[825,1865,1866],{},"agent.runs.principal"," is ",[825,1869,1870],{},"NOT NULL",", and the intersection itself belongs to the policy plane. So ",[825,1873,1359],{}," takes it as a protocol, and ",[825,1876,1877],{},"governance\u002Fpolicy\u002Fprincipals.py"," implements it. ",[847,1880,1881],{},"It is given the declared names and not the definition",", because ",[825,1884,1885],{},"ResolvedDefinition"," lives in ",[825,1888,149],{},[825,1890,1891],{},"src.agentic.governance"," may not import ",[825,1894,1895],{},"src.agentic.runtime",[807,1897,1898,924,1901,1904],{},[847,1899,1900],{},"Four seams, five calls.",[825,1902,1903],{},"PolicyGate"," is one protocol with two methods, because the policy plane is one owner and swaps in one commit. The other three are called once each.",[817,1906,1908],{"className":1124,"code":1907,"language":1126,"meta":823,"style":823},"PolicyGate.check_day_cap(actor)                        # before the resolve\nDefinitionResolver.resolve(definition_id, actor)       # tenancy only\nPrincipalFactory.for_run(definition_id, declared_scopes, actor, run_id)\nSnapshotBuilder.freeze(definition)\nPolicyGate.admit(run, principal, definition, arguments) # after the insert\n",[825,1909,1910,1915,1920,1925,1930],{"__ignoreMap":823},[1130,1911,1912],{"class":1132,"line":22},[1130,1913,1914],{},"PolicyGate.check_day_cap(actor)                        # before the resolve\n",[1130,1916,1917],{"class":1132,"line":32},[1130,1918,1919],{},"DefinitionResolver.resolve(definition_id, actor)       # tenancy only\n",[1130,1921,1922],{"class":1132,"line":233},[1130,1923,1924],{},"PrincipalFactory.for_run(definition_id, declared_scopes, actor, run_id)\n",[1130,1926,1927],{"class":1132,"line":244},[1130,1928,1929],{},"SnapshotBuilder.freeze(definition)\n",[1130,1931,1932],{"class":1132,"line":264},[1130,1933,1934],{},"PolicyGate.admit(run, principal, definition, arguments) # after the insert\n",[807,1936,1937,1939,1940,1943,1944,1946,1947,1950],{},[825,1938,1509],{}," calls them in that order, so no later phase reshapes the flow. Each is a protocol, and every one of the four has its real implementation. ",[810,1941,1942],{"href":365},"Definitions"," owns the resolver and the builder. ",[825,1945,1877],{}," owns the factory, and ",[825,1948,1949],{},"runtime\u002Fruns\u002Fadmission.py"," owns the gate.",[807,1952,1953,924,1958,1961,1962,1964,1965,1967,1968,1970,1971,1974,1975,1978],{},[847,1954,1955,1957],{},[825,1956,1903],{}," lives on the runtime side of the plane boundary, and its two arguments say why.",[825,1959,1960],{},"admit()"," takes a ",[825,1963,1494],{}," and a ",[825,1966,1885],{},", and both live in ",[825,1969,149],{},", so ",[825,1972,1973],{},"PolicyEngine"," cannot satisfy this protocol. ",[825,1976,1977],{},"AdmissionGate"," holds the engine, the accrual checker and the decision log, and it decides nothing of its own except the fatal scope set.",[807,1980,1981,924,1986,1988,1989,1991,1992,1995,1996,1998,1999,2001,2002,937,2005,2007],{},[847,1982,1983,1985],{},[825,1984,1960],{}," takes the arguments, and the Run does not carry them.",[825,1987,1494],{}," is the narrow read on the start path, so the row holds no ",[825,1990,1420],{},". Without this argument a rule reading the reserved ",[825,1993,1994],{},"arguments"," root saves and never fires. The gate writes the ",[825,1997,860],{}," fact root from the row it is given, because ",[825,2000,1973],{}," writes ",[825,2003,2004],{},"principal",[825,2006,1994],{}," alone.",[807,2009,2010,924,2013,2016,2017,2019,2020,2022,2023,2026,2027,2001,2029,937,2031,2033],{},[847,2011,2012],{},"The Run ID is minted in Python, before the insert.",[825,2014,2015],{},"PrincipalFactory.for_run()"," needs it, and ",[825,2018,866],{}," points at the Run's own ",[825,2021,927],{}," for a top level Run. Both facts want the id before the statement runs, and ",[825,2024,2025],{},"runs_tree_shape"," refuses the row unless the two agree, so ",[825,2028,1359],{},[825,2030,927],{},[825,2032,866],{}," in the same insert rather than reading a database default back.",[807,2035,2036,2038],{},[825,2037,855],{}," is how a workflow node starts a child Run. There is no second start method and no private synchronous path.",[807,2040,2041,2050],{},[847,2042,2043,2045,2046,2049],{},[825,2044,855],{}," also decides how a ",[825,2047,2048],{},"disabled"," definition is treated."," Disable stops a new Run tree; it does not stop a tree that was admitted while the definition was still active. A workflow can wait three days and then start its next agent node, and failing that child would stop a Run nobody asked to stop.",[817,2052,2055],{"className":2053,"code":2054,"language":822,"meta":823},[820],"parent_run_id IS NULL      draft -> refuse    disabled -> refuse\nparent_run_id IS NOT NULL  draft -> refuse    disabled -> allow\n",[825,2056,2054],{"__ignoreMap":823},[807,2058,2059,2062,2063,2065,2066,2069,2070,2072,2073,1752,2078,2081,2082,2085],{},[825,2060,2061],{},"assert_run_shape()"," already encodes this, and it raises for a ",[825,2064,2048],{}," definition only when ",[825,2067,2068],{},"parent_run_id IS NULL",". ",[825,2071,1359],{}," runs first, so whichever component refuses every disabled definition wins and the trigger's carve-out never fires. ",[847,2074,2075,2077],{},[825,2076,1509],{}," must carry the same asymmetry",[825,2079,2080],{},"disable"," becomes a stop button for work already in flight. A draft is refused on both sides, because it holds no ",[825,2083,2084],{},"published_config"," and there is nothing to run.",[807,2087,2088,2098,2099,2102,2103,2105,2106,2108,2109,2111,2112,2114,2115,2119,2120,2069,2122,2124,2125,965],{},[847,2089,2090,2091,2093,2094,2097],{},"The asymmetry lives in ",[825,2092,1509],{},", and not in ",[825,2095,2096],{},"DefinitionResolver",", because of what the seam returns."," The resolver answers a definition or nothing, and nothing means ",[825,2100,2101],{},"definition_not_found",". A draft, a disabled definition at the top level and a ",[825,2104,1790],{}," all answer ",[825,2107,1828],{},", so a rule that lived in the resolver would turn a ",[825,2110,1782],{}," into a ",[825,2113,1778],{}," on a definition the Builder lists on its next tab, which is the answer ",[810,2116,2118],{"href":2117},"#shared-start-contract","this page already calls data loss",". So the seam answers one question, does this actor's organization own a definition with this id, and it takes no ",[825,2121,855],{},[825,2123,1509],{}," applies the three publishability rules to what comes back. A later resolver that needs to refuse for its own reasons needs a wider return type first, not a rule smuggled through ",[825,2126,2127],{},"None",[807,2129,2130,924,2133,2135,2136,2138,2139,2142],{},[847,2131,2132],{},"No caller outside the runtime supplies it.",[825,2134,1509],{}," sends the dispatch event only when ",[825,2137,855],{}," is null, so a Run created with one and no invoking parent is never claimed, and the reaper fails it two minutes later. The API start route therefore refuses the field rather than passing it through, and ",[825,2140,2141],{},"parent_cancelled"," never reaches an HTTP caller.",[828,2144,2146],{"id":2145},"runmanager",[825,2147,1359],{},[807,2149,2150,2152],{},[825,2151,1359],{}," is the only component allowed to create or control a Run.",[817,2154,2156],{"className":1124,"code":2155,"language":1126,"meta":823,"style":823},"class RunManager:\n    async def start(self, command: StartRunCommand) -> StartRunResult: ...\n    async def claim(self, run_id: UUID, execution_ref: dict) -> Run | None: ...\n    async def mark_waiting(self, run_id: UUID, waiting_on: WaitingOn,\n                           *, expires_at: datetime,\n                           ref_id: UUID | None) -> None: ...\n    async def resume(self, run_id: UUID) -> ResumeOutcome: ...\n    async def advance_segment(self, run_id: UUID, from_index: int) -> None: ...\n    async def succeed(self, run_id: UUID, result: RunResult) -> None: ...\n    async def fail(self, run_id: UUID, error: RunError) -> None: ...\n    async def cancel(self, run_id: UUID, reason: str, actor: ActorIdentity) -> None: ...\n",[825,2157,2158,2163,2168,2173,2178,2183,2188,2193,2198,2203,2208],{"__ignoreMap":823},[1130,2159,2160],{"class":1132,"line":22},[1130,2161,2162],{},"class RunManager:\n",[1130,2164,2165],{"class":1132,"line":32},[1130,2166,2167],{},"    async def start(self, command: StartRunCommand) -> StartRunResult: ...\n",[1130,2169,2170],{"class":1132,"line":233},[1130,2171,2172],{},"    async def claim(self, run_id: UUID, execution_ref: dict) -> Run | None: ...\n",[1130,2174,2175],{"class":1132,"line":244},[1130,2176,2177],{},"    async def mark_waiting(self, run_id: UUID, waiting_on: WaitingOn,\n",[1130,2179,2180],{"class":1132,"line":264},[1130,2181,2182],{},"                           *, expires_at: datetime,\n",[1130,2184,2185],{"class":1132,"line":222},[1130,2186,2187],{},"                           ref_id: UUID | None) -> None: ...\n",[1130,2189,2190],{"class":1132,"line":360},[1130,2191,2192],{},"    async def resume(self, run_id: UUID) -> ResumeOutcome: ...\n",[1130,2194,2195],{"class":1132,"line":368},[1130,2196,2197],{},"    async def advance_segment(self, run_id: UUID, from_index: int) -> None: ...\n",[1130,2199,2200],{"class":1132,"line":375},[1130,2201,2202],{},"    async def succeed(self, run_id: UUID, result: RunResult) -> None: ...\n",[1130,2204,2205],{"class":1132,"line":157},[1130,2206,2207],{},"    async def fail(self, run_id: UUID, error: RunError) -> None: ...\n",[1130,2209,2210],{"class":1132,"line":182},[1130,2211,2212],{},"    async def cancel(self, run_id: UUID, reason: str, actor: ActorIdentity) -> None: ...\n",[807,2214,2215,2218,2219,2222,2223,2226,2227,2229,2230,2233],{},[825,2216,2217],{},"ref_id"," is optional because a ",[825,2220,2221],{},"delay"," wait points at no row. ",[825,2224,2225],{},"cancel()"," takes an ",[847,2228,1845],{},", not a Principal: a Principal is scoped to one Run, and the person who stops a Run is often not the person who started it. ",[825,2231,2232],{},"agent.run_control.cancel_requested_by"," stores that actor.",[2235,2236,2238],"h3",{"id":2237},"one-transition-primitive-and-one-table-of-legal-moves","One transition primitive, and one table of legal moves",[807,2240,2241,2242,2245,2246,2248,2249,2251],{},"Every method above is the same conditional ",[825,2243,2244],{},"UPDATE",", and none of them is safe unconditionally. A worker that finishes in the same instant as a cancel would otherwise write ",[825,2247,1240],{}," over ",[825,2250,1329],{},", and the person who stopped the Run would watch it succeed.",[817,2253,2257],{"className":2254,"code":2255,"language":2256,"meta":823,"style":823},"language-sql shiki shiki-themes github-dark","UPDATE agent.runs SET ... WHERE id = :run_id AND status IN (:legal_from_states)\n","sql",[825,2258,2259],{"__ignoreMap":823},[1130,2260,2261],{"class":1132,"line":22},[1130,2262,2255],{},[807,2264,2265,2266,2268],{},"Zero rows means the Run moved on. The caller stops, and it does not raise: another writer owns that Run now. This is the same fence ",[810,2267,200],{"href":269}," puts on a lease token, for the same reason.",[2270,2271,2272,2322],"table",{},[2273,2274,2275],"thead",{},[2276,2277,2278,2282,2287,2292,2297,2302,2307,2312,2317],"tr",{},[2279,2280,2281],"th",{},"From",[2279,2283,2284],{},[825,2285,2286],{},"admit",[2279,2288,2289],{},[825,2290,2291],{},"claim",[2279,2293,2294],{},[825,2295,2296],{},"mark_waiting",[2279,2298,2299],{},[825,2300,2301],{},"advance_segment",[2279,2303,2304],{},[825,2305,2306],{},"resume",[2279,2308,2309],{},[825,2310,2311],{},"succeed",[2279,2313,2314],{},[825,2315,2316],{},"fail",[2279,2318,2319],{},[825,2320,2321],{},"cancel",[2323,2324,2325,2357,2391,2429,2451,2473],"tbody",{},[2276,2326,2327,2332,2335,2339,2343,2345,2347,2349,2353],{},[2328,2329,2330],"td",{},[825,2331,1751],{},[2328,2333,2334],{},"—",[2328,2336,2337],{},[825,2338,1062],{},[2328,2340,2341],{},[825,2342,889],{},[2328,2344,2334],{},[2328,2346,2334],{},[2328,2348,2334],{},[2328,2350,2351],{},[825,2352,1232],{},[2328,2354,2355],{},[825,2356,1329],{},[2276,2358,2359,2363,2365,2369,2373,2377,2379,2383,2387],{},[2328,2360,2361],{},[825,2362,1062],{},[2328,2364,2334],{},[2328,2366,2367],{},[825,2368,1062],{},[2328,2370,2371],{},[825,2372,889],{},[2328,2374,2375],{},[825,2376,1062],{},[2328,2378,2334],{},[2328,2380,2381],{},[825,2382,1240],{},[2328,2384,2385],{},[825,2386,1232],{},[2328,2388,2389],{},[825,2390,1329],{},[2276,2392,2393,2397,2401,2405,2409,2413,2417,2421,2425],{},[2328,2394,2395],{},[825,2396,889],{},[2328,2398,2399],{},[825,2400,1751],{},[2328,2402,2403],{},[825,2404,1062],{},[2328,2406,2407],{},[825,2408,889],{},[2328,2410,2411],{},[825,2412,889],{},[2328,2414,2415],{},[825,2416,1062],{},[2328,2418,2419],{},[825,2420,1240],{},[2328,2422,2423],{},[825,2424,1232],{},[2328,2426,2427],{},[825,2428,1329],{},[2276,2430,2431,2435,2437,2439,2441,2443,2445,2447,2449],{},[2328,2432,2433],{},[825,2434,1240],{},[2328,2436,2334],{},[2328,2438,2334],{},[2328,2440,2334],{},[2328,2442,2334],{},[2328,2444,2334],{},[2328,2446,2334],{},[2328,2448,2334],{},[2328,2450,2334],{},[2276,2452,2453,2457,2459,2461,2463,2465,2467,2469,2471],{},[2328,2454,2455],{},[825,2456,1232],{},[2328,2458,2334],{},[2328,2460,2334],{},[2328,2462,2334],{},[2328,2464,2334],{},[2328,2466,2334],{},[2328,2468,2334],{},[2328,2470,2334],{},[2328,2472,2334],{},[2276,2474,2475,2479,2481,2483,2485,2487,2489,2491,2493],{},[2328,2476,2477],{},[825,2478,1329],{},[2328,2480,2334],{},[2328,2482,2334],{},[2328,2484,2334],{},[2328,2486,2334],{},[2328,2488,2334],{},[2328,2490,2334],{},[2328,2492,2334],{},[2328,2494,2334],{},[807,2496,2497,2498,937,2500,2502,2503,2069,2505,937,2507,2509,2510,965],{},"A dash is a no-op. A terminal Run stays terminal. ",[825,2499,2311],{},[825,2501,2301],{}," exclude ",[825,2504,1751],{},[825,2506,2306],{},[825,2508,2286],{}," accept only ",[825,2511,889],{},[807,2513,2514,2516,2517,937,2520,2523,2524,965],{},[825,2515,2286],{}," releases the temporary capability admission hold. Its caller also requires ",[825,2518,2519],{},"waiting_on = approval",[825,2521,2522],{},"waiting_ref_id IS NULL",". It clears the wait fields and dispatches only if the update returns a row. A cancellation or recovery that wins the race prevents dispatch. See ",[810,2525,2527],{"href":2526},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fproducts\u002Fcapability-contracts#capability-start-identity","Capability start identity",[817,2529,2531],{"className":1124,"code":2530,"language":1126,"meta":823,"style":823},"NON_TERMINAL = frozenset({'queued', 'running', 'waiting'})\n\nLEGAL_FROM = {\n    'admit':           frozenset({'waiting'}),\n    'claim':           NON_TERMINAL,\n    'mark_waiting':    NON_TERMINAL,\n    'advance_segment': NON_TERMINAL - {'queued'},\n    'resume':          frozenset({'waiting'}),\n    'succeed':         NON_TERMINAL - {'queued'},\n    'fail':            NON_TERMINAL,\n    'cancel':          NON_TERMINAL,\n}\n",[825,2532,2533,2538,2542,2547,2552,2557,2562,2567,2572,2577,2582,2587],{"__ignoreMap":823},[1130,2534,2535],{"class":1132,"line":22},[1130,2536,2537],{},"NON_TERMINAL = frozenset({'queued', 'running', 'waiting'})\n",[1130,2539,2540],{"class":1132,"line":32},[1130,2541,1181],{"emptyLinePlaceholder":1180},[1130,2543,2544],{"class":1132,"line":233},[1130,2545,2546],{},"LEGAL_FROM = {\n",[1130,2548,2549],{"class":1132,"line":244},[1130,2550,2551],{},"    'admit':           frozenset({'waiting'}),\n",[1130,2553,2554],{"class":1132,"line":264},[1130,2555,2556],{},"    'claim':           NON_TERMINAL,\n",[1130,2558,2559],{"class":1132,"line":222},[1130,2560,2561],{},"    'mark_waiting':    NON_TERMINAL,\n",[1130,2563,2564],{"class":1132,"line":360},[1130,2565,2566],{},"    'advance_segment': NON_TERMINAL - {'queued'},\n",[1130,2568,2569],{"class":1132,"line":368},[1130,2570,2571],{},"    'resume':          frozenset({'waiting'}),\n",[1130,2573,2574],{"class":1132,"line":375},[1130,2575,2576],{},"    'succeed':         NON_TERMINAL - {'queued'},\n",[1130,2578,2579],{"class":1132,"line":157},[1130,2580,2581],{},"    'fail':            NON_TERMINAL,\n",[1130,2583,2584],{"class":1132,"line":182},[1130,2585,2586],{},"    'cancel':          NON_TERMINAL,\n",[1130,2588,2589],{"class":1132,"line":290},[1130,2590,2591],{},"}\n",[807,2593,2594,2595,2598],{},"A table written cell by cell holds forty-eight answers, and a terminal cell filled in by hand is a Run that un-ends. Written as a subtraction from ",[825,2596,2597],{},"NON_TERMINAL",", \"a terminal Run stays terminal\" is structural: no entry can name a terminal status, because none of them starts from the whole set.",[807,2600,2601,2604,2605,2608],{},[847,2602,2603],{},"The primitive clears the waiting three, so no method has to remember to."," This page says three separate times that ",[825,2606,2607],{},"waiting_on = NULL, waiting_ref_id = NULL, waiting_expires_at = NULL"," is the line a review misses, which is a sign that five call sites is four too many. One rule replaces the repetition, and it takes the same shape the table above takes.",[817,2610,2613],{"className":2611,"code":2612,"language":822,"meta":823},[820],"the write names a target status, and it is not `waiting`  -> clear the three\nthe write names `waiting`                                 -> mark_waiting sets the three\nthe write names no target status                          -> touch neither column\n",[825,2614,2612],{"__ignoreMap":823},[807,2616,2617,928,2619,928,2621,928,2624,937,2626,2629,2630,2633,2634,2637,2638,2641,2642,2645,2646,2648],{},[825,2618,1960],{},[825,2620,1502],{},[825,2622,2623],{},"resume()",[825,2625,1333],{},[825,2627,2628],{},"fail()"," all take the first line, and none of them repeats it. ",[825,2631,2632],{},"mark_waiting()"," is the second. ",[825,2635,2636],{},"advance_segment()"," is the third and the only one: it moves ",[825,2639,2640],{},"segment_index",", it leaves ",[825,2643,2644],{},"status"," alone, and a Run that is ",[825,2647,889],{}," must stay pointed at the row it waits on. One rule and one exception, rather than five statements each carrying two lines that must never be dropped.",[807,2650,2651,2653,2654,2656,2657,2659,2660,2662,2663,2665],{},[825,2652,2636],{}," is a lifecycle write like every other one, and it is guarded the same way. It drops ",[825,2655,1751],{}," for the same reason ",[825,2658,1333],{}," does. A segment runs only after ",[825,2661,1502],{},", so a ",[825,2664,1751],{}," Run has no segment to advance and the cell is unreachable.",[807,2667,2668,2670,2671,2673,2674,2670,2676,2678,2679,2682],{},[825,2669,2296],{}," from ",[825,2672,1751],{}," is the admission approval, which happens before the claim. ",[825,2675,2296],{},[825,2677,889],{}," is a second wait replacing the first, which a ",[825,2680,2681],{},"parallel"," node produces.",[807,2684,2685,2687],{},[825,2686,1502],{}," is re-entrant, and it clears the waiting three.",[817,2689,2691],{"className":2254,"code":2690,"language":2256,"meta":823,"style":823},"UPDATE agent.runs\n   SET status = 'running',\n       waiting_on = NULL,               -- the waiting shape constraint rejects the row otherwise\n       waiting_ref_id = NULL,\n       resumed_from_wait = false,\n       execution_ref = :execution_ref,\n       started_at = COALESCE(started_at, now()),   -- a retry must not reset the wall clock\n       heartbeat_at = now()\n WHERE id = :run_id AND status IN ('queued','waiting','running')\n",[825,2692,2693,2698,2703,2708,2713,2718,2723,2728,2733],{"__ignoreMap":823},[1130,2694,2695],{"class":1132,"line":22},[1130,2696,2697],{},"UPDATE agent.runs\n",[1130,2699,2700],{"class":1132,"line":32},[1130,2701,2702],{},"   SET status = 'running',\n",[1130,2704,2705],{"class":1132,"line":233},[1130,2706,2707],{},"       waiting_on = NULL,               -- the waiting shape constraint rejects the row otherwise\n",[1130,2709,2710],{"class":1132,"line":244},[1130,2711,2712],{},"       waiting_ref_id = NULL,\n",[1130,2714,2715],{"class":1132,"line":264},[1130,2716,2717],{},"       resumed_from_wait = false,\n",[1130,2719,2720],{"class":1132,"line":222},[1130,2721,2722],{},"       execution_ref = :execution_ref,\n",[1130,2724,2725],{"class":1132,"line":360},[1130,2726,2727],{},"       started_at = COALESCE(started_at, now()),   -- a retry must not reset the wall clock\n",[1130,2729,2730],{"class":1132,"line":368},[1130,2731,2732],{},"       heartbeat_at = now()\n",[1130,2734,2735],{"class":1132,"line":375},[1130,2736,2737],{}," WHERE id = :run_id AND status IN ('queued','waiting','running')\n",[807,2739,2740,924,2743,2745,2746,2748,2749,2751,2752,2755,2756,2759,2760,2763],{},[847,2741,2742],{},"Two lines in that statement are the ones a review misses.",[825,2744,1502],{}," moves a Run out of ",[825,2747,889],{}," after an admission approval, so it must clear ",[825,2750,896],{},"; leave it and the waiting shape constraint rejects the update and the Run never starts. And ",[825,2753,2754],{},"started_at"," is set once: write ",[825,2757,2758],{},"now()"," and every Inngest retry restarts the wall clock, so ",[825,2761,2762],{},"max_run_duration"," bounds nothing on the Run that needs it most.",[807,2765,2766,2767,2769,2770,2772,2773,2776],{},"An Inngest retry claims the same Run again. A duplicate delivery finds the Run already ",[825,2768,1062],{}," and continues from the memoized steps. Do not write ",[825,2771,1502],{}," as a one way ",[825,2774,2775],{},"queued -> running"," transition. Every retry would fail.",[807,2778,2779,2780,1970,2783,2785,2786,2788],{},"A cancelled, succeeded or failed Run does not match the ",[825,2781,2782],{},"WHERE",[825,2784,1502],{}," returns ",[825,2787,2127],{}," and the executor stops without an error. That is why the return type is nullable: a Run cancelled between the dispatch and the claim is an ordinary outcome, not a fault.",[828,2790,2792],{"id":2791},"start-flow","Start flow",[817,2794,2797],{"className":2795,"code":2796,"language":822,"meta":823},[820],"StartRunCommand\n  -> refuse when the parent Run reads cancelled   (the status, not the control row)\n  -> accrual gate: the organization day cap\n       deny -> refuse before any Run exists; agent.policy_decisions with run_id null\n  -> resolve the active published definition\n  -> PrincipalFactory.for_run(definition_id, declared_scopes, actor, run_id)\n  -> SnapshotBuilder.freeze(definition)\n  -> INSERT agent.runs, status=queued ... ON CONFLICT (organization_id,\n                                                       idempotency_key) DO NOTHING\n       inserted -> a new Run\n       conflict -> read the row and return it as 'duplicate'\n  -> Policy admission: grant, rules, and the run budget\n       allow            -> send the Inngest dispatch event\n       deny             -> fail the Run with the policy reason; never dispatch\n       require_approval -> hold the Run, create the approval, aim the Run at it,\n                           then send the same Inngest dispatch event\n",[825,2798,2796],{"__ignoreMap":823},[807,2800,2801,2802,2805,2806,2808,2809,2811],{},"Capability starts add two steps to this generic flow. They first check for a stored request with the same caller, tenant, source and key. A matching digest returns the stored Run; a changed digest returns ",[825,2803,2804],{},"idempotency_conflict",". A new capability Run is inserted in the temporary ",[825,2807,889],{}," admission hold. Policy allow must release that hold through ",[825,2810,2286],{}," before dispatch. A crash before release leaves an undispatched Run for the wait sweep to fail.",[2235,2813,2815],{"id":2814},"an-admission-approval-is-held-before-its-row-is-written","An admission approval is held before its row is written",[807,2817,2818,2821],{},[825,2819,2820],{},"require_approval"," makes four writes, and the order decides what a crash costs.",[817,2823,2826],{"className":2824,"code":2825,"language":822,"meta":823},[820],"1  mark_waiting(run, 'approval', None, expires_at=now())\n2  ApprovalService.create(proposal, ttl, run_deadline=None)\n3  mark_waiting(run, 'approval', approval.id, expires_at=approval.expires_at)\n4  the Inngest dispatch event\n",[825,2827,2825],{"__ignoreMap":823},[807,2829,2830,2833,2834,2836,2837,2839,2840,2842],{},[847,2831,2832],{},"The hold comes first."," Write the row first and a crash before the hold leaves a ",[825,2835,1751],{}," Run holding an orphan approval. The retry of that delivery reads the start key, answers ",[825,2838,1735],{}," and returns. The reaper then re-dispatches the ",[825,2841,1751],{}," Run, and it executes with nobody having approved it. That is a policy bypass, and nothing reports it: the person's inbox card resolves into no waiter.",[807,2844,2845,2846,2848,2849,2851,2852,2856,2857,1798,2860,2862,2863,2865,2866,2868],{},"Hold first and every crash leaves the Run ",[825,2847,889],{}," and undispatched. A ",[825,2850,889],{}," Run is a stall ",[810,2853,2855],{"href":2854},"#the-wait-sweep","the wait sweep"," ends, and never a start nobody approved. ",[825,2858,2859],{},"runs_waiting_ref_shape",[825,2861,900],{}," to a ",[825,2864,889],{}," row and admits a null one, so step 1 is a legal row. ",[825,2867,2632],{}," takes every non-terminal status, so step 3 is a re-aim and not a second statement.",[807,2870,2871,2879,2880,2883,2884,2887,2888,2890],{},[847,2872,1787,2873,2875,2876,2878],{},[825,2874,889],{}," Run whose ",[825,2877,900],{}," is null is a real state."," It means the approval was never written. The dispatch is the last write, so no function run ever sees it. ",[810,2881,2882],{"href":2854},"The wait sweep"," is its only reader, and it ends that Run under ",[825,2885,2886],{},"wait_abandoned",". Step 1 writes ",[825,2889,2758],{}," as the deadline, so that ending arrives one grace window later and not one approval TTL later.",[807,2892,2893,2899,2900,2903,2904,1330,2906,2909],{},[847,2894,2895,2896,2898],{},"The approval write can raise, and ",[825,2897,1509],{}," may not."," Four callers branch on a closed answer, and one of them is an Inngest step. So the write is caught and the Run ends under ",[825,2901,2902],{},"approval_unwritable",". The Run already reads ",[825,2905,889],{},[825,2907,2908],{},"LEGAL_FROM['fail']"," takes that status. The code is a platform stop: a rule asked for a person, the database refused the row, and nobody decided anything.",[2235,2911,2913],{"id":2912},"the-day-cap-is-checked-before-the-expensive-work","The day cap is checked before the expensive work",[807,2915,2916],{},"Admission has two halves, and they sit either side of the Run row on purpose.",[2270,2918,2919,2932],{},[2273,2920,2921],{},[2276,2922,2923,2926,2929],{},[2279,2924,2925],{},"Half",[2279,2927,2928],{},"Needs",[2279,2930,2931],{},"Runs",[2323,2933,2934,2948],{},[2276,2935,2936,2939,2942],{},[2328,2937,2938],{},"the organization day cap",[2328,2940,2941],{},"the organization ID only",[2328,2943,2944,2947],{},[847,2945,2946],{},"before"," the resolve and the freeze",[2276,2949,2950,2953,2956],{},[2328,2951,2952],{},"the grant, the rules, the definition state",[2328,2954,2955],{},"the definition and the principal",[2328,2957,2958],{},"after the Run row exists",[807,2960,2961],{},"Five hundred triggers firing against a spent day cap is the case that decides this. Check the cap after the freeze and each one resolves a definition, mints a principal, freezes a snapshot and writes a Run row, only to be refused. Check it first and each one costs one indexed query.",[807,2963,2964,2967,2968,2971,2972,2975,2976,2978,2979,2785,2981,1330,2984,2986,2987,2990],{},[847,2965,2966],{},"A refusal here creates no Run."," There is nothing to attach it to, so the denial is recorded as a ",[825,2969,2970],{},"agent.policy_decisions"," row with ",[825,2973,2974],{},"run_id"," null, exactly as the front door records a usage row with ",[825,2977,2974],{}," null for a turn that started no Run. ",[825,2980,1359],{},[825,2982,2983],{},"organization_budget_exhausted",[810,2985,266],{"href":261}," counts it as a ",[825,2988,2989],{},"budget"," skip.",[807,2992,2993,2996,2997,965],{},[847,2994,2995],{},"The two calls sit either side of the insert, and each writes at most one row."," The day cap half writes nothing when it allows: admission records one row for each run and the engine writes that row after the insert, so a row here would double the count for every start. A refusal writes the one row this log holds with a null ",[825,2998,2974],{},[807,3000,3001,924,3004,3007,3008,3011,3012,3015,3016,3007,3018,3011,3020,3023,3024,3026,3027,3030,3031,3034,3035,3037,3038,3041],{},[847,3002,3003],{},"A fault is not a spent cap.",[825,3005,3006],{},"AccrualChecker"," answers ",[825,3009,3010],{},"deny"," under ",[825,3013,3014],{},"metering_unavailable"," when the meter did not read, and ",[825,3017,1973],{},[825,3019,3010],{},[825,3021,3022],{},"policy_unavailable"," when the rule set or the live rights did not read. Read as ",[825,3025,2983],{}," a pooler outage refuses every start of that moment as a spent day cap, the API answers ",[825,3028,3029],{},"429"," with a ",[825,3032,3033],{},"Retry-After"," measured for a budget, and a workflow node tolerates it. So the day cap half answers ",[825,3036,3022],{},", which is a ",[825,3039,3040],{},"503",", and the admission half ends the run under the fault code itself.",[807,3043,3044],{},"That is the one denial with no Run behind it. Every other admission outcome has a Run row, because everything else needs the definition to decide.",[2235,3046,960],{"id":3047},"the-insert-is-the-claim",[807,3049,3050],{},"Two writes cannot both happen here, and one of them must carry the duplicate guard.",[817,3052,3055],{"className":3053,"code":3054,"language":822,"meta":823},[820],"resolve -> freeze -> INSERT agent.runs\n                          ✗ the worker dies here\n                     complete a claim in a second table   never runs\n",[825,3056,3054],{"__ignoreMap":823},[807,3058,3059,3060,3063],{},"A separate claim table needs those two writes in one transaction, or the retry inserts a ",[847,3061,3062],{},"second"," Run. Both would be valid, and both would dispatch: one Slack message, two runs.",[807,3065,3066],{},[847,3067,3068],{},"So the Run row carries its own start key, and the insert is the claim.",[817,3070,3072],{"className":2254,"code":3071,"language":2256,"meta":823,"style":823},"INSERT INTO agent.runs (organization_id, idempotency_key, ...) VALUES (...)\nON CONFLICT (organization_id, idempotency_key) DO NOTHING\nRETURNING id\n",[825,3073,3074,3079,3084],{"__ignoreMap":823},[1130,3075,3076],{"class":1132,"line":22},[1130,3077,3078],{},"INSERT INTO agent.runs (organization_id, idempotency_key, ...) VALUES (...)\n",[1130,3080,3081],{"class":1132,"line":32},[1130,3082,3083],{},"ON CONFLICT (organization_id, idempotency_key) DO NOTHING\n",[1130,3085,3086],{"class":1132,"line":233},[1130,3087,3088],{},"RETURNING id\n",[807,3090,3091,3092,3095,3096,3098],{},"One statement is atomic on its own, so there is no transaction to lose. ",[825,3093,3094],{},"RETURNING"," an empty set means another delivery already created the Run; read that row and answer ",[825,3097,1735],{},". A crash before the insert leaves nothing, and the retry starts exactly one Run.",[807,3100,3101],{},"Three things disappear with the second table, and each was carrying real weight.",[3103,3104,3105,3119,3131],"ul",{},[3106,3107,3108,3118],"li",{},[847,3109,3110,3111,3114,3115,965],{},"No ",[825,3112,3113],{},"processing"," state, so no ",[825,3116,3117],{},"start_in_progress"," A claim in a second table has three answers, and the third one is a race: the claim exists, the Run does not, and the caller can be told nothing useful. Here the row and the guard are the same row, so a duplicate always reads a committed Run.",[3106,3120,3121,924,3124,3126,3127,3130],{},[847,3122,3123],{},"No transaction, so no second database driver.",[825,3125,1093],{}," reaches Postgres through PostgREST, which cannot span two statements. A start that needed a transaction would need an ",[825,3128,3129],{},"asyncpg"," pool beside it, and RLS would stop applying on that path.",[3106,3132,3133,3143,3144,3146],{},[847,3134,3110,3135,3138,3139,3142],{},[825,3136,3137],{},"complete()"," and no ",[825,3140,3141],{},"release()"," on the start path."," A refusal after a claim would otherwise strand it in ",[825,3145,3113],{},", and every refusal branch would need a release that is easy to forget. Here a refusal writes nothing at all.",[807,3148,3149,3151,3152,928,3155,3158,3159,3162,3163,965],{},[825,3150,964],{}," protects ",[825,3153,3154],{},"tool.\u003Cname>",[825,3156,3157],{},"webhook.\u003Cprovider>"," and the\nbounded ",[825,3160,3161],{},"surface.saved_search.start"," input freeze. Tools and webhooks protect\nan effect that is not a row we own. The saved-search scope freezes mutable\nproduct input before admission. None claims the Run. A Run start protects a row\nwe own, and its unique index remains the admission guarantee. See\n",[810,3164,200],{"href":269},[807,3166,3167,3172,3173,2069,3175,3177,3178,3180,3181,3184,3185,3188],{},[847,3168,1787,3169,3171],{},[825,3170,1735],{}," sends no dispatch event, and that is a rule rather than an omission."," The stable event ID dedupes for 24 hours, so a redelivery on day two would reach Inngest as a new event and start a second worker on a Run that may still be ",[825,3174,1062],{},[825,3176,1502],{}," is re-entrant, so neither worker refuses. The recovery path for a send that never landed is the reaper, which re-dispatches a ",[825,3179,1751],{}," Run and fails it on the next pass, and it is the only recovery path. Reading ",[825,3182,3183],{},"started -> dispatch"," and adding ",[825,3186,3187],{},"duplicate -> dispatch too, in case the first send was lost"," is the natural mistake, and it is the one this paragraph exists to stop.",[807,3190,3191],{},"Two consequences follow from the insert carrying the guard, and both are accepted.",[3103,3193,3194,3200],{},[3106,3195,3196,3199],{},[847,3197,3198],{},"A duplicate pays for the resolve and the freeze it then throws away."," The window is milliseconds and the work is reads, so this is cheaper than the transaction it replaces.",[3106,3201,3202,3205,3206,3209],{},[847,3203,3204],{},"The start key is unique for the life of the Run row, not for 24 hours."," Every source the design names is already globally unique: a Slack message ID, a provider delivery ID, a ",[825,3207,3208],{},"trigger:\u003Cid>:event:\u003Cid>"," pair, and a client request ID, which is the one the caller writes. A caller that reuses a key gets the first Run back, which is what the key asked for.",[807,3211,3212,3215],{},[847,3213,3214],{},"The key names the delivery, not the command."," It must satisfy two rules at once, and they pull in opposite directions.",[817,3217,3220],{"className":3218,"code":3219,"language":822,"meta":823},[820],"same intended start  ->  same key      a redelivered Slack message\nnext intended start  ->  new key       tomorrow's run of a daily trigger\n",[825,3221,3219],{"__ignoreMap":823},[807,3223,3224,3225,3228],{},"A key that carries ",[847,3226,3227],{},"only"," the command satisfies the first rule and breaks the second: a schedule sends an identical command every day, so day two collides with day one and never runs. The row is kept for thirteen months, so that collision does not age out. A key that carries a timestamp or a random value satisfies the second and breaks the first, which is no guard at all.",[807,3230,3231,3232,3234],{},"Every source the design names already answers both, because each carries a delivery identity: a Slack message ID, a provider delivery ID, ",[825,3233,3208],{},", and a client request ID. The first three are minted by the platform. The fourth is caller text, and the next rule is what contains it.",[807,3236,3237,924,3242,1867,3245,3248,3249,3252,3253,3256],{},[847,3238,3239,3241],{},[825,3240,1359],{}," hashes the source and what the caller supplies, and stores the hash.",[825,3243,3244],{},"agent.runs.idempotency_key",[825,3246,3247],{},"CHECK (char_length BETWEEN 1 AND 255)",", because an oversized value fails the unique btree with ",[825,3250,3251],{},"index row size exceeds btree version 4 maximum",". One of the four sources is caller text: an HTTP ",[825,3254,3255],{},"Idempotency-Key"," header is whatever the client sends. A hash gives every key one width, so no caller can reach the check, and the value stays a pure function of the source and the delivery identity, which is all the key promises.",[807,3258,3259,3262,3263,3266,3267,3270,3271,3273,3274,3277,3278,3281],{},[847,3260,3261],{},"The source is part of the key, and the caller text is why."," The four sources share one unique index, ",[825,3264,3265],{},"(organization_id, idempotency_key)",", and only three of them are minted by the platform. Hash the delivery alone and a person sends ",[825,3268,3269],{},"Idempotency-Key: trigger:42:event:2026-08-22"," from the API. That is tomorrow's key for trigger 42 in the same organization. Tomorrow the trigger inserts, conflicts, reads back a Run it did not start, and reports ",[825,3272,1735],{},". The scheduled Run never executes and nothing raises. So the digest covers ",[825,3275,3276],{},"\u003Csource>"," and the delivery identity together, separated by a ",[825,3279,3280],{},"NUL",", and the four namespaces cannot meet.",[807,3283,3284,3285,3288,3289,3292,3293,3295],{},"The guard is the ",[847,3286,3287],{},"source",", not the separator. ",[825,3290,3291],{},"RunSource.kind"," is a closed set of four values and the platform chooses it, so the prefix is fixed before any caller text is appended and the join is injective. The ",[825,3294,3280],{}," is a second line of defence: it cannot appear in an HTTP header, and Postgres refuses it in text. A source that ever took caller text would lose the guard, and the separator would not save it.",[807,3297,3298,3305,3306,3309],{},[847,3299,3300,3301,3304],{},"The hash is ",[825,3302,3303],{},"sha256",", hex encoded."," Python's built-in ",[825,3307,3308],{},"hash()"," is salted per process, so two dynos would produce two keys for one delivery and the guard would stop guarding with nothing raised. A stable, named digest is the only kind that works here, and 64 hex characters sit well inside the 255 the check allows.",[807,3311,3312,924,3315,3318,3319,3321,3322,3324],{},[847,3313,3314],{},"The conflicting delivery waits for the first one to commit, so the read-back always finds the row.",[825,3316,3317],{},"ON CONFLICT DO NOTHING"," blocks on an in-progress insert of the same key and returns only after that transaction ends. Measured on the local stack: the second statement returned an empty set after 4.0 seconds against a 4 second first transaction, and the following select read the committed Run. So the duplicate branch needs no retry, and twenty concurrent starts answer one ",[825,3320,1743],{}," and nineteen ",[825,3323,1735],{},". Each start is its own PostgREST request and its own short transaction, so the wait is the length of one insert.",[807,3326,3327,3330,3331,3333],{},[847,3328,3329],{},"The definition is never in the key."," A duplicate Slack delivery re-runs the front door, and the model may select a different definition the second time. Fold ",[825,3332,934],{}," in and the same message starts a second Run under a different definition, which is the exact thing the key exists to stop.",[2235,3335,3337],{"id":3336},"a-cancelled-parent-refuses-its-next-child","A cancelled parent refuses its next child",[807,3339,3340,3343,3344,3347],{},[825,3341,3342],{},"RunManager.cancel()"," writes a control row for the Run and every descendant ",[847,3345,3346],{},"that exists",". A node that starts a child immediately after that write creates one with no row.",[817,3349,3352],{"className":3350,"code":3351,"language":822,"meta":823},[820],"node N reads agent.run_control                  -> clear\n        cancel lands, and enumerates the descendants it can see\nnode N calls start(parent_run_id=P)       -> the child would run to completion\n",[825,3353,3351],{"__ignoreMap":823},[807,3355,3356,3357,3359,3360,3363,3364,3366],{},"Safe boundaries sit between nodes, so the window is small. It is not zero, and the cost is a real send from a Run a person stopped. ",[825,3358,1509],{}," therefore reads the ",[847,3361,3362],{},"parent Run status"," and refuses with ",[825,3365,2141],{},". It is one primary key read on a path that already does four.",[807,3368,3369,3372,3373,3375,3376,3378,3379,3381,3382,3384,3385,928,3387,937,3389,3391],{},[847,3370,3371],{},"That read returns three columns, not one."," The child copies ",[825,3374,866],{}," from its parent, and ",[825,3377,2025],{}," plus ",[825,3380,2061],{}," both refuse the row when the two disagree. The parent's ",[825,3383,931],{}," must match the child's for the same reason. So the one read selects ",[825,3386,2644],{},[825,3388,866],{},[825,3390,931],{}," together. Written as a status check alone it becomes two reads of one row, and the second one is added by whoever discovers the constraint.",[807,3393,3394,3400,3401,3403,3404,3406,3407,3409],{},[847,3395,3396,3397,3399],{},"Refuse on ",[825,3398,1329],{},", and on nothing else."," A parent that reads ",[825,3402,889],{}," is ordinary: a ",[825,3405,2681],{}," node holds one branch on an approval while another branch starts its child. A rule of \"refuse unless the parent is ",[825,3408,1062],{},"\" therefore breaks the container the design added waits for.",[807,3411,3412,3415,3416,3418,3419,3421],{},[847,3413,3414],{},"It reads the status, not the control row, because the cancel writes the status first."," The two live one statement apart, so for that instant a cancelled parent already reads ",[825,3417,1329],{}," and still has no control row. A child admitted in that window would run to completion. The split also matches how often each caller reads: ",[825,3420,1509],{}," runs once per child and can afford the Run row, while the executor reads the sparse control row about forty times per Run and must never touch the heap.",[807,3423,3424,3425,3427],{},"The dispatch event carries a stable ID, so a retry inside ",[825,3426,1359],{}," cannot start a second worker on one Run.",[817,3429,3431],{"className":1124,"code":3430,"language":1126,"meta":823,"style":823},"await inngest.send(Event(\n    name='agent\u002Frun.execute',\n    id=f'run.execute:{run_id}',        # Inngest drops the duplicate send\n    data={'run_id': str(run_id), 'organization_id': str(org_id), 'lane': lane},\n))\n",[825,3432,3433,3438,3443,3448,3453],{"__ignoreMap":823},[1130,3434,3435],{"class":1132,"line":22},[1130,3436,3437],{},"await inngest.send(Event(\n",[1130,3439,3440],{"class":1132,"line":32},[1130,3441,3442],{},"    name='agent\u002Frun.execute',\n",[1130,3444,3445],{"class":1132,"line":233},[1130,3446,3447],{},"    id=f'run.execute:{run_id}',        # Inngest drops the duplicate send\n",[1130,3449,3450],{"class":1132,"line":244},[1130,3451,3452],{},"    data={'run_id': str(run_id), 'organization_id': str(org_id), 'lane': lane},\n",[1130,3454,3455],{"class":1132,"line":264},[1130,3456,3457],{},"))\n",[807,3459,3460,3462],{},[825,3461,1502],{}," is re-entrant on purpose, so it would admit that second worker. The event ID is what stops it arriving.",[807,3464,3465],{},"The Run row exists before any wait. The product then has one durable unit to show, to cancel and to resume.",[2235,3467,3469],{"id":3468},"a-child-run-is-invoked-never-dispatched","A child Run is invoked, never dispatched",[807,3471,3472,3474,3475,3478],{},[825,3473,1509],{}," dispatches a top level Run. It must ",[847,3476,3477],{},"not"," dispatch a child.",[807,3480,3481,3482,3485,3486,3488,3489,3492,3493,3495,3496,3498],{},"A workflow node starts its child with the same ",[825,3483,3484],{},"StartRunCommand",", carrying ",[825,3487,855],{},", and then calls ",[825,3490,3491],{},"step.invoke"," so the parent step holds the child result. If ",[825,3494,1509],{}," also sent the dispatch event, the child would execute twice: once from the event, once from the invoke. Two workers would claim the same Run, and ",[825,3497,1502],{}," is re-entrant, so neither would refuse.",[817,3500,3502],{"className":1124,"code":3501,"language":1126,"meta":823,"style":823},"result = await run_manager.start(command)     # parent_run_id set -> no dispatch event\noutcome = await step.invoke('node.research', function=run_execute, data={\n    'run_id': str(result.run.id),\n    'organization_id': str(result.run.organization_id),\n    'lane': 'batch',                          # every child is batch; see the lanes above\n})\n",[825,3503,3504,3509,3514,3519,3524,3529],{"__ignoreMap":823},[1130,3505,3506],{"class":1132,"line":22},[1130,3507,3508],{},"result = await run_manager.start(command)     # parent_run_id set -> no dispatch event\n",[1130,3510,3511],{"class":1132,"line":32},[1130,3512,3513],{},"outcome = await step.invoke('node.research', function=run_execute, data={\n",[1130,3515,3516],{"class":1132,"line":233},[1130,3517,3518],{},"    'run_id': str(result.run.id),\n",[1130,3520,3521],{"class":1132,"line":244},[1130,3522,3523],{},"    'organization_id': str(result.run.organization_id),\n",[1130,3525,3526],{"class":1132,"line":264},[1130,3527,3528],{},"    'lane': 'batch',                          # every child is batch; see the lanes above\n",[1130,3530,3531],{"class":1132,"line":222},[1130,3532,3533],{},"})\n",[807,3535,3536,924,3539,3542,3543,3546,3547,3550,3551,3553,3554,3557,3558,3561],{},[847,3537,3538],{},"The invoke payload carries the same three fields the dispatch event does, and it is not decoration.",[825,3540,3541],{},"run.execute"," keys its concurrency on ",[825,3544,3545],{},"event.data.organization_id + \":\" + event.data.lane",", and an invoked function reads ",[825,3548,3549],{},"event.data"," from exactly this dictionary. Send ",[825,3552,2974],{}," alone and both fields are absent: every child Run in the platform falls into one bucket, and the batch lane stops existing for the runs that most need it. The ",[825,3555,3556],{},"Cancel"," expression reads ",[825,3559,3560],{},"event.data.run_id"," and would keep working, which is what makes this fail quietly rather than loudly.",[807,3563,3564,3570,3571,3574],{},[847,3565,3566,3567,965],{},"A child is always ",[825,3568,3569],{},"batch"," The lane follows the actor kind, and a ",[825,3572,3573],{},"workflow_step"," child is machine work whatever started the tree. A child that inherited an interactive lane would let one person's workflow fan out five hundred children into the budget other people are waiting in.",[807,3576,3577,3578,3585],{},"The rule is one line: ",[847,3579,3580,2135,3582,3584],{},[825,3581,1509],{},[825,3583,855],{}," is null."," The caller does not choose. A caller-supplied flag is one refactor away from a child that dispatches.",[807,3587,3588],{},"An admission approval on a child still works. The invoked function waits before its first segment, exactly as a dispatched one does, and the parent step waits with it.",[807,3590,3591,3594,3595,3598],{},[847,3592,3593],{},"An admission approval dispatches too."," It looks wasteful, and it is the only way the approval gets an expiry owner. The wait timeout is the writer that resolves an approval to ",[825,3596,3597],{},"expired",", so a Run that waits without a waiter never ends. The dispatched function waits before its first segment, holds no worker while it waits, and costs nothing until a person answers.",[828,3600,3602],{"id":3601},"the-inngest-boundary","The Inngest boundary",[807,3604,3605],{},"This is the part that decides what a crash costs.",[807,3607,3608],{},[847,3609,3610],{},"One Inngest function runs one Run.",[817,3612,3614],{"className":1124,"code":3613,"language":1126,"meta":823,"style":823},"@inngest_client.create_function(\n    fn_id='run.execute',\n    trigger=TriggerEvent(event='agent\u002Frun.execute'),\n    cancel=[Cancel(event='agent\u002Frun.cancelled', if_exp='async.data.run_id == event.data.run_id')],\n    concurrency=[Concurrency(\n        key='event.data.organization_id + \":\" + event.data.lane',\n        limit=settings.run_concurrency,\n    )],\n)\nasync def run_execute(ctx: Context) -> dict: ...\n",[825,3615,3616,3621,3626,3631,3636,3641,3646,3651,3656,3661],{"__ignoreMap":823},[1130,3617,3618],{"class":1132,"line":22},[1130,3619,3620],{},"@inngest_client.create_function(\n",[1130,3622,3623],{"class":1132,"line":32},[1130,3624,3625],{},"    fn_id='run.execute',\n",[1130,3627,3628],{"class":1132,"line":233},[1130,3629,3630],{},"    trigger=TriggerEvent(event='agent\u002Frun.execute'),\n",[1130,3632,3633],{"class":1132,"line":244},[1130,3634,3635],{},"    cancel=[Cancel(event='agent\u002Frun.cancelled', if_exp='async.data.run_id == event.data.run_id')],\n",[1130,3637,3638],{"class":1132,"line":264},[1130,3639,3640],{},"    concurrency=[Concurrency(\n",[1130,3642,3643],{"class":1132,"line":222},[1130,3644,3645],{},"        key='event.data.organization_id + \":\" + event.data.lane',\n",[1130,3647,3648],{"class":1132,"line":360},[1130,3649,3650],{},"        limit=settings.run_concurrency,\n",[1130,3652,3653],{"class":1132,"line":368},[1130,3654,3655],{},"    )],\n",[1130,3657,3658],{"class":1132,"line":375},[1130,3659,3660],{},")\n",[1130,3662,3663],{"class":1132,"line":157},[1130,3664,3665],{},"async def run_execute(ctx: Context) -> dict: ...\n",[807,3667,3668],{},[847,3669,3670],{},"Two lanes, so a batch cannot starve a person.",[817,3672,3675],{"className":3673,"code":3674,"language":822,"meta":823},[820],"lane = interactive     an interactive user actor: front_door, api\nlane = batch           a machine actor, or any child: trigger, workflow_step\n",[825,3676,3674],{"__ignoreMap":823},[807,3678,3679,924,3682,3684],{},[847,3680,3681],{},"The lane follows the actor, not the source alone.",[825,3683,254],{}," was interactive when the API meant a person pressing Run. It is not interactive when a script or an outside AI agent starts five hundred runs on a user seat, and those runs would then share the budget a person is waiting in.",[807,3686,3687,3690,3691,1480,3694,1970,3697,3699,3700,3702,3703,965],{},[825,3688,3689],{},"ActorIdentity.kind"," decides it. Today that is ",[825,3692,3693],{},"user",[825,3695,3696],{},"trigger",[825,3698,254],{}," under a user actor stays interactive, and a user's own bulk job contends only with itself. When machine identity exists, an ",[825,3701,254],{}," run under a machine actor takes the batch lane with no further change here. See ",[810,3704,3705],{"href":219},"agent access",[807,3707,3708,3709,3711],{},"One key on ",[825,3710,931],{}," alone lets a 500 person email sequence fill the whole budget, and a person waiting in chat then queues behind it. It is Inngest configuration, not a second limiter.",[807,3713,3714,3720,3721,3723,3724,3726,3727,3729,3730,3732,3733,3736],{},[847,3715,3716,3717,3719],{},"The lane is a column on ",[825,3718,836],{},", and not only a field on the event."," The reaper re-dispatches a queued Run whose event never landed, and it must rebuild that event. The row holds no actor — only ",[825,3722,3287],{}," — and ",[825,3725,3287],{}," gives the same answer as the actor only until machine identity exists. A reaper that derived the lane would then repair machine runs into the lane a person waits in, and no test would fail. ",[825,3728,1359],{}," writes the column at the insert, from the same value the dispatch event carries, so the two cannot disagree. The default is ",[825,3731,3569],{},": a value nobody wrote must not read as interactive. It takes no ",[825,3734,3735],{},"authenticated"," grant, because queue state is infrastructure telemetry.",[807,3738,3739,3740,3743],{},"Inside it, every unit that costs money or touches the world is its own ",[825,3741,3742],{},"step.run",". Inngest memoizes a completed step, so a retry replays the result instead of doing the work again.",[807,3745,3746,3747,3750,3751,3753,3754,3757,3758,3760],{},"⚠️ ",[847,3748,3749],{},"A step handler must return a JSON value, and the pseudocode on this page does not."," The SDK memoizes what a handler answers, so no step may return a ",[825,3752,1494],{}," or an ",[825,3755,3756],{},"ExecutionOutcome",". Each one returns a mapping and the loop reads it back. Measured while building ENG-2077: ",[825,3759,3742],{},"'s own signature says the handler \"MUST return a JSON-serializable value\". Two consequences are easy to miss.",[3103,3762,3763,3772],{},[3106,3764,3765,3768,3769,3771],{},[847,3766,3767],{},"The claim cannot carry the run."," A wide ",[825,3770,1494],{}," holds the frozen snapshot, so memoizing it puts the whole prompt surface into Inngest step state on every claim. The claim answers the few fields the loop branches on, and the segment re-reads the row — which it does anyway, because two columns move between segments.",[3106,3773,3774,3780,3781,3784,3785,1330,3787,3790,3791,3794],{},[847,3775,3776,3779],{},[825,3777,3778],{},"dataclasses.asdict"," is not a serializer."," It leaves a ",[825,3782,3783],{},"UUID"," a ",[825,3786,3783],{},[825,3788,3789],{},"RunError.span_id"," is one. A segment that failed with a span attributed to it then fails to serialize ",[1108,3792,3793],{},"after"," the work was done, and the retry redoes the segment for the same ending.",[2270,3796,3797,3807],{},[2273,3798,3799],{},[2276,3800,3801,3804],{},[2279,3802,3803],{},"Run kind",[2279,3805,3806],{},"Step boundary",[2323,3808,3809,3821,3829],{},[2276,3810,3811,3814],{},[2328,3812,3813],{},"Agent",[2328,3815,3816,3817,3820],{},"One step per ",[847,3818,3819],{},"segment",". A segment is the agent loop up to a stop, an approval, or a ceiling.",[2276,3822,3823,3826],{},[2328,3824,3825],{},"Workflow",[2328,3827,3828],{},"One step per node.",[2276,3830,3831,3834],{},[2328,3832,3833],{},"Both",[2328,3835,3836],{},"One step for the claim, one for the finalize.",[807,3838,3746,3839,3842,3843,3846,3847,3850,3851,3853,3854,3856,3857,3860,3861,3864,3865,937,3868,3870],{},[847,3840,3841],{},"A workflow walk may not sit inside a step of this function."," The SDK refuses a nested step and answers ",[825,3844,3845],{},"STEP_NESTED",", which is ",[847,3848,3849],{},"not retriable",", so the first node step of the first workflow run would fail that run for ever. ",[825,3852,3541],{}," therefore reads ",[825,3855,1025],{}," off the memoized claim and branches ",[847,3858,3859],{},"above every step",": an agent run enters the segment loop, and a workflow run walks between the claim and the finalize. Measured on the Python SDK: ",[825,3862,3863],{},"ReportedStep.__aenter__"," raises ",[825,3866,3867],{},"NestedStepInterrupt",[825,3869,3742],{}," converts it to the coded error.",[807,3872,3873,3874,3876],{},"The claim answers the kind for the same reason it answers ",[825,3875,1263],{}," — the branch must be decided before any step exists, and a value re-read between two passes could move.",[807,3878,3879,3882,3883,3885,3886,3889,3890,3893,3894,3897],{},[847,3880,3881],{},"The function answers three keys, and a parent Run reads them."," A workflow ",[825,3884,1801],{}," node maps this mapping onto its own node outcome: ",[825,3887,3888],{},"result.output"," becomes ",[825,3891,3892],{},"steps.\u003Cnode id>.output",", which is what a later node resolves a reference against, and ",[825,3895,3896],{},"error"," becomes the node failure.",[817,3899,3902],{"className":3900,"code":3901,"language":822,"meta":823},[820],"{\"status\": \"succeeded\", \"result\": {\"summary\": ..., \"output\": {...}}, \"error\": null}\n",[825,3903,3901],{"__ignoreMap":823},[807,3905,3906,3907,3910,3911,3914],{},"Answering the status alone empties every ",[825,3908,3909],{},"steps.\u003Cid>.output"," in the tree and replaces every child failure with ",[825,3912,3913],{},"child_failed",". The exit that claims nothing answers the same three keys, with an error code naming that the Run had already ended.",[807,3916,3917,3918,3920],{},"A child Run uses ",[825,3919,3491],{},". The parent step stays durable while the child runs, and the child result comes back to the parent. The parent does not poll.",[807,3922,3923,3926,3927,3930,3931,928,3933,1480,3936,3939],{},[847,3924,3925],{},"This depends on one Inngest guarantee, so state it."," Inngest concurrency limits ",[1108,3928,3929],{},"active code execution",", not function runs: a run suspended on ",[825,3932,3491],{},[825,3934,3935],{},"step.wait_for_event",[825,3937,3938],{},"step.sleep"," holds no slot. A parent that invokes a child therefore releases its slot while the child executes.",[807,3941,3942,3943,3946],{},"If that were not true, a workflow tree would deadlock at the limit. Ten parents at a limit of ten would each hold a slot, waiting for ten children that could never get one, and a ",[825,3944,3945],{},"subworkflow"," nests three deep. The deadlock would appear under load and not in testing, so the guarantee is pinned by a contract test rather than trusted.",[807,3948,3949],{},"Concurrency and rate limits come from Inngest flow control, configured from the Policy limits. The runtime owns no second limiter.",[807,3951,3746,3952,3955,3956,937,3959,3962,3963,3966],{},[847,3953,3954],{},"The Run's own span needs an open call and a close call in two steps."," A\nrecorder that offers one context manager cannot open it: the SDK re-executes the\nfunction body once per step, so a span opened outside a step is written again on\nevery replay, and a block that closes where it opens cannot wrap the loop. The\nworkflow coordinator solves this with ",[825,3957,3958],{},"wf.run",[825,3960,3961],{},"wf.end"," steps, and the agent\nfunction needs the same shape. ",[825,3964,3965],{},"run_scope()"," is entered regardless, because it is\ncontextvars alone and costs nothing to re-enter.",[807,3968,3969,924,3972,3974,3975,965],{},[847,3970,3971],{},"Every Inngest function runs on the worker.",[825,3973,3541],{}," is not special, and neither is a reaper that finishes in 200 milliseconds. The platform draws no line between a short function and a long one, because that line has to be judged for every new function and it eventually gets judged wrong. The web process sends events and serves the API, and it serves no Inngest function. See ",[810,3976,3977],{"href":203},"deployment topology",[807,3979,3980,3981,965],{},"The worker holds an Inngest Connect session, so no HTTP router timeout bounds a\nstep. The platform must therefore set its own step limits. One session serves\ntwo Inngest apps: the live stack's functions move onto it in Phase 1 rather\nthan waiting for cutover. See ",[810,3982,3983],{"href":203},"one session, two apps",[807,3985,3986,3989,3990,3993,3994,3997],{},[847,3987,3988],{},"A deploy still bounds a step, and no router is involved."," The session drains the in-flight step on ",[825,3991,3992],{},"SIGTERM",", but the platform kills the dyno 30 seconds later, so any step still running is cut. The segment is the largest step we run and it is the one this hits. Completed steps stay memoized, and the run resumes at the first unfinished step. A completed journal entry stops the repeated effect of its matching tool call. An interrupted entry keeps the journal's lease behavior. The cost is latency, measured at roughly two minutes before the run is re-dispatched. Treat a long segment as ",[847,3995,3996],{},"restartable, not uninterruptible",". The drain is what makes a restart happen; it is not the ceiling on a step. The step budget below is that ceiling, and it is set from this measured cost.",[807,3999,4000,4003,4004,4007],{},[847,4001,4002],{},"The step budget is a ceiling we choose, and it is not the drain."," The drain is a platform fact of 30 seconds. The budget is the longest step this platform declares, and one rule sets it: ",[847,4005,4006],{},"never lose more work to a deploy kill than the restart costs."," Re-dispatch is measured at roughly two minutes, so the budget is two minutes.",[817,4009,4012],{"className":4010,"code":4011,"language":822,"meta":823},[820],"STEP_BUDGET_S = 120\n",[825,4013,4011],{"__ignoreMap":823},[807,4015,4016,4019,4020,4023],{},[825,4017,4018],{},"src\u002Fagentic\u002Fshared\u002Fceilings.py"," holds it. This page owns the number, and every other page links here rather than restating it. It is a ",[825,4021,4022],{},"Final"," constant and not an environment variable: publish validation reads it, so a definition that publishes on staging must publish on production.",[807,4025,4026],{},"Two limits sit inside the budget:",[817,4028,4031],{"className":4029,"code":4030,"language":822,"meta":823},[820],"ToolSpec.timeout_s       one tool call                              enforced\nMAX_SEGMENT_DURATION_S   one agent segment, the largest step we run  90 seconds\n",[825,4032,4030],{"__ignoreMap":823},[807,4034,4035,4036,4039,4040,965],{},"A tool whose ",[825,4037,4038],{},"timeout_s"," exceeds the step budget fails registry validation. See ",[810,4041,4042],{"href":190},"tools and integrations",[807,4044,4045,4048,4049,2069,4052,4055,4056,4058,4059,4062],{},[847,4046,4047],{},"The segment wall clock leaves 30 seconds for cancellation, session cleanup,\nspan cleanup, and the step response."," The durable segment step wraps the\nexisting executor in one ",[825,4050,4051],{},"asyncio.timeout(90)",[825,4053,4054],{},"Segment.exhausted()"," still\nreads ",[825,4057,2762],{},", which bounds the whole Run. The deploy clock is not a\nsecond Run ceiling, so ",[825,4060,4061],{},"RunCeilings"," does not carry it.",[807,4064,4065,4066,4068,4069,1867,4072,4074,4075,4077,4078,4081,4082,965],{},"When this timer expires, the step answers an ",[825,4067,3756],{}," whose\n",[825,4070,4071],{},"next_status",[825,4073,1062],{},". It carries no result, error, wait, or partial\nreason. The loop advances ",[825,4076,2640],{}," in its own durable step and starts\nthe next segment without a wait. A nested ",[825,4079,4080],{},"TimeoutError"," is still a fault: the\nstep treats it as a yield only when its own timeout object reports ",[825,4083,4084],{},"expired()",[2235,4086,4088],{"id":4087},"agent-segments-and-the-replay-journal","Agent segments and the replay journal",[807,4090,4091,4092,4094],{},"Agno runs its whole loop in process. We cannot put each model turn in its own ",[825,4093,3742],{}," without fighting the framework. So one segment is one step, and the segment can restart.",[807,4096,4097,4098,4101],{},"A restarted segment is a problem. The model is not deterministic, and it mints a ",[847,4099,4100],{},"new"," tool call ID on every attempt. A key built from the model tool call ID therefore fails to match, and an approved send happens twice.",[807,4103,4104,4105,4107],{},"The fix is a semantic key. The ",[810,4106,200],{"href":269}," claim is the journal.",[817,4109,4112],{"className":4110,"code":4111,"language":822,"meta":823},[820],"scope = tool.\u003Ctool_name>\nkey   = \u003Crun_id>:\u003Cstep_path>:\u003Cargs_hash>\n",[825,4113,4111],{"__ignoreMap":823},[807,4115,4116,4119,4120,4122,4123,4126],{},[825,4117,4118],{},"step_path"," is the workflow node ID, or the literal ",[825,4121,1801],{}," for a call the model proposed. ",[825,4124,4125],{},"args_hash"," is the hash of the canonical tool arguments.",[817,4128,4131],{"className":4129,"code":4130,"language":822,"meta":823},[820],"segment attempt 1\n  model proposes crm.update(company=X, stage=qualified)\n  ToolInvoker reads  run:agent:hash(...)  -> absent     -> decide\n  ToolInvoker claims run:agent:hash(...)  -> claimed    -> execute -> complete\n  model proposes email.send(...)\n  worker dies\n\nsegment attempt 2\n  model proposes crm.update(company=X, stage=qualified)\n  ToolInvoker reads  run:agent:hash(...)  -> completed  -> return the stored response\n  model proposes email.send(...)\n  ToolInvoker reads                       -> absent     -> decide\n  ToolInvoker claims                      -> claimed    -> execute -> complete\n",[825,4132,4130],{"__ignoreMap":823},[807,4134,3746,4135,4138,4139,4141],{},[847,4136,4137],{},"The read and the claim are two steps, and the checkpoints sit between them."," A single\nstep puts the whole journal answer after the policy checkpoint. A turn that holds two gated\ncalls then never finishes. The claim is the authority, because the read goes stale.\n",[810,4140,191],{"href":190}," owns the rule.",[807,4143,4144],{},"A completed claim already returns its stored response, so the replay needs no table of its own. The model call in attempt 2 is paid for again. That is the accepted cost of one segment step, and the ceilings bound it.",[807,4146,4147,4150],{},[847,4148,4149],{},"A replayed model turn counts."," The tokens were really spent and the meter really recorded them. A ceiling that ignored retries would not bound cost, which is the only thing it is for.",[807,4152,1787,4153,4156,4157,965],{},[847,4154,4155],{},"replayed tool call"," is the opposite case and counts nothing: it made no call, it wrote no usage row, and its span carries ",[825,4158,4159],{},"replayed = true",[807,4161,4162,924,4165,4167],{},[847,4163,4164],{},"Neither count is stored.",[825,4166,836],{}," grows no counter column, because the span tree already holds both facts and a second copy would be one more thing to keep true.",[817,4169,4172],{"className":4170,"code":4171,"language":822,"meta":823},[820],"turns used       count of llm spans of THIS RUN\ntool calls used  count of tool spans of THIS RUN, excluding replayed = true\n",[825,4173,4171],{"__ignoreMap":823},[807,4175,4176,4185,4186,4189,4190,4193,4194,4198],{},[847,4177,4178,4179,4181,4182,4184],{},"Both counts filter on ",[825,4180,2974],{},", never on ",[825,4183,866],{},", because both ceilings are per Run."," The cost ceiling is the one that sums the tree. Read the tree here and a workflow of ten agent children shares one turn budget: the tree passes ",[825,4187,4188],{},"max_agent_turns"," part way down, every later child is handed a remainder of zero, and each one ends on ",[825,4191,4192],{},"ceiling"," without a model call. The workflow reports success and did a fraction of the work. See ",[810,4195,4197],{"href":4196},"#ceilings","Ceilings"," for the split.",[807,4200,4201,4204,4205,4208],{},[825,4202,4203],{},"AgentExecutionResult.turns_used"," is the ",[847,4206,4207],{},"segment's"," count, which the executor needs before the segment ends. The Run total is the query above, and it follows the rule accrual follows: read the durable record, own no counter.",[807,4210,4211,4212,4215],{},"Two identical write calls inside one Run are treated as ",[847,4213,4214],{},"one effect"," on purpose. A workflow that needs the same tool twice uses two nodes, and the node ID separates them.",[807,4217,4218,4221,4222,4224,4225,4228],{},[847,4219,4220],{},"An approval row is keyed the same way, or a replay fills the inbox twice."," A\nsegment can persist an approval and then die before the wait starts. The replay\nreaches the same proposal, and a second ",[825,4223,870],{}," would put two rows in front of\na person for one decision, with only one of them wired to a wait. So the\napproval is claimed on the same semantic key as the effect it guards, and a\nreplay that meets a pending row ",[847,4226,4227],{},"reuses it"," and stops on that id.",[817,4230,4233],{"className":4231,"code":4232,"language":822,"meta":823},[820],"scope = approval.\u003Ctool_name>\nkey   = \u003Crun_id>:\u003Cstep_path>:\u003Cargs_hash>\n",[825,4234,4232],{"__ignoreMap":823},[807,4236,4237,4240,4241,4244,4245,4248],{},[847,4238,4239],{},"A second approval proposed in the same turn is superseded."," A model may\npropose several calls at once, and the first one that needs a person ends the\nsegment. ",[825,4242,4243],{},"AgentExecutor"," closes the segment by cancelling every ",[825,4246,4247],{},"pending","\napproval of the run other than the id the segment stopped on. A row nobody is\nwaiting on must never sit in a person's inbox.",[807,4250,3746,4251,4254,4257,4258,4261,4262,4265,4266,4269],{},[847,4252,4253],{},"The paused path files one row, and the read path is where the race is.",[825,4255,4256],{},"AgnoAgentRuntime._resolve()"," decides the paused proposals one at a time, and\nthe first ",[825,4259,4260],{},"ApprovalRequired"," leaves the loop, so no second row is written\nthere. The framework runs the ",[847,4263,4264],{},"read"," calls of one turn concurrently, and it\nfilters the ",[825,4267,4268],{},"external_execution"," calls out of that set. So two read tools can\nboth file a row before either records the stop.",[807,4271,3746,4272,4275,4276,4279],{},[847,4273,4274],{},"A read tool approval is never the stop."," A read call runs inside the\nframework loop, where there is no pause to carry a decision, so the runtime\nends the run under ",[825,4277,4278],{},"read_tool_needs_approval",". That segment stopped on no\napproval, and every row it left is cancelled.",[807,4281,3746,4282,4285,4286,4288],{},[847,4283,4284],{},"A segment that raises supersedes nothing, and that is the design.","\nInngest replays the whole step, and a paused approval carries the idempotency\nkey the replay reuses. Cancel that row and the replay reads a ",[825,4287,1329],{}," row\nback, then raises on it, and the wait is never answered. The rows a raised\nsegment left keep their own expiry clock.",[807,4290,4291,4292,4294,4295,4297],{},"The filter is the run, and never the root. ",[825,4293,866],{}," there would cancel the\napprovals of the parent and of every sibling, and a sibling branch waits on its\nown row. The statement is conditional on ",[825,4296,4247],{},", so a supersede that races a\nperson's answer touches no row and the decision stands.",[2235,4299,4301],{"id":4300},"segments-and-approvals","Segments and approvals",[807,4303,4304,4305,4307],{},"A tool cannot wait for a person from inside a ",[825,4306,3742],{},". So the segment returns, and the function waits outside it.",[817,4309,4312],{"className":4310,"code":4311,"language":822,"meta":823},[820],"state = step.run('admission.check', read_run_and_approval)   # one memoized step\nif state is not proceed:\n    if state is pending:\n        answered = step.wait_for_event('admission.wait', 'agent\u002Fapproval.resolved',\n                        if_exp='async.data.run_id == event.data.run_id'\n                               f' && async.data.approval_id == \"{approval_id}\"',\n                        timeout=whole_seconds(expires_at - now))\n        if answered:\n            state = step.run('admission.read', read_row)   # which decision\n        if not answered or state is pending:\n            state = step.run('admission.expire', expire)   # the one writer of `expired`\n    ending = admission_ending(state)            # the table below\n    if ending is not proceed:\n        return ending                           # no claim, and no work\n\nrun = step.run('claim', claim)          # None -> the function ends here\nenter run_scope(run)                    # every step below writes spans through it\n\nresumed_approval_id = None              # carried into the segment that follows a wait\n\nloop while segment_index \u003C max_segments:\n    outcome = step.run(f'agent.segment.{n}', run_segment, resumed_approval_id)\n    #   run_segment re-reads the Run row, and writes no lifecycle row of its own\n    if outcome is running:                         # the segment wall clock fired\n        if n + 1 >= max_segments:\n            return succeeded(partial_reason='limit_reached')\n        step.run(f'advance.{n}', advance_segment)  # no wait\n        n += 1\n        continue                                   # keep approval proof, if any\n    handed, resumed_approval_id = resumed_approval_id, None\n    if outcome is not waiting:\n        break\n    # Every refusal below comes before any write. A Run marked `waiting` and\n    # then refused ends with a status it never needed.\n    if handed is not None and outcome.waiting_ref_id == handed:\n        return succeeded(partial_reason='approval_not_actionable')  # asked again\n    if outcome.waiting_on is not approval:      # a delay needs step.sleep\n        return failed('wait_kind_not_implemented')\n    if n + 1 >= max_segments:                   # no segment left to act in\n        return succeeded(partial_reason='limit_reached')\n    step.run(f'wait.mark.{n}', mark_waiting)    # every surface reads `waiting`\n    step.run(f'advance.{n}', advance_segment)   # clears resumed_from_wait\n    state = step.run(f'wait.check.{n}', read_approval)   # closes the pre-wait gap\n    if state is pending:\n        answered = step.wait_for_event(f'wait.{n}', 'agent\u002Fapproval.resolved',\n                            if_exp='async.data.run_id == event.data.run_id'\n                                   f' && async.data.approval_id == \"{outcome.waiting_ref_id}\"',\n                            timeout=whole_seconds(outcome.waiting_expires_at - now))\n        state = 'answered' if answered else step.run(f'wait.expire.{n}', expire)\n    if state is gone:                   # absent, or another tenant's row\n        return failed('approval_row_missing')\n    if state is unusable:               # a person decided, and the clock beat them\n        return succeeded(partial_reason='approval_not_actionable')\n    if state is not answered:           # nobody decided, and nobody will\n        return succeeded(partial_reason='approval_expired')\n    woke = step.run(f'wait.resume.{n}', resume)   # conditional: see resume() below\n    if woke is unowned:                 # another writer ended the Run\n        return cancelled                # writes nothing\n    if woke is not running:             # an approval nobody waits on holds it\n        return failed('orphan_approval')\n    resumed_approval_id = outcome.waiting_ref_id\nstep.run('finalize', finalize)\n",[825,4313,4311],{"__ignoreMap":823},[807,4315,3746,4316,4323,4324,4327,4328,4330],{},[847,4317,4318,4319,4322],{},"Every exit above is the loop's answer, and ",[825,4320,4321],{},"finalize"," applies it."," The\n",[825,4325,4326],{},"return"," statements leave the loop, and never the function. ",[825,4329,4321],{}," is the one\nwriter of the terminal status, so a Run that skipped it would end with nothing to\nend it.",[807,4332,3746,4333,4340,4342,4343,4345,4346,4348,4349,4351],{},[847,4334,4335,4336,4339],{},"The guard tests ",[825,4337,4338],{},"handed is not None",", and it sits above the kind test.",[825,4341,3756],{}," accepts a ",[825,4344,2221],{}," wait that names no ref, so a first pass over\none compares ",[825,4347,2127],{}," against ",[825,4350,2127],{},". Drop the conjunct and that Run reports a\ndecision a person made, in place of the wait this deployment cannot serve.",[807,4353,3746,4354,4357,4358,4361],{},[847,4355,4356],{},"The last test is negative, and that is not a style choice."," Written as\n",[825,4359,4360],{},"if state is over"," a sixth state matches no branch, falls through, and resumes a\nRun on an approval nobody granted. Written this way it reports an ending.",[807,4363,3746,4364,924,4367,4370,4371,4373,4374,4377,4378,4380,4381,4383,4384,4386],{},[847,4365,4366],{},"A wait that expired is not a wait that was answered.",[825,4368,4369],{},"wait_for_event","\nanswers the event, or ",[825,4372,2127],{}," on the timeout. Resuming on both runs the very work\nthe approval was gating, with nobody having agreed to it. A gate that opens by\nitself after an hour gates nothing. An ",[847,4375,4376],{},"action"," approval that expires ends the\nRun as a partial success, because some work already happened, and ",[825,4379,1333],{},"\naccepts ",[825,4382,889],{}," and clears the three itself, so no ",[825,4385,2623],{}," runs on that path.",[807,4388,3746,4389,924,4392,4395],{},[847,4390,4391],{},"A timeout is not proof that nobody answered.",[825,4393,4394],{},"wait.expire"," decides that,\nand the row decides it, not the absent event. The step order below states why,\nand what the two reads around the wait cover.",[807,4397,3746,4398,4401,4402,4405,4406,4409,4410,937,4412,4415,4416,4418],{},[847,4399,4400],{},"The function reads that a decision was made, and never which one."," The\nanswer lives in the approval row, and the runtime reads it there when the next\nsegment hands it ",[825,4403,4404],{},"resumed_approval_id",". A decision read in the function as well\nwould be a second reader of one fact, and the two would disagree the first time\nan event was replayed. The publisher of ",[825,4407,4408],{},"agent\u002Fapproval.resolved"," therefore owes\nboth ",[825,4411,2974],{},[825,4413,4414],{},"approval_id"," on the event: a publisher that omits either\nmatches nothing, the wait times out, and the Run reports that nobody answered\n",[847,4417,3793],{}," a person answered.",[807,4420,4421,4422,4425],{},"The event path itself needs no read of the row. Inngest fires the timeout at the\ndeadline, so an event that reaches the waiter arrived before it, and the\ninclusive boundary of ",[825,4423,4424],{},"decided_in_time()"," covers the same instant.",[807,4427,3746,4428,4431,4432,4434,4435,4437],{},[847,4429,4430],{},"The timeout is whole seconds, and at least one."," The SDK rejects a\nduration under a second and one that is not a whole number of seconds, and a\ndeadline minus ",[825,4433,2758],{}," carries microseconds. The raise lands outside every step,\nafter the hold already moved the Run to ",[825,4436,889],{},", so the function retries, fails\nthe same way. The wait sweep is what ends the Run, one grace window past the\ndeadline the hold wrote.",[807,4439,3746,4440,4445,4446,4448,4449,4452,4453,4456],{},[847,4441,4442,4443,965],{},"The wait spends ",[825,4444,2762],{}," That ceiling is wall clock from\n",[825,4447,2754],{}," and it counts the waits, so a person who answers an hour later can\nleave the segment after approval with nothing left. ",[825,4450,4451],{},"ApprovalService.create()"," caps\n",[825,4454,4455],{},"expires_at"," at the run's own deadline for that reason, so the deadline this\nwait reads already sits inside the run's wall clock. The wait computes no\nsecond cap: one approval has one clock.",[807,4458,3746,4459,4464,4465,4467,4468,4470,4471,1330,4474,4477,4478,4481,4482,965],{},[847,4460,1787,4461,4463],{},[825,4462,889],{}," Run has two owners, and the second one is a clock."," The live function run is the first. ",[825,4466,2623],{}," clears ",[825,4469,900],{},", so the approval id lives in the memoized step output alone, and losing that function run leaves the row with no writer at all. The Run therefore carries ",[825,4472,4473],{},"waiting_expires_at",[825,4475,4476],{},"run.reaper"," runs a ",[847,4479,4480],{},"wait sweep"," over it. See ",[810,4483,2855],{"href":2854},[807,4485,4486,924,4489,4467,4491,4493,4494,4496,4497,4499,4500,4503],{},[847,4487,4488],{},"The approval id reaches the next segment through the function, not the Run\nrow.",[825,4490,2623],{},[825,4492,900],{}," on the branch that wakes the Run, and\nthe other branch re-aims it at a different approval. The constraint\n",[825,4495,2859],{}," forbids a ",[825,4498,1062],{}," Run from holding one, so the column\nis null by the time the segment starts. The id lives in the memoized outcome of\nthe segment that stopped, and the loop hands it to the next step. The\nalternative is a query over ",[825,4501,4502],{},"agent.approvals"," for the newest resolved row of this\nRun, which is a second source of truth for a value the caller already holds.",[807,4505,4506,4509,4510,937,4512,4514,4515,965],{},[847,4507,4508],{},"The run scope is entered after the claim and around the loop."," It is a context\nmanager, so a scope entered inside the claim step is gone when segment 1 starts.\nIt also needs ",[825,4511,866],{},[825,4513,851],{},", which only the Run row carries,\nso it cannot be entered before the claim either. See\n",[810,4516,4517],{"href":277},"observability and operations",[807,4519,3746,4520,4527,4528,4530,4531,4533,4534,4536],{},[847,4521,4522,937,4524,4526],{},[825,4523,2632],{},[825,4525,2636],{}," are called by the Inngest function, and by nothing below it."," No executor calls either. Each is its own step, and the order is the whole mechanism, because three writes in one step are not atomic: a failure after the hold re-runs the body, the executor re-reads a row that now says ",[825,4529,889],{},", and it answers ",[825,4532,1329],{}," — which writes nothing, so the run sits at ",[825,4535,889],{}," with no wait pending and no writer.",[817,4538,4541],{"className":4539,"code":4540,"language":822,"meta":823},[820],"step  segment.n      run the segment, and nothing else\nstep  wait.mark.n    mark_waiting(), so every surface reads `waiting` while it waits\nstep  advance.n      advance_segment(), which also clears resumed_from_wait\nstep  wait.check.n   read the row; a decision already made skips the wait\n      wait.n         wait_for_event\nstep  wait.expire.n  on the timeout: resolve(expired), then read the row back\nstep  wait.resume.n  resume(), which sets resumed_from_wait\n",[825,4542,4540],{"__ignoreMap":823},[807,4544,3746,4545,4548,4549,4552,4553,4556,4557,4560],{},[847,4546,4547],{},"One wait is not guaranteed delivery."," A wait catches only what arrives\nafter it registers, and ",[825,4550,4551],{},"segment.n"," commits the approval row three steps\nearlier. A person who answers in that gap sends into no waiter, and Inngest\ndrops the event. That person is never told: the press answered ",[825,4554,4555],{},"200"," and the\ninbox reads resolved, so nobody presses again. A retry on ",[825,4558,4559],{},"wait.mark.n"," widens\nthe gap to minutes.",[807,4562,4563,4564,4567,4568,4570,4571,4574,4575,4577],{},"Two reads close it, and neither is a poll loop. ",[825,4565,4566],{},"wait.check.n"," reads the row\nonce ",[847,4569,2946],{}," the wait registers, so an answer that arrived in the gap skips\nthe wait outright. ",[825,4572,4573],{},"wait.expire.n"," reads it once ",[847,4576,3793],{}," the wait ends, so an\nanswer lost to any residual gap is still honoured.",[807,4579,4580,4581,4583,4584,4586],{},"A smaller gap survives between ",[825,4582,4566],{}," and the pause registering, because\nthe step returns to the server in between. It costs latency and never an answer:\nthe wait runs its whole timeout, and ",[825,4585,4573],{}," then reads the row that\ncarries the decision. The Run resumes late rather than never.",[807,4588,4589,4592,4593,4596],{},[825,4590,4591],{},".claude\u002Frules\u002F12-inngest.md"," §4 names a bounded loop of short waits for this\nshape. It does not fit here: ",[825,4594,4595],{},"DEFAULT_APPROVAL_TTL"," is 24 hours, so 30-second\nslices are 5,760 steps against Inngest's cap of 1,000 for the whole run. The two\nreads above are constant at any TTL and lose no answer.",[807,4598,3746,4599,4604,4605,4607,4608,4611,4612,4614,4615,4617],{},[847,4600,4601,4602,965],{},"The wait timeout is the one writer of ",[825,4603,3597],{}," There is no sweeper job.\n",[825,4606,4573],{}," calls ",[825,4609,4610],{},"ApprovalService.resolve(expired)",", whose update is\nconditional on ",[825,4613,4247],{},", so this call and a person who presses Approve in the\nsame second produce one transition. The loser reads the winner's row back. Skip\nthe write and the row stays ",[825,4616,4247],{}," for ever, because its expiry writer is a\nwait that has already returned.",[807,4619,4620,4623,4626,4627,4630,4631,4633,4634,4637,4638,4640],{},[847,4621,4622],{},"Both reads answer whether a decision was made in time, and never which one.",[825,4624,4625],{},"ApprovalService.authorizes"," refuses a row whose ",[825,4628,4629],{},"resolved_at"," is past its\n",[825,4632,4455],{},". A wait that resumed on such a row would meet a live policy\ndecision and show that person a second card, on a Run whose own clock has\nalready run out. ",[825,4635,4636],{},"unusable"," ends the Run instead. One predicate,\n",[825,4639,4424],{},", serves both readers.",[807,4642,3746,4643,924,4646,4648,4649,4651,4652,4655,4656,4659,4660,4663,4664,4666,4667,4669],{},[847,4644,4645],{},"One path still reaches that second card, and it is the clock-skew race\nbelow.",[825,4647,4455],{}," comes from the worker and ",[825,4650,4629],{}," from the database,\nso a press inside that skew arrives as an ",[847,4653,4654],{},"event",", which proves a decision and\nskips the row read. The next segment decides the call again and files a fresh\nproposal, because ENG-2156 released the claim of a terminal row. ",[825,4657,4658],{},"_expiry"," gives\nthat proposal ",[825,4661,4662],{},"min(now + approval_ttl, run_deadline)",", so it ends two ways. The\nRun deadline binds: the card is already dead, the wait times out at once, and the\nRun ends ",[825,4665,1292],{}," rather than ",[825,4668,1296],{},". The TTL binds:\nthe card is live and the person answers once more. Both are bounded by one extra\nsegment, on a window of milliseconds, and both cost less than a durable read on\nevery answered approval.",[807,4671,4672,924,4675,4677],{},[847,4673,4674],{},"One look at the row answers one of five states. Three of them end the Run, and\nno two of them end it the same way.",[825,4676,1292],{}," says a person did not\nanswer in time, so every ending that reports it must be one where nobody\nanswered.",[2270,4679,4680,4693],{},[2273,4681,4682],{},[2276,4683,4684,4687,4690],{},[2279,4685,4686],{},"State",[2279,4688,4689],{},"Row",[2279,4691,4692],{},"Ending",[2323,4694,4695,4708,4720,4735,4751],{},[2276,4696,4697,4702,4705],{},[2328,4698,4699],{},[825,4700,4701],{},"answered",[2328,4703,4704],{},"a person decided, inside the clock",[2328,4706,4707],{},"the next segment runs",[2276,4709,4710,4714,4717],{},[2328,4711,4712],{},[825,4713,4247],{},[2328,4715,4716],{},"a person still holds it",[2328,4718,4719],{},"the wait registers",[2276,4721,4722,4726,4729],{},[2328,4723,4724],{},[825,4725,4636],{},[2328,4727,4728],{},"a person decided, and the clock beat them",[2328,4730,4731,928,4733],{},[825,4732,1240],{},[825,4734,1296],{},[2276,4736,4737,4742,4745],{},[2328,4738,4739],{},[825,4740,4741],{},"over",[2328,4743,4744],{},"no decision this Run can name",[2328,4746,4747,928,4749],{},[825,4748,1240],{},[825,4750,1292],{},[2276,4752,4753,4758,4761],{},[2328,4754,4755],{},[825,4756,4757],{},"gone",[2328,4759,4760],{},"absent, or another tenant's row",[2328,4762,4763,928,4765],{},[825,4764,1232],{},[825,4766,4767],{},"approval_row_missing",[807,4769,4770,4775,4777],{},[847,4771,4772,4774],{},[825,4773,4701],{}," is not an ending, and the row does not decide what follows it.",[825,4776,2623],{}," does. So the wait leaves this function six ways, and only one of them\nruns another segment.",[2270,4779,4780,4790],{},[2273,4781,4782],{},[2276,4783,4784,4787],{},[2279,4785,4786],{},"The row, then the resume",[2279,4788,4789],{},"The Run",[2323,4791,4792,4799,4811,4821,4832,4843],{},[2276,4793,4794,4797],{},[2328,4795,4796],{},"decided in time, and the Run woke",[2328,4798,4707],{},[2276,4800,4801,4804],{},[2328,4802,4803],{},"decided in time, and a wait still holds it",[2328,4805,4806,928,4808],{},[825,4807,1232],{},[825,4809,4810],{},"orphan_approval",[2276,4812,4813,4816],{},[2328,4814,4815],{},"decided in time, and another writer ended it",[2328,4817,4818,4820],{},[825,4819,1329],{},", writing nothing",[2276,4822,4823,4826],{},[2328,4824,4825],{},"decided after its clock",[2328,4827,4828,928,4830],{},[825,4829,1240],{},[825,4831,1296],{},[2276,4833,4834,4837],{},[2328,4835,4836],{},"nobody decided",[2328,4838,4839,928,4841],{},[825,4840,1240],{},[825,4842,1292],{},[2276,4844,4845,4848],{},[2328,4846,4847],{},"the row is gone",[2328,4849,4850,928,4852],{},[825,4851,1232],{},[825,4853,4767],{},[807,4855,4856,4857,4859],{},"A clock ending is a partial success. A defect is the other case, and both\n",[825,4858,1232],{}," rows are defects.",[807,4861,4862,937,4864,4866,4867,1318],{},[825,4863,4636],{},[825,4865,4757],{}," each exist for one reason: reporting an expiry for them\ntells an operator the opposite of what happened. A person whose inbox card reads\n",[825,4868,1317],{},[807,4870,4871,4876,4877,4879,4880,4883,4884,4886],{},[847,4872,4873,4875],{},[825,4874,4741],{}," also takes the row that records no decision time."," A resolved row\nwhose ",[825,4878,4629],{}," is null is a row ",[825,4881,4882],{},"approvals_resolution_time"," forbids, and\nthis path knows nothing about when that person decided. ",[825,4885,4636],{}," would report\nthat they decided late, which is the one fact the row is missing, so it fails\nclosed into the neutral ending instead.",[807,4888,4889,924,4894,4897,4898,4900],{},[847,4890,4891,4893],{},[825,4892,4757],{}," is also the tenancy boundary of this path.",[825,4895,4896],{},"ApprovalService.get()","\nreads by id alone and the client holds the service role, so no policy filters\nthe row. The read compares ",[825,4899,931],{}," itself, and another tenant's row\nanswers exactly as an absent one does. Any other answer would tell the holder of\nan approval id which rows exist in other tenants.",[807,4902,4903,924,4906,4909,4910,4912,4913,4916,4917,4920],{},[847,4904,4905],{},"A segment never re-asks for the decision it was given.",[825,4907,4908],{},"authorizes"," takes\n",[825,4911,1317],{}," alone, so a rejected decision skips no checkpoint and the call is\ndecided live. ",[825,4914,4915],{},"ToolInvoker"," answers it from the row: a rejection of this exact\ncall returns a ",[825,4918,4919],{},"rejected"," result and files nothing, and every other refusal files\na fresh proposal, which carries a new id. Both were one row before ENG-2156,\nbecause the idempotency key held every row of one claim.",[807,4922,4923,4924,4926],{},"The loop still carries the id it handed the segment, and a segment that parks on\nthat same id ends the Run with ",[825,4925,1296],{},". It is a guard on the\ntwo writers above rather than the ordinary path: without it a Run would spend\nevery segment it has left on one call, and the person would never be asked a\nsecond time.",[807,4928,4929,924,4932,4934,4935,937,4938,4940,4941,4944],{},[847,4930,4931],{},"The advance comes before the resume, and reversing them is silent.",[825,4933,2636],{}," is the only writer that clears ",[825,4936,4937],{},"resumed_from_wait",[825,4939,2623],{}," is the one that sets it, so an advance placed after the resume clears the flag before any segment reads it, and a definition with ",[825,4942,4943],{},"refresh_on_resume"," never rebuilds its brief.",[807,4946,4947,4952,4953,4955,4956,4958,4959,2069,4961,4963,4964,4966],{},[847,4948,4949,4950,965],{},"Every segment that continues ends with ",[825,4951,2636],{}," It is the one writer of ",[825,4954,2640],{},", and it is the ",[847,4957,3227],{}," thing that clears ",[825,4960,4937],{},[825,4962,1502],{}," clears the flag too, but ",[825,4965,1502],{}," runs once per function run, so it cannot clear it between segment 3 and segment 4. Leave the flag set and every later segment rebuilds the context brief, on a Run where nothing moved.",[807,4968,4969,4970,928,4973,4976,4977,4980,4981,4983],{},"It is conditional on the index it was handed ",[847,4971,4972],{},"and on the Run not having ended",[825,4974,4975],{},"WHERE segment_index = :from_index AND status IN ('running','waiting')",". The status list is ",[825,4978,4979],{},"LEGAL_FROM['advance_segment']",", so it drops ",[825,4982,1751],{}," like the table above. The index guard stops a step that commits and then crashes before Inngest memoizes it from skipping a segment on the retry. The status guard keeps it inside the rule every other lifecycle write follows: a Run cancelled mid segment must not be advanced afterwards.",[807,4985,4986,924,4991,4993,4994,4996,4997,4999,5000,5002,5003,5005,5006,5008],{},[847,4987,4988,4989,965],{},"Every wait ends with ",[825,4990,2623],{},[825,4992,2632],{}," moved the Run to ",[825,4995,889],{}," and set ",[825,4998,900],{},". Without the matching call the Run executes its next segment while every surface still reads ",[825,5001,889],{},", pointing at an approval that was resolved. The call is not always a wake: the three answers below say what it did, and only one of them runs another segment. ",[825,5004,2623],{}," is the inverse of ",[825,5007,2632],{},", and the two are always written as a pair.",[817,5010,5012],{"className":2254,"code":5011,"language":2256,"meta":823,"style":823},"UPDATE agent.runs\n   SET status = 'running',\n       waiting_on = NULL,               -- the waiting shape constraint rejects the row otherwise\n       waiting_ref_id = NULL,\n       resumed_from_wait = true,        -- the next segment rebuilds the brief; advance_segment clears it\n       heartbeat_at = now()\n WHERE id = :run_id AND status = 'waiting'\n",[825,5013,5014,5018,5022,5026,5030,5035,5039],{"__ignoreMap":823},[1130,5015,5016],{"class":1132,"line":22},[1130,5017,2697],{},[1130,5019,5020],{"class":1132,"line":32},[1130,5021,2702],{},[1130,5023,5024],{"class":1132,"line":233},[1130,5025,2707],{},[1130,5027,5028],{"class":1132,"line":244},[1130,5029,2712],{},[1130,5031,5032],{"class":1132,"line":264},[1130,5033,5034],{},"       resumed_from_wait = true,        -- the next segment rebuilds the brief; advance_segment clears it\n",[1130,5036,5037],{"class":1132,"line":222},[1130,5038,2732],{},[1130,5040,5041],{"class":1132,"line":360},[1130,5042,5043],{}," WHERE id = :run_id AND status = 'waiting'\n",[807,5045,5046,924,5051,5054,5055,5058,5059,3864,5061,5063,5064,928,5066,937,5068,5070],{},[847,5047,5048,5050],{},[825,5049,2623],{}," clears the waiting three on the branch that wakes the Run, and it is the write that most often forgets to.",[825,5052,5053],{},"runs_waiting_shape"," is an equivalence, so ",[825,5056,5057],{},"status='running'"," beside a surviving ",[825,5060,896],{},[825,5062,1813],{}," and the Run never wakes. ",[825,5065,2225],{},[825,5067,1333],{},[825,5069,2628],{}," carry the same line for the same reason.",[807,5072,5073,5081,5082,5084,5085,5087],{},[847,5074,5075,5077,5078,5080],{},[825,5076,2623],{}," is conditional, because a ",[825,5079,2681],{}," node can hold two waits at once."," Branch B resolving while branch C still waits must not report the Run as ",[825,5083,1062],{},": the next node of B would run against a Run every surface reads as awake, while a person still has C in their inbox. So ",[825,5086,2623],{}," re-reads the outstanding waits of the Run.",[2270,5089,5090,5103],{},[2273,5091,5092],{},[2276,5093,5094,5097,5100],{},[2279,5095,5096],{},"Waits left",[2279,5098,5099],{},"What it writes",[2279,5101,5102],{},"Status after",[2323,5104,5105,5120],{},[2276,5106,5107,5110,5116],{},[2328,5108,5109],{},"none",[2328,5111,5112,5113],{},"the statement above, and ",[825,5114,5115],{},"resumed_from_wait = true",[2328,5117,5118],{},[825,5119,1062],{},[2276,5121,5122,5125,5130],{},[2328,5123,5124],{},"one or more",[2328,5126,5127,5129],{},[825,5128,900],{}," moves to the oldest",[2328,5131,5132,5134],{},[825,5133,889],{},", unchanged",[807,5136,5137,5138,5140,5141,5143,5144,5146],{},"The second branch is ",[825,5139,2632],{}," again, so ",[825,5142,1359],{}," gains no new statement: that method takes every non-terminal status and targets ",[825,5145,889],{},", which is exactly a re-aim. It is idempotent, so a duplicate resolve re-reads the same waits and writes the same row.",[807,5148,3746,5149,5154],{},[847,5150,5151,5153],{},[825,5152,4937],{}," is written on the waking branch alone."," It tells the next segment to rebuild its context brief, and no segment runs next when the Run stays asleep. Set it on the re-aim and the brief is rebuilt on a Run where nothing moved.",[807,5156,3746,5157,5160,5161,5163,5164,5166,5167,5169],{},[847,5158,5159],{},"The clock is half the read."," A row that reads ",[825,5162,4247],{}," past its ",[825,5165,4455],{}," is expired, exactly as the inbox queue treats it. Answer that row here and the Run holds at ",[825,5168,889],{}," for ever, pointed at an approval no wait will ever resolve.",[807,5171,3746,5172,5177,5178,5180,5181,5183,5184,5186,5187,5189,5190,5192,5193,5195],{},[847,5173,5174,5175,965],{},"The agent loop reads the answer, and stops on anything but ",[825,5176,1062],{}," A segment holds one wait at a time, so it normally reads ",[825,5179,1062],{}," and continues. A segment that left a second ",[825,5182,4247],{}," row points this at that row, and the loop must not run its next segment then: ",[825,5185,4243],{}," re-reads a Run that is not ",[825,5188,1062],{}," and answers ",[825,5191,1329],{},", which writes nothing, so the Run would sit at ",[825,5194,889],{}," with no wait pending and no terminal writer.",[807,5197,5198,5201,5202,5204],{},[847,5199,5200],{},"The segment supersede is not the guard, because it is best effort."," It catches and logs every fault of its own, on purpose: a row nobody waits on must not turn a finished segment into a failure. So a stale ",[825,5203,4247],{}," row surviving a segment is a state the platform accepts, and the answer below is what stops the loop.",[807,5206,5207,5209],{},[825,5208,2623],{}," therefore answers three states, and it reads them off the Run and never off the read of its waits.",[2270,5211,5212,5224],{},[2273,5213,5214],{},[2276,5215,5216,5219,5221],{},[2279,5217,5218],{},"Answer",[2279,5220,4789],{},[2279,5222,5223],{},"What the loop does",[2323,5225,5226,5238,5252],{},[2276,5227,5228,5232,5235],{},[2328,5229,5230],{},[825,5231,1062],{},[2328,5233,5234],{},"woke, by this call or by an earlier attempt of it",[2328,5236,5237],{},"runs the next segment",[2276,5239,5240,5244,5247],{},[2328,5241,5242],{},[825,5243,889],{},[2328,5245,5246],{},"re-aimed at another wait",[2328,5248,5249,5250],{},"fails the Run, ",[825,5251,4810],{},[2276,5253,5254,5259,5262],{},[2328,5255,5256],{},[825,5257,5258],{},"unowned",[2328,5260,5261],{},"gone or terminal",[2328,5263,5264,5265,5267],{},"ends ",[825,5266,1329],{},", and writes nothing",[807,5269,3746,5270,924,5273,5275,5276,5278],{},[847,5271,5272],{},"A write that matched no row is three different facts.",[825,5274,2306],{}," takes ",[825,5277,889],{}," alone, so a zero-row write says only that the Run is no longer waiting.",[3103,5280,5281,5299],{},[3106,5282,5283,5286,5287,5289,5290,5292,5293,5295,5296,5298],{},[847,5284,5285],{},"This step's own committed attempt."," A transport fault after the commit leaves the caller with no answer, Inngest retries the step, and the Run is already ",[825,5288,1062],{}," with its index moved and its waiting three cleared. It is ready to continue. Answer ",[825,5291,5258],{}," here and the loop ends ",[825,5294,1329],{},", which writes nothing: the row then reads ",[825,5297,1062],{}," with no function behind it until the reaper takes it, six hours later in V1.",[3106,5300,5301,924,5304,5306,5307,5309,5310,5312,5313,5315,5316,5319,5320,2069,5323,5325],{},[847,5302,5303],{},"A person cancelling.",[825,5305,2225],{}," writes the status first and clears the approvals last, so a cancel landing between the two reads leaves the read answering a ",[825,5308,4247],{}," row while the row already says ",[825,5311,1329],{},". Report that as ",[825,5314,4810],{}," and one person's Stop becomes a ",[847,5317,5318],{},"platform stop",", so a parent node stops honouring ",[825,5321,5322],{},"continue_on_error",[825,5324,1329],{}," is not a platform stop, and it writes nothing, so the real writer's status stands.",[807,5327,5328,5329,5331,5332,5335],{},"So ",[825,5330,2623],{}," re-reads the Run row on a zero-row write. The count of written rows cannot tell the two apart, and this is the rule ",[825,5333,5334],{},"ResolutionOutcome"," already states for the approval writer: a retry reads its own write as another writer's.",[807,5337,5338,5339,5342,5343,5346,5347,5349,5350,5352,5353,965],{},"The same pairing applies to a workflow ",[825,5340,5341],{},"wait"," node and a workflow ",[825,5344,5345],{},"approval"," node. The admission wait is the one exception: ",[825,5348,1502],{}," follows it, and ",[825,5351,1502],{}," already writes ",[825,5354,1062],{},[807,5356,5357,5360],{},[847,5358,5359],{},"The admission wait reads which decision was made, and every other wait does not."," An action wait reads only that a decision was made, because the next segment reads the row and acts on it. The admission wait has no next segment: an approval runs the claim, and a rejection ends the Run.",[807,5362,5363,5364,5367,5368,5371,5372,5375],{},"So this wait has a third step. ",[825,5365,5366],{},"admission.check"," reads the row before the wait registers, and ",[825,5369,5370],{},"admission.expire"," reads it after the timeout, exactly as the segment loop does. ",[825,5373,5374],{},"admission.read"," is the one the loop has no use for: the event proves a decision and never which one, so the answered arm reads the row as well.",[807,5377,5378,5387,5388,5390,5391,5393,5394,5396],{},[847,5379,5380,5381,5383,5384,5386],{},"A row that still reads ",[825,5382,4247],{}," after the event runs ",[825,5385,5370],{}," too."," A redelivered or hand sent event reaches the waiter while the row is unresolved. This wait is the one writer of ",[825,5389,3597],{}," and it has already returned, so a row left ",[825,5392,4247],{}," there has no writer left and sits in a person's inbox for ever. The expiry update is conditional on ",[825,5395,4247],{},", so a person who answers in the same second still wins and the read-back carries their decision.",[807,5398,3746,5399,924,5402,5405,5406,5408,5409,5411],{},[847,5400,5401],{},"That write can end an approval before its own deadline, and the cost is accepted.",[825,5403,5404],{},"ApprovalService"," publishes ",[825,5407,4408],{}," only after a person decides, so an event that finds a ",[825,5410,4247],{}," row is out of band: a replay, an ops script, or a faulty integration. A wait that has matched cannot register again, so the two available endings are an approval closed early and a row that never closes at all. The early close ends the Run and frees the person to be asked again. The row that never closes ends nothing, and the person is told nothing.",[807,5413,5414],{},[847,5415,5416],{},"One look at the row answers six endings, and one of them runs the Run.",[2270,5418,5419,5428],{},[2273,5420,5421],{},[2276,5422,5423,5426],{},[2279,5424,5425],{},"The row",[2279,5427,4789],{},[2323,5429,5430,5440,5453,5464,5477,5489],{},[2276,5431,5432,5437],{},[2328,5433,5434,5436],{},[825,5435,1317],{},", decided in time",[2328,5438,5439],{},"the claim runs, and the Run executes",[2276,5441,5442,5447],{},[2328,5443,5444,5446],{},[825,5445,1317],{},", decided after its clock",[2328,5448,5449,928,5451],{},[825,5450,1232],{},[825,5452,1296],{},[2276,5454,5455,5460],{},[2328,5456,5457,5459],{},[825,5458,4919],{},", at any time",[2328,5461,5462],{},[825,5463,1329],{},[2276,5465,5466,5471],{},[2328,5467,5468,5470],{},[825,5469,3597],{},", or resolved with no decision time",[2328,5472,5473,928,5475],{},[825,5474,1232],{},[825,5476,1292],{},[2276,5478,5479,5483],{},[2328,5480,5481],{},[825,5482,1329],{},[2328,5484,5485,5486,5488],{},"write nothing; ",[825,5487,3342],{}," owns the ending",[2276,5490,5491,5493],{},[2328,5492,4760],{},[2328,5494,5495,928,5497],{},[825,5496,1232],{},[825,5498,4767],{},[807,5500,5501,5504,5505,5507],{},[847,5502,5503],{},"A rejection needs no clock."," Refusing to act is safe at any time, and a person who pressed Reject must read ",[825,5506,1329],{}," rather than a report that nobody answered. An approval is the opposite: acting on a decision the clock beat is the failure the one-clock rule exists to stop.",[807,5509,5510,5516,5517,5519,5520,5523,5524,5527,5528,5532,5533,5535,5536,5538,5539,5541],{},[847,5511,5512,5513,5515],{},"Nothing ran, so every ending here is ",[825,5514,1232],{}," and never a partial success."," An ",[847,5518,4376],{}," approval that expires is the other case: some work already happened, so that Run ",[847,5521,5522],{},"succeeds"," with ",[825,5525,5526],{},"partial_reason=approval_expired",". This is the one status rule, and ",[810,5529,5531],{"href":5530},"#result-and-error","result and error"," states it. ",[825,5534,1296],{}," therefore reports ",[825,5537,1232],{}," at admission and ",[825,5540,1240],{}," inside the segment loop, from the same row state.",[807,5543,5544,924,5549,5552,5553,5555,5556,5558,5559,5561,5562,5565,5566,5568],{},[847,5545,5546,5548],{},[825,5547,1329],{}," needs a writer of its own.",[825,5550,5551],{},"apply_outcome()"," writes nothing for a ",[825,5554,1329],{}," outcome, because the ordinary cause of one is another writer that already ended the Run. ",[825,5557,3342],{}," does not serve this either: it takes an ",[825,5560,1601],{},", walks the subtree, writes the control rows, and publishes ",[825,5563,5564],{},"agent\u002Frun.cancelled",", which cancels the very function run that called it. So the rejection is one conditional transition to ",[825,5567,1329],{},", in a step of its own, and the outcome that follows it writes nothing.",[807,5570,5571,5574,5575,1330,5578,5580,5581,5583,5584,5586],{},[847,5572,5573],{},"The pre-claim read is one memoized step, and that is not tidiness."," The branch is chosen from ",[825,5576,5577],{},"run.waiting_on",[825,5579,1502],{}," clears that column. Read outside a step and the second execution of this function reads ",[825,5582,1062],{},", skips the branch the first pass took, and the step sequence stops matching. The same step reads the approval row, so it also closes the gap before the wait registers, exactly as ",[825,5585,4566],{}," does inside the loop.",[807,5588,5589,5592,5593,5595,5596,5598,5599,5601],{},[847,5590,5591],{},"Its answer is read with a default, as the claim's answer is."," Inngest replays that mapping on every later invocation of the function run, so a Run held before a deploy re-enters with the previous shape. A bare subscript raises outside every step, and here that raise lands ",[847,5594,2946],{}," the claim: the Run stays ",[825,5597,889],{},", and only the wait sweep ends it, one grace window past its deadline. A shape this deploy cannot read answers ",[825,5600,4767],{},", which is an ending.",[807,5603,5604,5607],{},[847,5605,5606],{},"The read is narrow."," Every Run start reaches this step, and nearly none of them holds an approval. A wide read would carry the frozen snapshot and the Run input over the wire for all of them. The common case costs one indexed read and one durable step.",[807,5609,5610,5613,5614,5616,5617,5619,5620,5622,5623,5625,5626,5628],{},[847,5611,5612],{},"The admission wait comes before the claim."," The Run stays ",[825,5615,889],{},", not ",[825,5618,1062],{},", for as long as a person takes to answer. That ordering is what keeps the heartbeat read away from it: it fails a stale ",[825,5621,1062],{}," Run and a stuck ",[825,5624,1751],{}," Run, and it never takes a ",[825,5627,889],{}," one. The wait sweep does take one, and it reads the Run's own deadline rather than its silence. Claim first and a Run waiting overnight for a decision looks like a dead worker.",[807,5630,5631],{},"The next segment reconstructs the agent from the frozen snapshot, continues the same Agno session identity, and replays the journal. Never serialize an in memory Agno agent as the durable boundary.",[2235,5633,5635],{"id":5634},"where-the-agent-session-lives","Where the agent session lives",[807,5637,5638],{},"A segment resumes on a worker that never saw the first segment. It rebuilds the configuration from the snapshot, and it needs one more thing: the message history the loop already produced. That history is the Agno session, and it needs an owner.",[807,5640,5641],{},[847,5642,5643],{},"Agno writes its session to one table in our database, and the runtime owns that table.",[817,5645,5648],{"className":5646,"code":5647,"language":822,"meta":823},[820],"agent.sessions\n  run_id (pk)          the Run this session belongs to; one session per Run\n  organization_id      the tenancy boundary; RLS reads it\n  agno_state jsonb     AgnoAgentRuntime owns the shape; nothing above it parses it\n  segment_index        the segment that produced the stored history\n  updated_at\n",[825,5649,5647],{"__ignoreMap":823},[807,5651,5652,5658],{},[847,5653,5654,5655,5657],{},"The write cannot go backwards, and ",[825,5656,2640],{}," is why the column exists.","\nInngest delivers at least once, so two workers can hold one segment. A slow worker\ncan finish segment 4 after a second worker finished it and a third finished segment\n5. An unguarded write puts the older history back, the next segment rehydrates a\nconversation that lost its middle, and nothing reports the loss.",[807,5660,5661],{},"Two guards, and they answer two different writers.",[2270,5663,5664,5674],{},[2273,5665,5666],{},[2276,5667,5668,5671],{},[2279,5669,5670],{},"Guard",[2279,5672,5673],{},"Answers",[2323,5675,5676,5693],{},[2276,5677,5678,5681],{},[2328,5679,5680],{},"The repository writes only above the stored index",[2328,5682,5683,5684,5686,5687,2785,5690],{},"a duplicate worker; its ",[825,5685,2244],{}," matches no row, and ",[825,5688,5689],{},"save()",[825,5691,5692],{},"false",[2276,5694,5695,5705],{},[2328,5696,5697,5700,5701,5704],{},[825,5698,5699],{},"assert_session_moves_forward",", a ",[825,5702,5703],{},"BEFORE UPDATE OF segment_index"," trigger",[2328,5706,5707],{},"every writer that never filtered: a psql fix-up, a backfill, a second service",[807,5709,5710,5711,5713,5714,5717,5718,5523,5720,5722,5723,5725],{},"They are not two mechanisms for one fact. The repository write is two statements\nthat are each conditional on their own — an ",[825,5712,2244],{}," filtered on ",[825,5715,5716],{},"segment_index \u003C","\nthe new one, and an ",[825,5719,870],{},[825,5721,3317],{}," for the run that has no\nrow yet — so an ordinary duplicate never reaches the trigger. The trigger exists\nbecause an invariant that lives in one ",[825,5724,2782],{}," clause in one method is one\nforgotten predicate away from silence.",[807,5727,5728,5731],{},[847,5729,5730],{},"The column is an envelope with two halves."," Agno owns one of them.",[817,5733,5736],{"className":5734,"code":5735,"language":822,"meta":823},[820],"agno_state = {\n  'agno':         \u003CAgentSession.to_dict()>,   # opaque, Agno's shape\n  'instructions': '\u003Cthe composed system block>',\n}\n",[825,5737,5735],{"__ignoreMap":823},[807,5739,5740,5741,5744,5745,5748,5749,5753,5754,5756],{},"The second half exists because Agno rebuilds the system message from ",[825,5742,5743],{},"instructions"," on every run and never replays the stored one. A later segment that arrives with ",[825,5746,5747],{},"context = None"," has nothing to keep unless the runtime kept it. See ",[810,5750,5752],{"href":5751},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime\u002Findex","where the session lives"," for why that is the whole system block and not only the brief. Two halves in one column beat a second column: ",[825,5755,976],{}," already shipped, and both halves have the same owner, the same lifetime and the same reader.",[807,5758,5759,5766,5767,5769,5770,5773,5774,5777,5778,5781],{},[847,5760,5761,5762,5765],{},"There is no ",[825,5763,5764],{},"session_id"," column."," Agno takes the session identity from its caller, on ",[825,5768,3813],{}," and on ",[825,5771,5772],{},"run()",", so the runtime passes ",[825,5775,5776],{},"str(run_id)"," and the Agno session identity ",[1108,5779,5780],{},"is"," the Run. A separate column would be a second unique key on a table that already has one, and a second identifier to keep in step with the first.",[807,5783,5784,5785,5788,5789,5792],{},"The column names its owner on purpose. ",[825,5786,5787],{},"state"," would read as platform state, and this is the one column in the platform that our own code may not open. A second runtime would add its own column, and ",[825,5790,5791],{},"AgentRuntime"," would not change.",[807,5794,5795],{},"Three rules make this a runtime table rather than a second store.",[5797,5798,5799,5812,5823],"ol",{},[3106,5800,5801,5804,5805,937,5808,5811],{},[847,5802,5803],{},"One session per Run, keyed on the Run."," The Run is the primary key, so the\none-to-one is a constraint rather than a convention. The session is execution state, so it lives and dies with the Run. It is not conversation state, and it is not memory. ",[825,5806,5807],{},"agent.conversations",[825,5809,5810],{},"agent.memories"," keep their own rows and their own meaning.",[3106,5813,5814,5820,5821,965],{},[847,5815,5816,5817,965],{},"We never read ",[825,5818,5819],{},"agno_state"," Agno writes it, Agno reads it. The platform treats the column as opaque. The moment our code parses it, the framework stops being replaceable, which is the whole point of ",[825,5822,5791],{},[3106,5824,5825,5828,5829,5831],{},[847,5826,5827],{},"It is deleted with its Run."," Retention follows ",[825,5830,836],{},". A finished Run needs no session, and a session that outlives its Run is a leak with a customer's message history in it.",[807,5833,5834,5840],{},[847,5835,5836,5837,965],{},"The runtime writes the session on every exit path that produces an\n",[825,5838,5839],{},"AgentExecutionResult"," Only the retryable-failure path writes nothing. The\nouter segment timer is not a runtime result.",[817,5842,5845],{"className":5843,"code":5844,"language":822,"meta":823},[820],"completed          write the session, then return\nceiling reached    write the session, then return\nneeds_approval     write the session, then return the approval id\ncancelled          write the session, then return\nfailed, terminal   write the session, then return the error\nfailed, retryable  WRITE NOTHING; Inngest replays this step\n",[825,5846,5844],{"__ignoreMap":823},[807,5848,5849,5852,5853,5856,5857,5860],{},[847,5850,5851],{},"A wall-clock yield makes no promise about the session write."," The outer\ntimer can cancel the runtime before a write, while the database call is in\nflight, or after the database accepted it. The next segment therefore handles\nthree states. It continues a saved paused run, starts one stable continuation\nturn from saved history with ",[825,5854,5855],{},"Continue the task from the saved conversation.",",\nor restarts the original Run input when no session exists. Only\n",[825,5858,5859],{},"AgnoAgentRuntime"," inspects the opaque session to choose.",[807,5862,5863],{},"An approval proof survives repeated wall-clock yields. The runtime applies it\nonly when it continues the saved paused run that still needs the decision. If\nthe session holds completed history, or no session exists, the runtime starts a\nfresh turn and drops the proof. A new tool proposal must get its own decision.",[807,5865,5866,5867,5870],{},"The approval path is the one that costs the most when it is wrong. The loop is paused mid run, and the model has already read the results of every call before it. The session is what holds the paused run, so losing it there loses the requirement the continuation needs: ",[825,5868,5869],{},"acontinue_run"," looks the run up by id in the session and raises when it is absent. Even if it were rebuilt, the segment after approval would start from older history, and the model would pay for the same turns again and reason over a conversation that lost its middle.",[807,5872,5873,5876],{},[847,5874,5875],{},"The retryable failure is the opposite case, and writing it is the bug."," Inngest replays the whole step, and a step is a whole segment. A session written from the failed attempt holds a half finished turn, and the replay rehydrates that wreckage as its starting point. Leaving the row alone leaves the clean pre-segment session in place, which is the state a replay is supposed to begin from.",[807,5878,5879,5885,5886,5889,5890,5893,5894,5897,5898,5900],{},[847,5880,5881,5882,5884],{},"Only ",[825,5883,5859],{}," touches the shape of that column."," The refreshed ",[810,5887,5888],{"href":178},"context brief"," arrives as ",[825,5891,5892],{},"AgentExecutionRequest.context",", and the runtime ",[847,5895,5896],{},"replaces the system block inside the session"," before it continues the loop. Nothing above the runtime reads or writes ",[825,5899,5819],{},", because replacing a system block means knowing Agno's message shape, and knowing it above this line is what would make the framework unreplaceable.",[807,5902,5903,5904,5906],{},"A second runtime would bring its own continuation shape. It would get its own table, and ",[825,5905,5791],{}," would stay unchanged.",[828,5908,5910],{"id":5909},"agent-execution","Agent execution",[817,5912,5914],{"className":1124,"code":5913,"language":1126,"meta":823,"style":823},"class AgentExecutor:\n    async def execute(\n        self, claimed: Run, *, resumed_approval_id: UUID | None = None\n    ) -> ExecutionOutcome:\n        run = await run_repository.get(claimed.id, claimed.organization_id)\n        if run is None or run.status != 'running':      # a cancel, or a reaper\n            return ExecutionOutcome.cancelled()\n\n        accrual = await accrual_checker.check(\n            run.organization_id, root_run_id=run.root_run_id,\n            ceilings=run.snapshot.ceilings, scope='run_and_day',\n        )\n        if accrual.outcome != 'allow':\n            return self.stop_short(run, 'budget_exhausted')\n\n        ceilings = await self.remaining(run)\n        if ceilings.exhausted:                          # nothing left to spend\n            return self.stop_short(run, 'limit_reached')\n\n        policy = run.snapshot.context_policy\n        if run.segment_index == 0 or (policy.refresh_on_resume and run.resumed_from_wait):\n            context = await context_builder.build(self.context_request(run), policy)\n        else:\n            context = None                       # reuse the instructions the runtime stored\n\n        segment_input = self.segment_input(run, resumed_approval_id)\n        tools = tool_factory.build(run.snapshot.tools, run.principal)\n        request = AgentExecutionRequest.from_run(run, segment_input, context, tools, ceilings)\n        result = await agent_runtime.execute(request, event_sink.for_run(run.id))\n        return self.to_outcome(run, result)      # AgentExecutionResult -> ExecutionOutcome\n",[825,5915,5916,5921,5926,5931,5936,5941,5946,5951,5955,5960,5965,5970,5975,5980,5985,5989,5994,5999,6004,6008,6013,6018,6023,6029,6035,6040,6046,6052,6058,6064],{"__ignoreMap":823},[1130,5917,5918],{"class":1132,"line":22},[1130,5919,5920],{},"class AgentExecutor:\n",[1130,5922,5923],{"class":1132,"line":32},[1130,5924,5925],{},"    async def execute(\n",[1130,5927,5928],{"class":1132,"line":233},[1130,5929,5930],{},"        self, claimed: Run, *, resumed_approval_id: UUID | None = None\n",[1130,5932,5933],{"class":1132,"line":244},[1130,5934,5935],{},"    ) -> ExecutionOutcome:\n",[1130,5937,5938],{"class":1132,"line":264},[1130,5939,5940],{},"        run = await run_repository.get(claimed.id, claimed.organization_id)\n",[1130,5942,5943],{"class":1132,"line":222},[1130,5944,5945],{},"        if run is None or run.status != 'running':      # a cancel, or a reaper\n",[1130,5947,5948],{"class":1132,"line":360},[1130,5949,5950],{},"            return ExecutionOutcome.cancelled()\n",[1130,5952,5953],{"class":1132,"line":368},[1130,5954,1181],{"emptyLinePlaceholder":1180},[1130,5956,5957],{"class":1132,"line":375},[1130,5958,5959],{},"        accrual = await accrual_checker.check(\n",[1130,5961,5962],{"class":1132,"line":157},[1130,5963,5964],{},"            run.organization_id, root_run_id=run.root_run_id,\n",[1130,5966,5967],{"class":1132,"line":182},[1130,5968,5969],{},"            ceilings=run.snapshot.ceilings, scope='run_and_day',\n",[1130,5971,5972],{"class":1132,"line":290},[1130,5973,5974],{},"        )\n",[1130,5976,5977],{"class":1132,"line":280},[1130,5978,5979],{},"        if accrual.outcome != 'allow':\n",[1130,5981,5982],{"class":1132,"line":272},[1130,5983,5984],{},"            return self.stop_short(run, 'budget_exhausted')\n",[1130,5986,5987],{"class":1132,"line":1203},[1130,5988,1181],{"emptyLinePlaceholder":1180},[1130,5990,5991],{"class":1132,"line":1209},[1130,5992,5993],{},"        ceilings = await self.remaining(run)\n",[1130,5995,5996],{"class":1132,"line":1700},[1130,5997,5998],{},"        if ceilings.exhausted:                          # nothing left to spend\n",[1130,6000,6001],{"class":1132,"line":1706},[1130,6002,6003],{},"            return self.stop_short(run, 'limit_reached')\n",[1130,6005,6006],{"class":1132,"line":1712},[1130,6007,1181],{"emptyLinePlaceholder":1180},[1130,6009,6010],{"class":1132,"line":520},[1130,6011,6012],{},"        policy = run.snapshot.context_policy\n",[1130,6014,6015],{"class":1132,"line":318},[1130,6016,6017],{},"        if run.segment_index == 0 or (policy.refresh_on_resume and run.resumed_from_wait):\n",[1130,6019,6020],{"class":1132,"line":327},[1130,6021,6022],{},"            context = await context_builder.build(self.context_request(run), policy)\n",[1130,6024,6026],{"class":1132,"line":6025},23,[1130,6027,6028],{},"        else:\n",[1130,6030,6032],{"class":1132,"line":6031},24,[1130,6033,6034],{},"            context = None                       # reuse the instructions the runtime stored\n",[1130,6036,6038],{"class":1132,"line":6037},25,[1130,6039,1181],{"emptyLinePlaceholder":1180},[1130,6041,6043],{"class":1132,"line":6042},26,[1130,6044,6045],{},"        segment_input = self.segment_input(run, resumed_approval_id)\n",[1130,6047,6049],{"class":1132,"line":6048},27,[1130,6050,6051],{},"        tools = tool_factory.build(run.snapshot.tools, run.principal)\n",[1130,6053,6055],{"class":1132,"line":6054},28,[1130,6056,6057],{},"        request = AgentExecutionRequest.from_run(run, segment_input, context, tools, ceilings)\n",[1130,6059,6061],{"class":1132,"line":6060},29,[1130,6062,6063],{},"        result = await agent_runtime.execute(request, event_sink.for_run(run.id))\n",[1130,6065,6066],{"class":1132,"line":481},[1130,6067,6068],{},"        return self.to_outcome(run, result)      # AgentExecutionResult -> ExecutionOutcome\n",[807,6070,6071,924,6074,6077,6078,6080,6081,6084,6085,6087,6088,965],{},[847,6072,6073],{},"The last line is a mapping, not a pass through.",[825,6075,6076],{},"agent_runtime.execute()"," answers an ",[825,6079,5839],{},", which says why one segment stopped, and this method is typed ",[825,6082,6083],{},"-> ExecutionOutcome",", which says what ",[825,6086,1359],{}," must do next. Returning the first where the second is declared collapses two of the three result types the design keeps apart, and the stop-to-status table stops having a place to live. See ",[810,6089,6091],{"href":6090},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime#three-result-types-and-why-each-exists","three result types",[807,6093,6094,924,6097,6099,6100,2069,6102,6104,6105,6107,6108,937,6111,6113],{},[847,6095,6096],{},"The segment re-reads the Run row, and the claimed Run is not enough.",[825,6098,1502],{},"\nruns one time per function run and answers one ",[825,6101,1494],{},[825,6103,2640],{}," and\n",[825,6106,4937],{}," move between segments, so the object the claim answered\nreports ",[825,6109,6110],{},"0",[825,6112,5692],{}," for the whole run. Segment 4 reading it rebuilds no brief\nafter a three day approval and advances from the wrong index. The read is one\nprimary key query, and it carries a second fact for free: the status.",[807,6115,6116,6122,6124,6125,6128,6129,6132],{},[847,6117,6118,6119,6121],{},"A status other than ",[825,6120,1062],{}," ends the segment before anything else.",[825,6123,2225],{}," writes the status first and the ",[825,6126,6127],{},"agent.run_control"," row last, so the\nRun row is the earlier signal and it needs no second repository. The control row\nstays the boundary ",[847,6130,6131],{},"inside"," the segment, where the tool adapter reads it about\nforty times per Run. Between segments, the row this step already reads answers\nthe same question.",[807,6134,6135,937,6137,6139,6140,6142,6143,6145,6146,6148],{},[825,6136,2640],{},[825,6138,4937],{}," are Run columns, because the worker that\nreads them is not the worker that wrote them. ",[825,6141,2623],{}," sets ",[825,6144,4937],{},",\nand ",[825,6147,2636],{}," clears it, so the flag describes the transition that woke\nthis segment rather than the whole history of the Run.",[807,6150,6151,6154,6155,6157,6158,6161,6162,6165,6166,965],{},[847,6152,6153],{},"Accrual is the first thing a segment checks, and it is not the remainder."," The\ntwo answer different questions from different records. ",[825,6156,3006],{}," reads the\ncanonical usage meter for ",[825,6159,6160],{},"max_cost_cents"," over the tree, and the organization\nday. ",[825,6163,6164],{},"self.remaining()"," reads the span tree and the wall clock for the four\nceilings this Run owns. A segment that checked only the second would pass the\nmoney ceiling by a whole segment. See\n",[810,6167,1608],{"href":287},[807,6169,6170,6173,6174,6176,6177,6179,6180,6183],{},[847,6171,6172],{},"A stop before the runtime is a failure at segment 0 and a partial success after\nit."," That is the one status rule, and ",[810,6175,5531],{"href":5530},"\nstates it. ",[825,6178,2640],{}," is the record ",[825,6181,6182],{},"_stop_short"," reads it from: a Run that\nnever reached a model call produced no output.",[817,6185,6188],{"className":6186,"code":6187,"language":822,"meta":823},[820],"segment_index == 0    fail(RunError(code=\u003Cthe reason>))\nsegment_index  > 0    succeed(RunResult(partial_reason=\u003Cthe reason>))\n",[825,6189,6187],{"__ignoreMap":823},[807,6191,6192,6193,6195,6196,6198,6199,6201],{},"The reason is ",[825,6194,1284],{}," when the accrual check refused, and\n",[825,6197,1288],{}," when a remainder reached zero. ",[825,6200,1292],{}," is the third\nmember, and the wait writes it rather than the executor.",[807,6203,6204,6213,6214,6216,6217,6220,6221,6223,6224,6226,6227,6229,6230,6232,6233,6235,6236,6239,6240,6242],{},[847,6205,6206,6207,1330,6209,6212],{},"Two places name ",[825,6208,6160],{},[825,6210,6211],{},"RunCeilings.exhausted"," is neither.","\nThat property skips the money field on purpose: the meter answers it, not a\nremainder. The tool adapter records the field name inside the loop when\n",[825,6215,4915],{}," refuses a metered call. ",[825,6218,6219],{},"_reason_for"," then maps that one name to\n",[825,6222,1284],{},", and every other field of ",[825,6225,4061],{}," to ",[825,6228,1288],{},",\nso a run stopped by money never reads as stopped by turns. ",[825,6231,4243],{},"\nanswers ",[825,6234,1284],{}," directly at the segment boundary, from its own\naccrual check. The mark the adapter reads is ",[825,6237,6238],{},"ToolResult.stop",", and\n",[810,6241,4042],{"href":190},"\nowns it.",[807,6244,6245,6250,6251,6254,6255,4204,6258,6261,6262,6265],{},[847,6246,6247,6248,965],{},"A retryable platform fault answers no ",[825,6249,3756],{}," It propagates out of\n",[825,6252,6253],{},"execute()",", and Inngest replays the whole step. A completed matching Tool claim\nreturns its stored result. An interrupted claim keeps its lease and vendor\nrecovery rules. ",[825,6256,6257],{},"stop = failed",[847,6259,6260],{},"terminal"," case only. An executor that caught\nevery exception and mapped it to ",[825,6263,6264],{},"next_status = 'failed'"," would turn a database\noutage into a failed Run that no retry can save.",[807,6267,6268,6271],{},[847,6269,6270],{},"The brief is not rebuilt after every successful segment."," Segment 0 always\nbuilds one. A later segment builds one only when it resumed from a wait and the\npolicy asks for it, which is the case where the world really moved. An Inngest\nreplay of a completed segment uses its memoized result and builds nothing. A\nretry of a failed segment can build the brief again because the step committed\nno result.",[807,6273,6274,6276,6277,6279,6280,6282,6283,6286],{},[825,6275,5747],{}," therefore means \"reuse the instructions you stored\", and it is the ordinary case. It does ",[847,6278,3477],{}," mean \"keep the block Agno has\", because Agno keeps none: it rebuilds the system message from ",[825,6281,5743],{}," on every run. The snapshot froze the ",[825,6284,6285],{},"ContextPolicy",", not the content. Skills were rendered when the snapshot was frozen.",[807,6288,6289,6296,6297,6299],{},[847,6290,6291,6292,6295],{},"Every segment carries an input, because ",[825,6293,6294],{},"Agent.arun(input=...)"," has no default."," Segment 0 carries the Run input. A segment that follows a resolved approval carries the outcome of that approval, and ",[810,6298,4042],{"href":190}," owns how the approved call reaches the loop.",[807,6301,6302,6305,6306,6308],{},[847,6303,6304],{},"A remainder of zero never reaches the runtime."," The executor subtracts first, and a run with nothing left ends on ",[825,6307,4192],{}," without starting a segment. Handing a runtime a ceiling of zero asks it to guess whether zero means none or unlimited, and the two framework knobs that could carry it disagree.",[807,6310,6311,6312,6315,6316,965],{},"Agno never calls a tool handler. ",[825,6313,6314],{},"AgnoToolAdapter"," declares the tool, and the call returns through ",[825,6317,4915],{},[828,6319,6321],{"id":6320},"workflow-execution","Workflow execution",[817,6323,6326],{"className":6324,"code":6325,"language":822,"meta":823},[820],"node type    executes as\ntool         ToolInvoker.invoke  + span\nagent        child Run via step.invoke\nsubworkflow  child Run via step.invoke\nbranch       ConditionEvaluator selects one child + span\nparallel     a fixed set of Inngest steps\nwait         Inngest wait + span\napproval     approval row + Inngest wait + span\n",[825,6327,6325],{"__ignoreMap":823},[807,6329,6330],{},"There is no second workflow state machine beside the Run, step and span model.",[2235,6332,6334,6335,6338],{"id":6333},"the-walk-lives-in-workflowexecutor-not-in-the-function","The walk lives in ",[825,6336,6337],{},"WorkflowExecutor",", not in the function",[807,6340,6341,6342,6344],{},"A workflow is a tree, and the Inngest function is a flat body. A walk written in\nthe function would need a durable stack of its own, so the walk is ordinary\nrecursion inside ",[825,6343,6337],{},". The durability comes from the steps it\ncreates, and from nothing else.",[807,6346,6347,6350],{},[847,6348,6349],{},"The SDK re-executes the function body once per step."," A step with no memoized\nvalue runs its body and then unwinds the function, and the server sends a new\nrequest that re-enters the body from the top. A twenty node workflow therefore\nruns its body about twenty times, and every memoized step returns at once.",[807,6352,6353],{},"Two consequences are load-bearing.",[3103,6355,6356,6362],{},[3106,6357,6358,6361],{},[847,6359,6360],{},"The walk must be deterministic."," The same definition and the same memoized\noutputs must produce the same node order and the same step IDs on every pass.\nA node ID is unique across the whole workflow, so it is the step ID.",[3106,6363,6364,6367],{},[847,6365,6366],{},"Every write a node makes sits inside a step."," A write above the steps\nhappens again on every pass. A span opened there is written twenty times.",[807,6369,6370,6372],{},[825,6371,6337],{}," reaches Inngest through one seam, so the walk is testable with\nno dev server.",[817,6374,6376],{"className":1124,"code":6375,"language":1126,"meta":823,"style":823},"class WorkflowSteps(Protocol):\n    \"\"\"One durable step, one child invoke, and one parallel group.\"\"\"\n\n    async def run(self, step_id: str, body: Callable[[], Awaitable[T]]) -> T: ...\n    async def invoke_run(self, step_id: str, run_id: UUID,\n                         organization_id: UUID) -> dict: ...\n    async def parallel(self, branches: tuple[Callable[[], Awaitable[T]], ...]\n                       ) -> tuple[T, ...]: ...\n",[825,6377,6378,6383,6388,6392,6397,6402,6407,6412],{"__ignoreMap":823},[1130,6379,6380],{"class":1132,"line":22},[1130,6381,6382],{},"class WorkflowSteps(Protocol):\n",[1130,6384,6385],{"class":1132,"line":32},[1130,6386,6387],{},"    \"\"\"One durable step, one child invoke, and one parallel group.\"\"\"\n",[1130,6389,6390],{"class":1132,"line":233},[1130,6391,1181],{"emptyLinePlaceholder":1180},[1130,6393,6394],{"class":1132,"line":244},[1130,6395,6396],{},"    async def run(self, step_id: str, body: Callable[[], Awaitable[T]]) -> T: ...\n",[1130,6398,6399],{"class":1132,"line":264},[1130,6400,6401],{},"    async def invoke_run(self, step_id: str, run_id: UUID,\n",[1130,6403,6404],{"class":1132,"line":222},[1130,6405,6406],{},"                         organization_id: UUID) -> dict: ...\n",[1130,6408,6409],{"class":1132,"line":360},[1130,6410,6411],{},"    async def parallel(self, branches: tuple[Callable[[], Awaitable[T]], ...]\n",[1130,6413,6414],{"class":1132,"line":368},[1130,6415,6416],{},"                       ) -> tuple[T, ...]: ...\n",[807,6418,6419,6420,6423,6424,6427,6428,6430,6431,1330,6434,6437,6438,6441],{},"The seam is built once per function run, so ",[825,6421,6422],{},"WorkflowExecutor.execute(run)"," still\nsatisfies ",[825,6425,6426],{},"SegmentExecutor",". That is also why the coordinator is ",[847,6429,3477],{}," a field\nof the graph the worker caches: it closes over ",[825,6432,6433],{},"ctx.step",[825,6435,6436],{},"ctx"," exists only\ninside one function call. The graph holds a factory instead, and ",[825,6439,6440],{},"executor_for()","\ntakes the seam of this call and passes it through.",[807,6443,6444,6445,6448],{},"The run is read wide again before the walk, ",[847,6446,6447],{},"outside every step",". The walk\nrebuilds its node tree from the frozen snapshot on each pass, and a snapshot\nmemoized into Inngest step state is the fault the claim already avoids. The\ncolumns the walk reads never move, so a later pass reads the same values, and the\ncost is one round trip per pass.",[2235,6450,6452],{"id":6451},"one-node-is-one-step-and-a-child-node-is-three","One node is one step, and a child node is three",[807,6454,1787,6455,6458,6459,6462,6463,6465],{},[825,6456,6457],{},"tool"," node and a ",[825,6460,6461],{},"branch"," node fit in one ",[825,6464,3742],{},": the span, the work and\nthe result all sit inside the memoized body.",[807,6467,6468,6471,6472,6474,6475,6477],{},[847,6469,6470],{},"A child node cannot."," The SDK refuses a nested step, so ",[825,6473,3491],{}," may not\nsit inside ",[825,6476,3742],{},", and the node splits across two.",[817,6479,6482],{"className":6480,"code":6481,"language":822,"meta":823},[820],"step.run     node:\u003Cid>          start(), and answer the child run ID\nstep.invoke  node:\u003Cid>:child    the child function; the parent holds no slot\n",[825,6483,6481],{"__ignoreMap":823},[807,6485,6486,6489,6490,6492],{},[847,6487,6488],{},"Two steps and not three, because the node opens no span."," The child's own\n",[825,6491,860],{}," span is the node's span, so there is nothing to close after the invoke.\nMapping the child's answer onto the node outcome is pure, so the walk rebuilds\nit on every pass rather than spending a step on it.",[807,6494,6495,6496,6498],{},"The first step is the one that must be idempotent, and it already is: the start\nkey is deterministic, so a replay of that step meets ",[825,6497,1735],{}," and reads back\nthe child its earlier attempt created.",[2235,6500,6502],{"id":6501},"the-childs-actor-comes-from-the-parents-principal","The child's actor comes from the parent's Principal",[807,6504,6505,6508,6509,6511,6512,6515,6516,6518,6519,928,6521,928,6524,937,6527,6530],{},[825,6506,6507],{},"RunManager.start()"," needs an ",[825,6510,1601],{}," to mint the child's grant, and\n",[847,6513,6514],{},"nothing at run time carries one",". The Inngest function receives a run ID, a\ntenant and a lane, and ",[825,6517,836],{}," stores a Principal and no actor. So the node\nrebuilds an actor from the parent Run's stored Principal, which already declares\n",[825,6520,931],{},[825,6522,6523],{},"user_id",[825,6525,6526],{},"trigger_id",[825,6528,6529],{},"scopes",". No column is added.",[807,6532,6533,6536,6537,6540],{},[847,6534,6535],{},"The parent's Principal is the right source, and the actor that started the\ntree is not."," The grant is an intersection, so reading the original actor lets\na child node intersect against the rights the ",[847,6538,6539],{},"tree"," started with, and a child\ncan then hold a scope its own parent no longer has. Reading the parent's\nPrincipal narrows the grant at every level, which is what \"a step cannot widen\nauthority\" means.",[807,6542,6543,6545,6546,6549],{},[825,6544,1866],{}," therefore stores the two identifiers beside the scopes.\nIt stores no actor kind: ",[825,6547,6548],{},"Principal.kind"," derives that from the one identifier\nthat is set, and a stored copy is a second thing to disagree. The run row\ncarries the organization, the run and the definition as columns, so the grant\nrepeats none of them.",[807,6551,6552,6555,6556,6558,6559,6562,6563,937,6566,6569],{},[847,6553,6554],{},"A span that wraps more than one step needs an open and a close, not a block.","\nThe ",[825,6557,2681],{}," span and a child node's span both do. ",[825,6560,6561],{},"SpanRecorder"," therefore\noffers ",[825,6564,6565],{},"open_span()",[825,6567,6568],{},"close_span()"," beside the scoped block. Each half sits\nin its own step, so a replay writes neither again. Application code that fits in\none step keeps the block.",[2235,6571,1787,6573,6575],{"id":6572},"a-parallel-node-runs-in-race-mode",[825,6574,2681],{}," node runs in race mode",[807,6577,6578,6581,6582,6585,6586,6589,6590],{},[825,6579,6580],{},"ctx.group.parallel"," is the only parallel primitive of the SDK. ",[825,6583,6584],{},"asyncio.gather","\ndoes not work, because a step signals by raising a ",[825,6587,6588],{},"BaseException"," that gather\npropagates before the sibling steps are discovered. ",[847,6591,6592],{},"A plain loop over the\nbranches is worse: it serialises the node and nothing reports it.",[807,6594,6595],{},"The mode is not a preference either.",[2270,6597,6598,6611],{},[2273,6599,6600],{},[2276,6601,6602,6605,6608],{},[2279,6603,6604],{},"Mode",[2279,6606,6607],{},"Behaviour",[2279,6609,6610],{},"Result here",[2323,6612,6613,6631],{},[2276,6614,6615,6621,6628],{},[2328,6616,6617,6620],{},[825,6618,6619],{},"WAIT",", the default",[2328,6622,6623,6624,6627],{},"one discovery request after ",[847,6625,6626],{},"all"," parallel steps end",[2328,6629,6630],{},"a branch of two steps waits for every other branch at each step",[2276,6632,6633,6638,6644],{},[2328,6634,6635],{},[825,6636,6637],{},"RACE",[2328,6639,6623,6640,6643],{},[847,6641,6642],{},"each"," parallel step ends",[2328,6645,6646],{},"a branch advances on its own",[807,6648,6649,6650,6652,6653],{},"A branch is a container, so almost every branch holds more than one step. Under\n",[825,6651,6619],{}," a branch that parks holds its siblings at their next step boundary, which\nis the exact behaviour \"an approval inside a parallel node\" forbids. ",[847,6654,6655,6656,965],{},"The node\nuses ",[825,6657,6637],{},[807,6659,3746,6660,6663],{},[847,6661,6662],{},"That was measured, not reasoned about."," Against a real dev server, one\nbranch parking on an event nobody sends and one branch of two steps:",[817,6665,6668],{"className":6666,"code":6667,"language":822,"meta":823},[820],"ParallelMode.WAIT   sibling step 1 ran, sibling step 2 never ran\nParallelMode.RACE   both ran, while the first branch was still parked\n",[825,6669,6667],{"__ignoreMap":823},[807,6671,6672,6673,6676],{},"The mocked runner in ",[825,6674,6675],{},"inngest.experimental.mocked"," cannot answer this. It pops\none planned step per pass, so it serialises a parallel group by construction and\nreports the guarantee holding whether it holds or not.",[2235,6678,6680],{"id":6679},"a-node-writes-no-span-of-its-own","A node writes no span of its own",[807,6682,6683],{},"The node table above already names every span a node produces, and three of the\neight write none.",[2270,6685,6686,6700],{},[2273,6687,6688],{},[2276,6689,6690,6693,6696],{},[2279,6691,6692],{},"Node",[2279,6694,6695],{},"Span",[2279,6697,6699],{"align":6698},"right","Written by",[2323,6701,6702,6717,6732,6747,6762,6777,6788],{},[2276,6703,6704,6708,6712],{},[2328,6705,6706],{},[825,6707,6457],{},[2328,6709,6710],{},[825,6711,6457],{},[2328,6713,6714,6716],{"align":6698},[825,6715,4915],{},", which opens it before the checkpoints",[2276,6718,6719,6723,6727],{},[2328,6720,6721],{},[825,6722,6461],{},[2328,6724,6725],{},[825,6726,6461],{},[2328,6728,6729],{"align":6698},[825,6730,6731],{},"WorkflowStepExecutor",[2276,6733,6734,6738,6742],{},[2328,6735,6736],{},[825,6737,2681],{},[2328,6739,6740],{},[825,6741,2681],{},[2328,6743,6744,6746],{"align":6698},[825,6745,6731],{},", open and close in two steps",[2276,6748,6749,6753,6757],{},[2328,6750,6751],{},[825,6752,5341],{},[2328,6754,6755],{},[825,6756,5341],{},[2328,6758,6759,6761],{"align":6698},[825,6760,6731],{},", open in the mark step and close in the resume step",[2276,6763,6764,6768,6772],{},[2328,6765,6766],{},[825,6767,5345],{},[2328,6769,6770],{},[825,6771,5345],{},[2328,6773,6774,6776],{"align":6698},[825,6775,6731],{},", open in the raise step and close in the resume step",[2276,6778,6779,6784,6786],{},[2328,6780,6781],{},[825,6782,6783],{},"sequence",[2328,6785,5109],{},[2328,6787,2334],{"align":6698},[2276,6789,6790,6796,6799],{},[2328,6791,6792,928,6794],{},[825,6793,1801],{},[825,6795,3945],{},[2328,6797,6798],{},"none of its own",[2328,6800,6801,6802,6804],{"align":6698},"the child's ",[825,6803,860],{}," span is the node's span",[807,6806,6807,6810,6811,6813,6814,6816,6817,6819,6820,6823],{},[847,6808,6809],{},"A parking node spends no step on its span."," The ",[825,6812,2681],{}," span needs two steps of its own, because the steps it covers are not its own. A ",[825,6815,5341],{}," node and an ",[825,6818,5345],{}," node already own a step on each side of the pause, so the open rides in the first and the close rides in the last. ",[810,6821,6822],{"href":357},"The workflow visualizer"," reads that span to show how long a Run held for a person.",[807,6825,6826,924,6831,6833,6834,6836],{},[847,6827,1787,6828,6830],{},[825,6829,6457],{}," node opens nothing.",[825,6832,4915],{}," owns the ",[825,6835,6457],{}," span, and a second\none around it would double every tool row in the tree.",[807,6838,6839,6840,6842,6843,6845],{},"A wide node keeps the same rule. Each ",[825,6841,4915],{}," call opens one ",[825,6844,6457],{}," span.\nAn item that stops at a ceiling before the call opens no span. The span count\ntherefore follows started calls and not the input width.",[807,6847,6848,924,6856,1867,6858,6861,6862,6864,6865,6867,6868,6870],{},[847,6849,6850,6851,6458,6853,6855],{},"An ",[825,6852,1801],{},[825,6854,3945],{}," node open nothing either, and the child\nstill joins the tree.",[825,6857,851],{},[825,6859,6860],{},"current_span_id()"," at the node,\nwhich is the enclosing ",[825,6863,2681],{}," span, or the run's own ",[825,6866,860],{}," span. There is no\nninth span kind for a node, and the eight are closed. The child's ",[825,6869,860],{}," span\ncarries the node ID as its name, so a person still reads which node produced\nwhich child.",[817,6872,6875],{"className":6873,"code":6874,"language":822,"meta":823},[820],"workflow run\n └─ span run \"wf.outreach\"                     \u003C- current at a top level node\n    ├─ child run  agent \"classify\"\n    │   └─ span run \"classify\"                    parent = the run span\n    └─ span parallel \"fanout\"                  \u003C- current inside the container\n        └─ child run  agent \"draft\"\n            └─ span run \"draft\"                   parent = the parallel span\n",[825,6876,6874],{"__ignoreMap":823},[2235,6878,1787,6880,6882,6883],{"id":6879},"a-tool-node-spends-max_tool_calls",[825,6881,6457],{}," node spends ",[825,6884,6885],{},"max_tool_calls",[807,6887,6888,6890,6891,6893,6894,6896,6897,6899,6900,6902,6903,6906],{},[825,6889,4915],{}," counts nothing, and the frozen budget is read once per function run,\nso nothing above the node bounds a fan out. ",[825,6892,6731],{}," therefore reads\nthe Run's own ",[825,6895,6457],{}," spans before each ",[825,6898,6457],{}," node and compares the count against\nthe frozen ",[825,6901,6885],{},". That is the rule the ceilings section already states:\na per-Run ceiling counts spans of that Run. It is the same read an agent segment\nmakes, so the two paths cannot disagree about what a tool call is, and a span\nmarked ",[825,6904,6905],{},"replayed"," is dropped by both.",[807,6908,6909,6910,6239,6913,6915],{},"A spent ceiling settles the Run under ",[810,6911,6912],{"href":5530},"the one status rule",[825,6914,5322],{}," does not tolerate it. Tolerated, every node after it would be\nskipped and the run would report success having done a fraction of the work.",[807,6917,6918,6924,6925,6928,6929,6932,6933,6935],{},[847,6919,6920,6921,6923],{},"A wide ",[825,6922,6457],{}," node repeats the call-ceiling check for each item."," Calls that are\nalready in flight can pass the same count. The overshoot is bounded by\n",[825,6926,6927],{},"fanout_concurrency",", not by ",[825,6930,6931],{},"max_fanout",". The node stops scheduling new items when\nthe ceiling answers. It drains the current batch, keeps its ordered settled prefix\nthrough the first stopping input position, and marks the Run partial. A result after\nthat position is discarded even if its call finished first. The call cannot be\nundone, but a later position cannot leave a hole in the ordered output.\n",[825,6934,5322],{}," cannot turn a ceiling into a complete Run.",[2235,6937,6939],{"id":6938},"a-wide-tool-settles-items-not-platform-faults","A wide tool settles items, not platform faults",[807,6941,6942,6943,6945,6946,6948],{},"The scheduler starts batches in input order. It keeps at most\n",[825,6944,6927],{}," item workers in one batch. Each worker has one outer\n",[825,6947,4038],{}," guard around the complete call and result envelope. This is the clock\nthat the publish formula measures. A second outer guard bounds the whole node at\nthe step budget. Publish keeps 10 seconds of that guard outside the item batches.",[2270,6950,6951,6961],{},[2273,6952,6953],{},[2276,6954,6955,6958],{},[2279,6956,6957],{},"Item result",[2279,6959,6960],{},"Scheduler action",[2323,6962,6963,6975,6986,6994,7002,7014],{},[2276,6964,6965,6968],{},[2328,6966,6967],{},"success",[2328,6969,6970,6971,6974],{},"Store ",[825,6972,6973],{},"{\"ok\": true, \"data\": ...}"," in its input position",[2276,6976,6977,6980],{},[2328,6978,6979],{},"business refusal",[2328,6981,6970,6982,6985],{},[825,6983,6984],{},"{\"ok\": false, \"error\": ...}"," and continue",[2276,6987,6988,6991],{},[2328,6989,6990],{},"call or cost ceiling",[2328,6992,6993],{},"Drain the current batch, keep results through the lowest stopping input index, discard later results and stop the Run partial",[2276,6995,6996,6999],{},[2328,6997,6998],{},"policy or platform stop returned as a value",[2328,7000,7001],{},"Drain the current batch and fail the node",[2276,7003,7004,7008],{},[2328,7005,7006],{},[825,7007,4260],{},[2328,7009,7010,7011,7013],{},"Cancel the batch and fail with ",[825,7012,4278],{},"; do not retry",[2276,7015,7016,7019],{},[2328,7017,7018],{},"timeout or another raised platform fault",[2328,7020,7021],{},"Cancel the batch and raise, so Inngest retries the whole step",[807,7023,7024,7027,7028,965],{},[847,7025,7026],{},"A returned hard platform stop wins over a ceiling in the same batch."," It\nfails the node at any input position. A partial result cannot hide an outage or\na runtime defect. If the batch has several returned hard stops, the lowest input\nposition chooses the error. If the batch has only ceiling stops, the lowest\nstopping input position chooses the prefix and its ",[825,7029,1221],{},[807,7031,7032],{},"An exception does not enter this selection. The first observed exception cancels\nthe remaining workers and uses its action in the table. Input order decides only\nbetween returned results, even when calls finish in another order.",[807,7034,7035,7036,7038,7039,7042],{},"An empty list answers an empty list. An invalid list or item fails before the first\ncall. A list over ",[825,7037,6931],{}," also fails before the first call. The final serialized\nenvelope is measured before the step returns. A value above Inngest's 4 MiB\nstep-output limit fails with ",[825,7040,7041],{},"fanout_output_too_large",". It is never cut and reported\nas complete.",[807,7044,7045,7048,7049,7052],{},[847,7046,7047],{},"The whole step is the retry unit."," A raised fault discards every item result from\nthat attempt. The next function attempt repeats the wide read. This can repeat a\nmetered vendor read, but it cannot repeat an effect because Phase 4 permits only\n",[825,7050,7051],{},"side_effects = read",". A write or send fan out stays invalid until each item has a\ndurable claim and partial effects have a recovery rule.",[807,7054,7055,924,7058,7060,7061,7063,7064,7066,7067,7070],{},[847,7056,7057],{},"An approval is not one failed item.",[825,7059,4260],{}," is a control signal and\ncannot sit in a result list. A read approval cannot resume inside a tool node, just\nas it cannot resume inside the Agno loop. The scheduler cancels its batch and fails\nthe node with ",[825,7062,4278],{},". It does not spend a function retry on a\nvalid policy decision. This code is a non-tolerable platform stop for a wide node,\nso ",[825,7065,5322],{}," cannot leave the Run active. The normal terminal cleanup\nsupersedes every pending approval row. Use a ",[825,7068,7069],{},"run.start"," admission rule when a wide\nsearch must wait for a person before it starts. Phase 4 does not park a wide tool\nnode.",[807,7072,3746,7073,7078],{},[847,7074,1787,7075,7077],{},[825,7076,2681],{}," fan out can overshoot, by the calls in flight."," Each branch is\nits own step in its own request, so several branches read the same count before any\nof them opens a span. The span opens before the call, so the overshoot is bounded\nby how many steps the server runs at once, and never by the width of the fan out.\nAn exact bound needs one atomic counter, and no product need asks for one.",[2235,7080,7082],{"id":7081},"a-platform-stop-is-never-tolerated","A platform stop is never tolerated",[807,7084,7085,7087,7088,7091,7092,7094],{},[825,7086,5322],{}," says the author expects ",[847,7089,7090],{},"that step"," to fail sometimes. A node\nthat declares it settles ",[825,7093,1232],{},", and the walk runs the next node. Some failures\nare not that, and the flag must not decide them.",[807,7096,7097,7100,7101,7104,7105,7107],{},[847,7098,7099],{},"The rule: stop when the runtime could not read or could not measure, and tolerate\nwhen the graph, the data or the business answered."," A tool that returned\n",[825,7102,7103],{},"not_found"," answers. A meter that never replied did not answer. A snapshot this\nruntime cannot parse did not answer either, and that node never ran, so nothing\nknows what it would have produced. Tolerated, every later node runs. The Run then\nreports ",[847,7106,6967],{}," during an outage, or on a workflow the runtime could not\nread.",[807,7109,7110,7113,7114,7116],{},[847,7111,7112],{},"The set spans three processes."," A node reads the stored code of a child Run, so\na code the agent runtime or the child's own walk produced decides this parent.\n",[825,7115,6731],{}," cannot import those modules, so it mirrors each literal and\na test pins every one against the module that declares it. A rename on either side\nwould break the match silently.",[807,7118,7119,7122,7123,7125,7126,7129],{},[825,7120,7121],{},"PLATFORM_STOP_CODES"," holds seventeen codes. Seven are raised by this node, and\nevery one can arrive from a child Run. Eleven are declared only in another\nmodule, so ",[825,7124,6731],{}," mirrors each literal. ",[825,7127,7128],{},"invalid_snapshot"," is\nin both groups.",[2270,7131,7132,7145],{},[2273,7133,7134],{},[2276,7135,7136,7139,7142],{},[2279,7137,7138],{},"Code",[2279,7140,7141],{},"Raised by",[2279,7143,7144],{},"Why it stops",[2323,7146,7147,7159,7171,7183,7195,7207,7235,7253,7266,7278,7290,7302,7314,7326,7338,7350,7361],{},[2276,7148,7149,7153,7156],{},[2328,7150,7151],{},[825,7152,3014],{},[2328,7154,7155],{},"this node, a child",[2328,7157,7158],{},"The meter did not answer. No ceiling decided this.",[2276,7160,7161,7166,7168],{},[2328,7162,7163],{},[825,7164,7165],{},"runtime_contract",[2328,7167,7155],{},[2328,7169,7170],{},"The walk broke its own contract, such as a node outside a Run scope.",[2276,7172,7173,7178,7180],{},[2328,7174,7175],{},[825,7176,7177],{},"no_parent_span",[2328,7179,7155],{},[2328,7181,7182],{},"No span was current, and a child Run must hang from one. It carries its own code, so an operator alerts on a severed span tree alone.",[2276,7184,7185,7190,7192],{},[2328,7186,7187],{},[825,7188,7189],{},"reference_unreadable",[2328,7191,7155],{},[2328,7193,7194],{},"A reference is not a reference this runtime reads. It is proved at parse, so this names a snapshot frozen before that rule existed.",[2276,7196,7197,7202,7204],{},[2328,7198,7199],{},[825,7200,7201],{},"condition_unreadable",[2328,7203,7155],{},[2328,7205,7206],{},"A branch condition could not be read. Proved at parse in the same way.",[2276,7208,7209,7214,7216],{},[2328,7210,7211],{},[825,7212,7213],{},"internal_error",[2328,7215,7155],{},[2328,7217,7218,7219,7222,7223,7226,7227,7230,7231,7234],{},"The platform could not complete a tool call. ",[825,7220,7221],{},"DefaultToolInvoker"," answers eight causes, such as a missing idempotency journal or a claim that carries no row. ",[825,7224,7225],{},"InvokerToolNodeCaller"," answers a failed ",[825,7228,7229],{},"ToolResult"," that carries no error. No handler constructs a ",[825,7232,7233],{},"ToolError",", so no tool answers it about its own work.",[2276,7236,7237,7241,7243],{},[2328,7238,7239],{},[825,7240,7128],{},[2328,7242,7155],{},[2328,7244,7245,7246,7248,7249,7252],{},"The snapshot does not hold a workflow or an agent the runtime can read. ",[825,7247,1509],{}," also answers it when ",[825,7250,7251],{},"freeze"," cannot assemble one, because the registry no longer holds a tool the definition names.",[2276,7254,7255,7260,7263],{},[2328,7256,7257],{},[825,7258,7259],{},"no_run_scope",[2328,7261,7262],{},"a child",[2328,7264,7265],{},"The child walk ran with no Run bound to the context. A defect in whoever built the function.",[2276,7267,7268,7273,7275],{},[2328,7269,7270],{},[825,7271,7272],{},"session_unreadable",[2328,7274,7262],{},[2328,7276,7277],{},"The framework could not read the session of a continuation segment.",[2276,7279,7280,7285,7287],{},[2328,7281,7282],{},[825,7283,7284],{},"invalid_tool_contract",[2328,7286,7262],{},[2328,7288,7289],{},"The frozen snapshot names a tool the agent runtime cannot declare.",[2276,7291,7292,7297,7299],{},[2328,7293,7294],{},[825,7295,7296],{},"unknown_provider",[2328,7298,7262],{},[2328,7300,7301],{},"The frozen snapshot names a model provider the platform cannot build.",[2276,7303,7304,7309,7311],{},[2328,7305,7306],{},[825,7307,7308],{},"snapshot_max_segments_unreadable",[2328,7310,7262],{},[2328,7312,7313],{},"The frozen snapshot carries no usable segment bound. Every claimed Run reads that bound, so a child workflow Run answers it too.",[2276,7315,7316,7321,7323],{},[2328,7317,7318],{},[825,7319,7320],{},"paused_run_absent",[2328,7322,7262],{},[2328,7324,7325],{},"The segment after approval found no paused Run to continue. The runtime could not read the state it wrote itself.",[2276,7327,7328,7333,7335],{},[2328,7329,7330],{},[825,7331,7332],{},"run_not_found",[2328,7334,7262],{},[2328,7336,7337],{},"The dispatch named a Run this tenant does not own, or a row that is gone.",[2276,7339,7340,7345,7347],{},[2328,7341,7342],{},[825,7343,7344],{},"wait_kind_not_implemented",[2328,7346,7262],{},[2328,7348,7349],{},"The frozen snapshot names a wait this deployment cannot serve. Phase 1 has no producer of one, so the code is a member before the first producer ships.",[2276,7351,7352,7356,7358],{},[2328,7353,7354],{},[825,7355,4767],{},[2328,7357,7262],{},[2328,7359,7360],{},"The row the wait names is absent, or belongs to another tenant. The runtime could not read a row it filed itself, so no person could ever answer that wait.",[2276,7362,7363,7367,7369],{},[2328,7364,7365],{},[825,7366,4810],{},[2328,7368,7262],{},[2328,7370,7371,7372,7374],{},"A resume left the Run ",[825,7373,889],{},", so it holds an approval nobody waits on. The runtime could not clear a row it filed itself, and nothing else would ever end that Run.",[807,7376,7377,924,7380,7383,7384,7387],{},[847,7378,7379],{},"The code carries the rule, and never a flag.",[825,7381,7382],{},"start_child"," flattens an outcome\ninto a ",[825,7385,7386],{},"ChildStart",", and that record crosses an Inngest step as JSON. A boolean is\ndropped there, so the same fault reaches the parent tolerated. The code survives\nthat trip. It also survives a child Run that ended under the same code in another\nprocess.",[807,7389,7390,7393,7394,7396,7397,7399,7400,7402],{},[847,7391,7392],{},"A spent ceiling is not a member, and a member is the wrong fix for one."," A\nceiling stop keeps the work the Run already did. The set turns a code into a hard\nfailure, which would discard it. A ceiling carries a ",[825,7395,1221],{}," instead: the\naccrual gate sets ",[825,7398,1284],{},", and the wall clock gate and the tool call\nceiling both set ",[825,7401,1288],{},". The same check reads that mark as well, so a\nmarked ceiling stops for its own reason and stays a partial success.",[807,7404,7405,7408],{},[847,7406,7407],{},"Five writers set the mark."," The accrual gate, the wall clock gate and the tool\ncall ceiling all run before the node calls anything. The other two arrive from\noutside the node.",[3103,7410,7411,7443],{},[3106,7412,7413,924,7416,7418,7419,7421,7422,6239,7425,7427,7428,2069,7431,7434,7435,7438,7439,7442],{},[847,7414,7415],{},"The tool call.",[825,7417,4915],{}," reads the run cost ceiling again at the call, so\na ",[825,7420,2681],{}," fan out passes the gate on several branches and the invoker refuses\nthe later ones. Its accrual checkpoint writes the mark into ",[825,7423,7424],{},"ToolResultMeta",[825,7426,7225],{}," copies it onto ",[825,7429,7430],{},"ToolAnswer",[847,7432,7433],{},"The mark travels in a\nfield and never in the code",": a handler answering the money code for a vendor\nquota would otherwise report a failed Run as a partial success. ",[825,7436,7437],{},"_bound"," is the\nonly author of ",[825,7440,7441],{},"meta"," on the handler path, and it rebuilds the record rather\nthan returning the handler's own, so a handler cannot write the field either.",[3106,7444,7445,7448,7449,7452,7453,7456,7457,7460,7461,7463,7464,7466],{},[847,7446,7447],{},"A child Run."," A child that stops at its first segment reports a ",[847,7450,7451],{},"failure",",\nbecause nothing was done, and it carries the clock it spent on ",[825,7454,7455],{},"RunError",".\n",[825,7458,7459],{},"child_outcome"," reads that field. Unread, the parent tolerates a spent ceiling,\nand a ",[825,7462,2681],{}," of such children as the last node reports full success after a\nfraction of the work. ",[825,7465,1288],{}," is the child's own frozen ceiling, so no\nparent gate ever re-catches it.",[807,7468,7469,924,7472,7474,7475,3030,7477,7479,7480,7483,7484,1964,7486,7488,7489,7491,7492,924,7494,7497],{},[847,7470,7471],{},"A child that succeeded partially still marks its parent.",[825,7473,6182],{}," reports\na failure only at segment 0, where nothing was done. After that the child reports\n",[847,7476,1240],{},[825,7478,1221],{},", and an expired action approval always does.\nThe node is ",[825,7481,7482],{},"ok"," and the walk goes on, because the child did succeed. The mark\ntravels anyway: a ",[825,7485,6783],{},[825,7487,2681],{}," each carry the first one an ",[825,7490,7482],{},"\nchild produced, and the Run reports ",[825,7493,1240],{},[847,7495,7496],{},"with"," that clock.",[807,7499,7500,7501,7504,7505,7508,7509,7511],{},"Dropped, the severity inverts: a child that did ",[847,7502,7503],{},"nothing"," stops its parent, and a\nchild that did ",[847,7506,7507],{},"some"," of the work reports an unqualified success. The second is\nthe common shape. ",[825,7510,1292],{}," shows this most clearly, because the child ends\nwith an empty output and a parent would report clean success for work nobody\napproved.",[807,7513,3746,7514,924,7517,7520,7521,7524,7525,2069,7527,7530],{},[847,7515,7516],{},"A Run error code is not the runtime's alone.",[825,7518,7519],{},"run_tool"," passes a tool's\n",[825,7522,7523],{},"error_code"," through verbatim, and a failed node's code becomes the Run's terminal\ncode. So a handler answering the money code for a vendor quota reaches a parent as\n",[825,7526,1284],{},[847,7528,7529],{},"The clock travels in a field at every boundary",", and no\nreader derives it from a code:",[817,7532,7535],{"className":7533,"code":7534,"language":822},[820],"ToolResultMeta -> ToolAnswer -> NodeOutcome -> RunError -> child payload\n  -> parent NodeOutcome\n",[825,7536,7534],{"__ignoreMap":823},[807,7538,3746,7539,924,7542,7544,7545,7548,7549,7551,7552,7554],{},[847,7540,7541],{},"The start path is the one place a ceiling carries no mark, and that is\nsanctioned.",[825,7543,1509],{}," refuses a child on the organization day cap, and the node\nanswers ",[825,7546,7547],{},"start_refused"," with no ",[825,7550,1221],{},". A node that declares\n",[825,7553,5322],{}," walks past a spent day cap. The Child Run rules below state\nthat rule: the refusal names a cap the author can read, not a runtime that could\nnot answer.",[807,7556,7557,924,7560,7563,7564,7566,7567,7569,7570,965],{},[847,7558,7559],{},"One refusal is not a refusal the author can read.",[825,7561,7562],{},"snapshot_unbuildable"," says\n",[825,7565,7251],{}," could not assemble the snapshot, because the registry no longer holds a\ntool the definition names, or a skill no longer resolves. Publish checked both, so\nit names a deploy that changed after the definition was published. The node\nanswers ",[825,7568,7128],{}," for it, and never ",[825,7571,7547],{},[807,7573,7574,7575,7578,7579,7581,7582,7584],{},"That asymmetry is the reason. The ",[847,7576,7577],{},"same"," missing tool answers\n",[825,7580,7284],{}," when a later segment reads a snapshot that was already\nfrozen, and that code stops the Run. Which branch fires depends only on whether the\ndeploy rolled before or after the freeze, so both answer the same platform stop.\nCollapsed into ",[825,7583,7547],{},", a declaring node skips the child and the Run\nreports success on a definition this deploy cannot build.",[807,7586,3746,7587,7592,7593,1480,7595,7597],{},[847,7588,1787,7589,7591],{},[825,7590,2681],{}," container answers one outcome, so severity decides it."," One\nbranch may carry a mark and another may carry a bare platform stop. The container\nprefers the unmarked failure, whichever branch met it. Taking the first failure in\ndeclaration order instead, the same two faults report ",[825,7594,1240],{},[825,7596,1232],{},"\ndepending on the order the author wrote the branches in.",[807,7599,7600,7601,7603,7604,7606],{},"These are ",[847,7602,3477],{}," platform stops. The runtime read each one and answered it, so\n",[825,7605,5322],{}," decides them as the author asked:",[3103,7608,7609,7614,7626,7632,7643,7669,7677],{},[3106,7610,7611,7613],{},[825,7612,7547],{}," — the day cap or a cancelled parent refused the child, and the\nChild Run rules below sanction tolerating it.",[3106,7615,7616,928,7619,928,7622,7625],{},[825,7617,7618],{},"workflow_cycle",[825,7620,7621],{},"subworkflow_unresolvable",[825,7623,7624],{},"depth_exceeded"," — the walk read\nthe graph, and the graph is wrong.",[3106,7627,7628,7631],{},[825,7629,7630],{},"unresolved_reference"," — the reference is readable, and the step it names\nproduced no value.",[3106,7633,7634,928,7637,928,7639,7642],{},[825,7635,7636],{},"tool_failed",[825,7638,3913],{},[825,7640,7641],{},"child_cancelled"," — the tool, the child or a\nperson answered.",[3106,7644,7645,928,7648,928,7651,928,7654,7657,7658,937,7660,7662,7663,7665,7666,7668],{},[825,7646,7647],{},"tool_unavailable",[825,7649,7650],{},"timeout",[825,7652,7653],{},"denied",[825,7655,7656],{},"invalid_input"," — a tool answered.\n⚠️ ",[825,7659,7647],{},[825,7661,7284],{}," name the same condition and\nanswer differently. A snapshot naming a tool this deployment cannot serve stops\nan ",[847,7664,1801],{}," child, and is tolerated on a ",[847,7667,6457],{}," node. The agent case is the\nfrozen snapshot the runtime could not read. The tool case is a name the registry\nread and did not find, so the author can fix it.",[3106,7670,7671,928,7674,7676],{},[825,7672,7673],{},"model_provider_refused",[825,7675,4278],{}," — the vendor answered, and\npolicy answered.",[3106,7678,7679,7682],{},[825,7680,7681],{},"run_already_ended"," — the claim found nothing to claim, and a person who\ncancelled the child is the ordinary cause. A cancelled child stays tolerable, so\nthe reaper's lost worker is tolerated with it.",[807,7684,3746,7685,7692,7693,7695],{},[847,7686,1787,7687,7689,7690,965],{},[825,7688,6461],{}," node declares no ",[825,7691,5322],{}," The flag is on the three\ntypes that do work. So ",[825,7694,7201],{}," reaches a node that declares the flag\nonly across a Run boundary. A subworkflow node reads a child Run that failed on\nits own branch. The code must still be in the set. Without it that parent tolerates\na snapshot nothing could read.",[2235,7697,7699],{"id":7698},"cancellation-the-wall-clock-and-accrual-are-checked-before-every-node","Cancellation, the wall clock and accrual are checked before every node",[807,7701,7702,7704],{},[825,7703,6731],{}," checks all three, in this order, and each one answers a\ndifferent question.",[3103,7706,7707,7725,7741],{},[3106,7708,7709,7715,7716,7718,7719,7721,7722,7724],{},[847,7710,7711,7712,7714],{},"The ",[825,7713,6127],{}," row."," Safe boundaries sit between nodes. A workflow\nthat meets a cancel stops at the next node and answers ",[825,7717,1329],{},", which\nnames no ",[825,7720,1359],{}," method because ",[825,7723,2225],{}," already wrote the status. It\nis first for two reasons. A person who pressed stop must not pay for an\naccrual read. A cancelled run must not be reported as out of budget.",[3106,7726,7727,7730,7731,7734,7735,7737,7738,7740],{},[847,7728,7729],{},"The wall clock",", against ",[825,7732,7733],{},"run_deadline(run, ceilings)",". It is second,\nbecause it reads no record. A run past the deadline answers the code\n",[825,7736,2762],{}," and the reason ",[825,7739,1288],{},". The run keeps the outputs\nof the nodes it did finish.",[3106,7742,7743,7746,7747,7750,7751,7754],{},[847,7744,7745],{},"Accrual",", scope ",[825,7748,7749],{},"run_and_day",". The run tree total and the organization day.\n",[825,7752,7753],{},"RunExecutor"," claimed the run once and returned long before node 4.",[807,7756,7757],{},"All three sit inside the node's own step, so none repeats on a replay.",[807,7759,3746,7760,7765,7766,7768,7769,7772],{},[847,7761,7762,7764],{},[825,7763,6211],{}," cannot answer the wall clock here."," The walk hands\nevery node the frozen budget, so ",[825,7767,2762],{}," holds the whole ceiling and\nnever counts down. Only ",[825,7770,7771],{},"AgentExecutor._remaining"," subtracts the elapsed time,\nand it does so per segment.",[807,7774,3746,7775,7781,7782,7785],{},[847,7776,7777,7780],{},[825,7778,7779],{},"run_deadline"," reads the frozen ceilings, and never a remainder."," One\nfunction answers the deadline for both readers: this gate, and the\n",[825,7783,7784],{},"ToolInvocation"," that caps an approval expiry. A remainder subtracts the\nelapsed time a second time. A run at hour 3 of a 4 hour budget then files every\napproval already expired.",[807,7787,7788],{},"The deadline bounds the walk between nodes, and not inside one. A tool call\nclamps on the same instant. A child run does not: the parent waits for a child\nthat holds its own ceiling, so the parent notices at the next node.",[2235,7790,7792],{"id":7791},"what-a-workflow-run-produces","What a workflow run produces",[807,7794,7795],{},"A workflow node produces a value. By default, the Run result is the map of what\nthe nodes produced.",[817,7797,7799],{"className":1124,"code":7798,"language":1126,"meta":823,"style":823},"RunResult(\n    summary='4 of 5 nodes produced output',\n    output={'classify': {...}, 'draft': {...}},   # one key per node with an output\n    refs=[ResourceRef(kind='prospect', id='...')],\n)\n",[825,7800,7801,7806,7811,7816,7821],{"__ignoreMap":823},[1130,7802,7803],{"class":1132,"line":22},[1130,7804,7805],{},"RunResult(\n",[1130,7807,7808],{"class":1132,"line":32},[1130,7809,7810],{},"    summary='4 of 5 nodes produced output',\n",[1130,7812,7813],{"class":1132,"line":233},[1130,7814,7815],{},"    output={'classify': {...}, 'draft': {...}},   # one key per node with an output\n",[1130,7817,7818],{"class":1132,"line":244},[1130,7819,7820],{},"    refs=[ResourceRef(kind='prospect', id='...')],\n",[1130,7822,7823],{"class":1132,"line":264},[1130,7824,3660],{},[807,7826,5881,7827,928,7829,937,7831,7833,7834,7836,7837,7839,7840,7842],{},[825,7828,6457],{},[825,7830,1801],{},[825,7832,3945],{}," produce a ",[825,7835,3909],{},", so only\nthose three appear. A node that did not run is absent rather than null, which is\nthe same rule the reference check uses. ",[825,7838,1359],{}," bounds the map at 32 KB with\n",[825,7841,1399],{},", exactly as it bounds an agent result.",[807,7844,7845,7846,7848,7849,937,7851,7853,7854,965],{},"An optional workflow-level ",[825,7847,1392],{}," object selects the completed Run result\nfrom ",[825,7850,1420],{},[825,7852,3909],{}," references. The executor resolves it after\nthe full walk succeeds. This lets a product keep one small final projection\nwithout storing every intermediate output before it. If the selector cannot\nresolve, the Run fails with ",[825,7855,7630],{},[807,7857,7858,7859,7861],{},"A partial success keeps the default node-output map and its ",[825,7860,1221],{},".\nIt does not resolve the selector, because the selected final node might not\nhave run. This map is diagnostic only. A product consumer must reject a\npartial Run.",[807,7863,7864,7865,7868,7869,7872,7873,7875,7876,7878,7879,7881],{},"A successful write tool contributes resource refs when its live ",[825,7866,7867],{},"ToolSpec","\ndeclares an ",[825,7870,7871],{},"item_kind",". One returned row contributes one ref from its ",[825,7874,927],{},".\nA list contributes refs in returned order. A read tool, a failed call, a tool\nwith no ",[825,7877,7871],{}," and a row with no ",[825,7880,927],{}," contribute none.",[807,7883,7884,7885,7888],{},"The node stores its contributed refs in its memoized result. The workflow keeps\ndeclaration order and removes duplicate ",[825,7886,7887],{},"(kind, id)"," pairs. The first ref wins.\nA replay reads the memoized refs. It does not read a later live tool declaration\nor repeat the effect. Container nodes add no refs of their own.",[807,7890,7891,7894],{},[847,7892,7893],{},"The map is not a second state store."," It is rebuilt on every pass of the body\nfrom the memoized step outputs, and no table holds it.",[2235,7896,7898],{"id":7897},"child-run-rules","Child Run rules",[3103,7900,7901,7909,7924,7942,7976,7987,7990,8006,8012,8021,8030,8040,8053],{},[3106,7902,7903,7904,7906,7907,965],{},"The child gets ",[825,7905,855],{}," and inherits ",[825,7908,866],{},[3106,7910,7911,7917,7918,7920,7921,7923],{},[847,7912,7913,7914,965],{},"The child's start key is ",[825,7915,7916],{},"\u003Cparent_run_id>:\u003Cnode_id>"," Every other source of a start key is a delivery identity, and a workflow node has no delivery. It has a node identity, and that identity is already unique across the whole workflow and stable across a retry. This is the key that makes the next rule work: without a deterministic one, a retried node writes a new key and starts a ",[847,7919,3062],{}," child, and ",[825,7922,1735],{}," is unreachable. Phase 4 keeps child Run nodes at width one.",[3106,7925,7926,924,7929,7931,7932,7934,7935,3007,7937,1330,7939,7941],{},[847,7927,7928],{},"A refused start fails the node, and there is no child Run to show.",[825,7930,1509],{}," can refuse before any Run exists, for the organization day cap or for a cancelled parent. The node treats it as a node failure with that reason, so ",[825,7933,5322],{}," decides the rest exactly as it does for a child that ran and failed. Do not retry: neither reason changes on a retry inside this Run. Two refusals are platform stops instead. ",[825,7936,7562],{},[825,7938,7128],{},[825,7940,3022],{}," answers itself, because a gate that could not decide is a fault and never a business answer.",[3106,7943,7944,7947,7948,7950,7951,7953,7954,7956,7957,7959,7960,7963,7964,7966,7967,1798,7970,7972,7973,7975],{},[847,7945,7946],{},"A child ended at admission carries its own code."," Admission runs after the insert, so a refused child answers ",[825,7949,1743],{}," beside a Run that already reads ",[825,7952,1232],{},", and the node never invokes it. Read as ",[825,7955,3913],{}," a node declaring ",[825,7958,5322],{}," walks past a policy outage. ",[825,7961,7962],{},"StartRunResult.error_code"," carries the code the Run ended under, because ",[825,7965,1494],{}," is the narrow read on this path and ",[825,7968,7969],{},"RunPayloads",[825,7971,3896],{},". A rule that denied answers ",[825,7974,1761],{}," and stays tolerable; a fault answers its fault code and stops the node.",[3106,7977,7978,7983,7984,7986],{},[847,7979,1787,7980,7982],{},[825,7981,1735],{}," answer is not a refusal."," A retried node meets the child its\nearlier attempt created, and ",[825,7985,1509],{}," returns that Run. The node uses it and\ncontinues. It must never treat a duplicate as a failure, and it must never\nstart a second child.",[3106,7988,7989],{},"The child Principal is the parent Principal. A step cannot widen authority.",[3106,7991,7992,7998,7999,1005,8001,937,8003,8005],{},[847,7993,7994,7995,7997],{},"The child answers the three keys ",[825,7996,3541],{}," answers."," The node reads ",[825,8000,3888],{},[825,8002,3892],{},[825,8004,3896],{}," into its own failure. A child that ended before its claim answers a status of null beside an error naming that, so the node reports the reason rather than \"the child run ended None\".",[3106,8007,8008,8009,8011],{},"A failed child fails its parent node, unless the node declares ",[825,8010,5322],{},". That flag is node configuration, not a new node type.",[3106,8013,8014,8015,8017,8018,8020],{},"A platform stop is never tolerated, whatever the node declares. The ",[847,8016,825],{}," carries that rule, so it survives the JSON of a ",[825,8019,7386],{}," and a child Run that ended in another process.",[3106,8022,8023,8024,8026,8027,8029],{},"A cancelled parent cancels every descendant. ",[825,8025,3342],{}," writes the control row for the whole subtree and publishes ",[825,8028,5564],{}," for each Run ID.",[3106,8031,8032,8033,8035,8036,8039],{},"The depth cap from ",[810,8034,1470],{"href":365}," is checked again at run time, by the ",[847,8037,8038],{},"same function",". A definition published after the parent was validated cannot deepen a running tree.",[3106,8041,8042,8045,8046,8048,8049,8052],{},[847,8043,8044],{},"The run time check needs the depth above this Run, and no column carries it."," The definition side answers how deep the subworkflow goes below; the Run side answers how deep this node already sits. That is a walk up ",[825,8047,855],{}," until it is null, which the cap bounds to a handful of primary key reads. Do not add a ",[825,8050,8051],{},"depth"," column: it would be a fourth frozen column to prove on every insert, and the walk is shorter than the tree it measures.",[3106,8054,8055,8058,8064,8067,8068,8070,8071,8074,8075,8078,8079,965],{},[847,8056,8057],{},"The check is one inequality, and it must be written down.",[817,8059,8062],{"className":8060,"code":8061,"language":822,"meta":823},[820],"a = the workflow Runs from the root to this Run, counted inclusive\nd = depth(the target workflow), from the definition side\nrefuse the node when   a + d > 3\n",[825,8063,8061],{"__ignoreMap":823},[8065,8066],"br",{},"Only a ",[825,8069,3945],{}," node deepens the count. An agent Run starts no child of\nits own, so every Run above a workflow child is a workflow Run and the walk\ncounts what it should. ",[825,8072,8073],{},"d"," reads the ",[847,8076,8077],{},"live"," published definitions and never\nthe parent's frozen snapshot, which is the whole reason the check runs twice:\na subworkflow published after the parent was validated is exactly the case\npublish could not see. So the shared function takes a definition loader, and\nthe run time caller passes one that reads ",[825,8080,2084],{},[2235,8082,8084],{"id":8083},"parallel-node-failure","Parallel node failure",[807,8086,8087],{},"A failed branch does not cancel its siblings. The parallel node waits for every branch to settle, then reports. This keeps the effects predictable, and it matches \"one person fails in a batch, the others continue\" in the email sequence.",[807,8089,3746,8090,8093,8094,8097],{},[847,8091,8092],{},"A business failure is a returned value, and it never raises."," An exception\nout of a step is retried by Inngest and then fails the whole function, which ends\nevery sibling branch. That is the opposite of the rule above, and it is the\ndefault behaviour of the obvious implementation. A node body therefore catches\nthe failure it understands and answers a ",[825,8095,8096],{},"NodeOutcome"," naming it. Only a\nretryable platform fault leaves the body as an exception, exactly as it does on\nthe agent side.",[2235,8099,8101],{"id":8100},"an-approval-inside-a-parallel-node","An approval inside a parallel node",[807,8103,8104],{},"A wait is not a settle, so the two rules above do not compose on their own. A branch that needs a person can hold the container for three days while its siblings are already done.",[807,8106,8107,8110,8111,8113],{},[847,8108,8109],{},"Each branch owns its own wait."," A ",[825,8112,2681],{}," node runs its children as independent Inngest steps, and a step that parks does not hold the others.",[817,8115,8118],{"className":8116,"code":8117,"language":822,"meta":823},[820],"parallel\n  ├─ branch A  ── tool ── done\n  ├─ branch B  ── approval ── waiting 3 days ── resumed ── done\n  └─ branch C  ── tool ── failed\n                     the node settles when the last branch settles\n",[825,8119,8117],{"__ignoreMap":823},[807,8121,8122],{},"Three rules make that safe.",[5797,8124,8125,8148,8159],{},[3106,8126,8127,8130,8131,8133,8134,8136,8137,8140,8141,8144,8145,8147],{},[847,8128,8129],{},"A branch approval never raises out of the container."," In an ",[1108,8132,1801],{}," loop an approval stops the segment, because the model must not read a pending value. A workflow branch is not a model, so its ",[825,8135,5345],{}," step waits in place and the sibling steps keep running. ",[847,8138,8139],{},"This is an Inngest behaviour, so a contract test pins it",", exactly as one pins the suspended parent holding no concurrency slot. ",[825,8142,8143],{},"tests\u002Fagentic\u002Fruntime\u002Finngest\u002Ftest_parallel_contract.py"," is that test, and it runs a real dev server: one branch parks and one branch of two steps must reach its second step. An approval wait is the same ",[825,8146,4369],{}," primitive, so the approval node extends that case rather than building a second harness. A parallel container is the one place the platform depends on a step parking without stalling the steps beside it, and reasoning about it is not the same as measuring it.",[3106,8149,8150,924,8156,8158],{},[847,8151,8152,8153,8155],{},"The Run is ",[825,8154,889],{}," while any branch waits.",[825,8157,900],{}," names the oldest unresolved approval. A person opening the Run sees one thing to do, and Human Review lists every branch approval separately.",[3106,8160,8161,8164,8165,8167],{},[847,8162,8163],{},"The container inherits the longest timeout."," The node settles at the last branch, so ",[825,8166,2762],{}," is the backstop for the whole container, exactly as it is for one wait.",[807,8169,8170,8171,8173,8174,8177],{},"Two branches may each raise their own approval. They are two rows in the inbox, they resolve in any order, and the container settles when both are answered. ",[825,8172,900],{}," names the oldest of the two, so each raise reads ",[825,8175,8176],{},"oldest_pending()"," rather than writing its own id: two raises are two steps in either order, and the field would otherwise name whichever committed last.",[807,8179,3746,8180,8187,8188,8190,8191,8193],{},[847,8181,6850,8182,8184,8185,965],{},[825,8183,5345],{}," never shares a container with a ",[825,8186,5341],{}," Approvals are countable and a ",[825,8189,5341],{}," is not, so a container holding one of each wakes the Run on the answered approval while the other branch is still parked, and the reaper then fails it. ",[810,8192,1942],{"href":365}," owns the publish rule that refuses the shape.",[807,8195,8196,8201,8202,8204,8205,8207,8208,937,8210,8212,8213,965],{},[847,8197,1787,8198,8200],{},[825,8199,889],{}," Run can still be executing."," Rule 2 says the Run reads ",[825,8203,889],{}," while any branch waits, so a sibling branch runs its next node against a Run every surface calls waiting. Nothing may read ",[825,8206,889],{}," as \"no work in flight\". The reaper is already correct here for its own reason: it reads ",[825,8209,1751],{},[825,8211,1062],{}," and never ",[825,8214,889],{},[828,8216,8218],{"id":8217},"waits-and-approvals","Waits and approvals",[807,8220,8221,8222,8224],{},"An approval may outlive the worker that proposed it. Everything needed to resume is durable ",[847,8223,2946],{}," the wait starts.",[817,8226,8229],{"className":8227,"code":8228,"language":822,"meta":823},[820],"Agno proposes a tool call\n  -> ToolInvoker -> Policy: require_approval\n  -> persist the approval row, ON CONFLICT (run_id, idempotency_key) DO NOTHING:\n       run_id, tool name, exact arguments, args hash,\n       idempotency key, Agno session identity, expires_at\n  -> RunManager.mark_waiting(run_id, waiting_on=approval, ref_id=approval_id)\n  -> the segment step returns needs_approval\n  -> Inngest wait on agent\u002Fapproval.resolved\n  -> the worker may disappear\n  -> a person resolves the approval\n  -> ApprovalService publishes agent\u002Fapproval.resolved\n  -> a fresh worker rebuilds the agent from the snapshot and its session\n  -> the runtime executes the approved call from the approval row\n  -> it resolves the requirement, and acontinue_run fills that call's result slot\n",[825,8230,8228],{"__ignoreMap":823},[807,8232,8233,8236,8237,8239],{},[847,8234,8235],{},"The approval row is claimed on the journal key, exactly as an executed call is."," A segment that writes the row and then dies before its step returns is replayed by Inngest, and the replay rebuilds the agent, replays the journal, and reaches the same proposal again. An unconditional insert files a ",[847,8238,3062],{}," inbox row, so a person sees one send twice and approves it twice.",[807,8241,8242,8243,8246,8247,8250,8251,8253],{},"The key is the same ",[825,8244,8245],{},"\u003Crun_id>:\u003Cstep_path>:\u003Cargs_hash>"," the journal uses, and ",[825,8248,8249],{},"unique(run_id, idempotency_key)"," makes the second write a no-op that reads the first row back. It has to be the journal key rather than a key of its own: a pending approval is a proposed effect, not an executed one, so ",[825,8252,964],{}," does not hold it, and the two must agree on what \"the same call\" means.",[807,8255,8256,8257,8259,8260,8263],{},"That also closes an orphan. Without it the first row stays ",[825,8258,4247],{}," for ever: its expiry writer is the Inngest wait that its own segment never reached, so nothing terminal ever writes it. ",[810,8261,8262],{"href":344},"Human review"," says every terminal state has a real writer, and that row would be the exception.",[807,8265,8266,8267,8270,8271,8273,8274,8276],{},"The wait timeout is computed from ",[825,8268,8269],{},"approval.expires_at",", and never from a second timer of its own. ",[825,8272,5404],{}," re-checks the proposal immediately before the effect runs, and a stale approval fails closed. ",[810,8275,288],{"href":287}," owns both rules.",[807,8278,8279,8282,8283,8285,8286,8288],{},[847,8280,8281],{},"The resume does not wait for the model to ask again."," The approval row holds the tool name and the exact arguments, so the runtime executes that call and hands the outcome back through the paused requirement. Relying on the model to re-propose it would make an approved send depend on a sampling outcome, and a person who approved would sometimes get nothing. A rejection resolves the requirement as rejected instead, and the agent adapts. ",[825,8284,4915],{}," owns that answer, and it reads the rejection of the claim as well as the row it was handed, so a later segment of the same run does not ask the person again. ",[810,8287,191],{"href":190}," owns the invoke path this uses.",[828,8290,8291],{"id":377},"Cancellation",[807,8293,8294,8295,965],{},"Cancellation is cooperative, durable and idempotent. It lives in ",[825,8296,6127],{},[817,8298,8301],{"className":8299,"code":8300,"language":822,"meta":823},[820],"RunManager.cancel()\n  -> read the named Run     id, parent_run_id, root_run_id, organization_id\n                            every filter below also carries organization_id\n  -> STATUS FIRST\n       the ROOT      -> one UPDATE, WHERE root_run_id = :id\n       a MID-TREE Run-> per level: UPDATE this level, THEN read its children\n       UPDATE agent.runs SET status='cancelled', ended_at=now(),\n                            waiting_on=NULL, waiting_ref_id=NULL\n              WHERE \u003Cfilter> AND organization_id = :actor_org\n                AND status IN ('queued','running','waiting')\n       THEN read the answer, which is not the same set as the write\n       SELECT id WHERE \u003Cfilter> AND organization_id = :actor_org\n                   AND status = 'cancelled'   -- this call's movers, and a crash's\n  -> THEN publish agent\u002Frun.cancelled per returned run_id\n       Inngest stops a queued, waiting or running function\n  -> THEN the control rows\n       INSERT agent.run_control (run_id, cancel_requested_at, by, reason)\n              ON CONFLICT (run_id) DO NOTHING     -- the returned ids only\n       a running step reads this at its next safe boundary\n  -> LAST the pending approvals, whether or not any Run moved\n       UPDATE agent.approvals SET status='cancelled'\n              WHERE root_run_id = :id AND status = 'pending'      -- root cancel\n       UPDATE agent.approvals SET status='cancelled'\n              WHERE run_id IN (:movers) AND status = 'pending'    -- mid-tree\n",[825,8302,8300],{"__ignoreMap":823},[807,8304,8305,8308,8309,8311,8312,8314,8315,8317],{},[847,8306,8307],{},"The walk descends from the level it attempted, never from the Runs that moved."," A Run that did not move still has live children: an intermediate node a reaper failed, a branch that ended under ",[825,8310,5322],{},", and every node of a subtree that a crashed earlier pass already cancelled. Descend from the ",[825,8313,3094],{}," set and any one of those hides its whole subtree, permanently. Measured on a root -> M -> C -> G tree: a re-run of a mid-tree cancel answered an empty set and left C and G ",[825,8316,1751],{},", which is the exact case the re-run advice below exists for, and a terminal middle node hid its grandchild with no crash at all. Keep the moved set for the control rows, and walk the attempted set.",[807,8319,8320,8323,8324,1970,8326,8330,8331,8334,8335,8337,8338,8340,8341,8344],{},[847,8321,8322],{},"A mid-tree cancel writes each level before it reads the next one, and the order is the whole reason the walk is safe."," Collect the whole subtree first and a child born during the walk is missed twice over: it is not in the collected set, and its parent still reads ",[825,8325,1062],{},[810,8327,8329],{"href":8328},"#a-cancelled-parent-refuses-its-next-child","the parent status check"," does not refuse it either. That check was added for a window of one statement; a collect-then-update walk widens it to the whole walk, which is several round trips on a deep tree. Cancel level ",[1108,8332,8333],{},"k"," first and every parent is already ",[825,8336,1329],{}," before its children are enumerated, so ",[825,8339,1509],{}," refuses a new child at level ",[1108,8342,8343],{},"k+1"," and no descendant can be born unseen. It costs no extra round trip. It is the same reads and the same writes, in the other order.",[807,8346,8347,8353,8354,8356,8357,8359,8360,8362,8363,8366,8367,8369],{},[847,8348,8349,8350,8352],{},"Every filter carries ",[825,8351,931],{},", because RLS is not on this path."," Each write to the ",[825,8355,1801],{}," schema uses the service role, so a policy filters nothing and raises nothing. ",[825,8358,2225],{}," takes a run ID from a caller, and ",[825,8361,1601],{}," carries the organization, so both filters take it: ",[825,8364,8365],{},"root_run_id = :id AND organization_id = :actor_org",", and the same on each level of the walk. ",[825,8368,2061],{}," already keeps a tree inside one organization, so the predicate can never narrow a legitimate cancel, and it is the only thing standing between a caller's run ID and another tenant's Run.",[807,8371,8372,8375,8376,8379],{},[847,8373,8374],{},"The cancel answers what is cancelled, not what it moved, and only those get a control row."," The status update already skips a terminal Run; the control insert must skip a Run that ",[847,8377,8378],{},"succeeded or failed",", or cancelling a tree of five hundred children where four hundred and eighty succeeded writes four hundred and eighty rows nobody reads, into the table whose whole design is that it stays empty.",[807,8381,8382,8390,8391,8393,8394,8396,8397,8399,8400,8402,8403,8406,8407,8409],{},[847,8383,8384,8385,8387,8388,965],{},"It must not skip a Run that is already ",[825,8386,1329],{},", and that is why the answer is a read rather than a ",[825,8389,3094],{}," This page tells the operator to re-run ",[825,8392,2225],{}," to place the rows a crash lost. Answer with the Runs this call moved and that instruction is dead: the first pass already cancelled them, a cancelled Run is not in the from-states, so the re-run matches zero rows and repairs nothing. The worker inside a segment then reads ",[825,8395,6127],{},", finds nothing, and runs to ",[825,8398,2762],{}," on a Run a person stopped. So the write is one conditional ",[825,8401,2244],{}," and the answer is a second statement, ",[825,8404,8405],{},"WHERE \u003Cfilter> AND organization_id = :actor_org AND status = 'cancelled'",". It names the movers of this call and the movers of the crashed one together, and the control insert's ",[825,8408,3317],{}," makes the overlap free.",[807,8411,8412,8415,8416,8418,8419,8422,8423,8426,8427,8430,8431,8434],{},[847,8413,8414],{},"The write needs no paging and the read does, which is the opposite of what it looks like."," A returned representation does ",[847,8417,3477],{}," obey ",[825,8420,8421],{},"PGRST_DB_MAX_ROWS",", while a read does. Measured against ",[825,8424,8425],{},"postgrest 14.7"," with the limit at 1000: a ",[825,8428,8429],{},"PATCH"," matching 1,501 Runs updated 1,501 and returned 1,501, and a ",[825,8432,8433],{},"GET"," over the same set returned 1,000. So the status write is one statement at any width, and the answering read is paged by the same keyset rule the walk uses. Do not add a page loop to the write to be safe: it costs a round trip on every wide cancel, and it would not help if the behaviour ever changed, because that statement has already moved those Runs out of the status filter and no re-run can name them again.",[807,8436,8437,924,8443,8445,8446,8448,8449,3007,8451,8454,8455,8457],{},[847,8438,8439,8440,8442],{},"The control row insert is ",[825,8441,3317],{},", or the second cancel raises.",[825,8444,2974],{}," is the primary key of ",[825,8447,6127],{},", so a plain ",[825,8450,870],{},[825,8452,8453],{},"23505"," on a Run that already carries a row. This page says the caller may re-run ",[825,8456,2225],{}," to place the rows a crash lost. Without the clause, that re-run fails on the first Run that already has one and never reaches the ones that are missing, which is the exact case the advice exists for.",[807,8459,8460,924,8470,2226,8472,8474,8475,8478,8479,8482,8483,8485,8486,8489,8490,8493],{},[847,8461,8462,8465,8466,8469],{},[825,8463,8464],{},"cancel_requested_by"," is a ",[825,8467,8468],{},"public.profiles"," key, so only a person fills it.",[825,8471,2225],{},[825,8473,1601],{},", and that actor is a user or a machine. A machine identity has no ",[825,8476,8477],{},"profiles"," row, so writing its id answers ",[825,8480,8481],{},"23503"," and the whole cancel tail is lost. The column is nullable for this reason: write the id for a ",[825,8484,3693],{}," actor, and ",[825,8487,8488],{},"NULL"," for every other kind. ",[825,8491,8492],{},"cancel_reason"," still records who asked, in words.",[807,8495,8496,8499,8500,8503,8504,8506],{},[847,8497,8498],{},"The fourth write clears the approvals of the subtree."," A root cancel filters ",[825,8501,8502],{},"root_run_id = :id",", which is one indexed statement whatever the width. A mid-tree cancel names the Runs the walk reached, because ",[825,8505,866],{}," there would clear the approvals of the siblings and the parent, which the person did not ask to stop.",[807,8508,8509,8512,8513,8515,8516,8519],{},[847,8510,8511],{},"That write runs even when no Run moved, and it catches its own fault."," A branch that already ended moves nothing and can still hold a pending approval: the invoker files the row, and the segment then fails inside the window the fail path clears it in. Guard the write with the other three and no later ",[825,8514,2225],{}," reaches it, because ",[825,8517,8518],{},"moved"," is empty every time. And a person who stopped a Run must not read a failure for an inbox row, so a fault there is logged rather than raised.",[807,8521,8522,8527],{},[847,8523,8524,8525,965],{},"Every filter names ",[825,8526,4247],{}," A person who resolves an approval in the same moment keeps that decision, and the freeze trigger would refuse the overwrite in any case.",[807,8529,8530,924,8533,8536,8537,8539,8540,965],{},[847,8531,8532],{},"The approvals filter takes the walk, and the other three writes take the movers.",[825,8534,8535],{},"cancel_subtree"," walks every descendant and moves only the Runs that changed status, so the two sets differ by the descendants that already ended. Handed the movers, the fourth write skips exactly the Run whose approval is already an orphan, while a root cancel of the same tree clears it, and one intent gives two answers. A stopped function and a cooperative control row mean nothing on a Run that did not move, so those two keep the movers. ",[825,8538,8535],{}," therefore answers both sets from one walk: a second read of the tree would race the first, and a child born between them would land in one answer and not the other. ",[810,8541,8545],{"href":8542,"rel":8543},"https:\u002F\u002Flinear.app\u002Fagencycore\u002Fissue\u002FENG-2157",[8544],"nofollow","ENG-2157",[807,8547,8548,8551,8552,8554,8555,2069,8557,8560,8561,8563,8564,8566,8567,8569,8570,8573,8574,8576,8577,8580,8581,8584,8585,8587,8588,965],{},[847,8549,8550],{},"A terminal write that is not a cancel clears the approvals of its own Run."," The same orphan arrives without any cancel: the invoker files a row and the segment then fails, or a resume leaves the Run ",[825,8553,889],{}," and the loop ends it on ",[825,8556,4810],{},[825,8558,8559],{},"resolve"," still accepts such a row and the approve route answers ",[825,8562,4555],{},", so a person answers a question on a Run that ended minutes ago. ",[825,8565,2628],{}," therefore clears the pending approvals of the Run it ended, when the write moved a row: zero rows matched means another writer owns the Run, and a cancel of it owns its approvals. ",[825,8568,1333],{}," does not, and an ordinary segment failure needs no help from it: ",[825,8571,8572],{},"AgentExecutor.execute()"," supersedes on every path that returns, so a segment that ended the Run either way already cleared its own rows. The write is for the endings that reach no segment supersede, which are the loop's own ",[825,8575,4810],{},", the reaper, ",[825,8578,8579],{},"run_failed"," when Inngest gives up on the function run, an admission that could not write its hold, and a supersede that swallowed its own fault. A segment that ",[847,8582,8583],{},"raises"," reaches neither write: the raise leaves no terminal status, and the Inngest replay reads back the pending row that carries the idempotency key it reuses. ",[825,8586,8579],{}," clears that row only once Inngest stops replaying, and no replay then wants it. Every failed Run therefore pays one conditional statement and most match no row, and the alternative is an inbox that keeps a row for every such Run until its own clock expires it. ",[810,8589,8592],{"href":8590,"rel":8591},"https:\u002F\u002Flinear.app\u002Fagencycore\u002Fissue\u002FENG-2118",[8544],"ENG-2118",[807,8594,8595,8598],{},[847,8596,8597],{},"The four writes are ordered by what a crash costs, because none of them shares a transaction with the next."," The status is the product truth every surface reads, so it goes first. The Inngest publish is what actually stops a live function, so it goes second. The control rows are the cooperative backstop for a step already inside a segment, so they go third. Nothing waits on an approval of a stopped Run, so the approvals go last.",[807,8600,8601,924,8604,8606,8607,8610,8611,8614,8615,8618,8619,8622],{},[847,8602,8603],{},"No filter carries a list of Run IDs, because a wide tree makes the URL too long to send.",[825,8605,1093],{}," filters through PostgREST, so ",[825,8608,8609],{},"id IN (...)"," is a query string, and Kong refuses a request line over 8 KB. A UUID plus its comma is 37 bytes, so one ",[825,8612,8613],{},"in.()"," filter holds about ",[847,8616,8617],{},"220"," identifiers. Measured against the local stack: 200 ids is a 7,462 byte URL and answers; 400 ids is 14,862 bytes and answers ",[825,8620,8621],{},"414",". A five hundred person email sequence is the design's own named case, and it is twice over that line.",[807,8624,8625,8626,8628,8629,8631,8632,8634,8635,8637,8638,1385],{},"That is the same failure ",[810,8627,209],{"href":277}," avoided by stamping ",[825,8630,866],{}," on every usage row rather than filtering the meter by twenty thousand span identifiers. Cancellation takes the same answer, and ",[825,8633,4502],{}," therefore carries ",[825,8636,866],{}," exactly as ",[825,8639,973],{},[817,8641,8644],{"className":8642,"code":8643,"language":822,"meta":823},[820],"cancelling the root      the status and approvals writes filter on root_run_id = :id,\n                         whatever the width; the control rows take the returned ids\ncancelling a mid-tree    batch each in.() filter at 200 ids, and page the walk\n",[825,8645,8643],{"__ignoreMap":823},[807,8647,8648,8650,8651,8654],{},[825,8649,8421],{}," is 1000, and it truncates a read ",[847,8652,8653],{},"silently",". The level by level walk must therefore page, and never treat one response as the whole level.",[807,8656,8657,8660,8661,8664,8665,8668,8669,8671,8672,8674,8675,8677,8678,5523,8681,8684,8685,8688,8689,8692],{},[847,8658,8659],{},"Page the walk by keyset, and end on an empty page."," Two things make the obvious form wrong. A Run has no ordering of its own, so PostgREST returns physical order, and any write that cannot be a ",[825,8662,8663],{},"HOT"," update relocates a row between two pages: measured, 1,500 children read in two ",[825,8666,8667],{},"offset"," pages with only ",[825,8670,1105],{}," rewritten between them answered 1,000 and then 500, missing 244 children and repeating 244. ",[825,8673,6561],{}," writes that column every ten seconds on every live Run, so the case is ordinary rather than rare. A Run is never deleted and its ",[825,8676,927],{}," never changes, so ",[825,8679,8680],{},"ORDER BY id",[825,8682,8683],{},"id > :last"," cannot skip one. And the loop must end on an ",[847,8686,8687],{},"empty"," page rather than a short one, because PostgREST applies the smaller of the client limit and ",[825,8690,8691],{},"db-max-rows",", which is a per project setting the application cannot read: a project that sets it below the page size answers a short first page, and a loop that stops there truncates the level with no error.",[807,8694,8695,8698,8699,8702,8703,8705,8706,8708],{},[847,8696,8697],{},"The approvals write is cleanup, not correctness."," A cancelled Run must not leave a row in a person's inbox, and ",[810,8700,8701],{"href":344},"human review"," names ",[825,8704,3342],{}," as the writer of that ",[825,8707,1329],{}," approval status. It sits last because the inbox already defends itself: the queue reads the Run state and shows an approval on a cancelled Run as non-actionable, so a lost write costs a stale row rather than a wrong action.",[807,8710,8711,8712,8714,8715,937,8717,8719,8720,8722,8723,8725,8726,8728],{},"Lose the tail and the Run still reads ",[825,8713,1329],{},", which is the answer the person asked for. A worker inside a long segment then keeps running with no signal, and the reaper cannot help: it reads ",[825,8716,1751],{},[825,8718,1062],{}," only, so a ",[825,8721,1329],{}," Run is invisible to it. The ceilings and ",[825,8724,2762],{}," bound that worker, and re-running ",[825,8727,2225],{}," places the missing rows. Order the writes the other way and the failure is worse in every case: the person is shown a failure the reaper wrote, on a Run they stopped themselves.",[807,8730,8731,924,8737,8739,8740,5058,8743,3864,8745,8747,8748,937,8750,8752,8753,965],{},[847,8732,8733,8734,8736],{},"Any transition out of ",[825,8735,889],{}," clears the waiting three in the same statement.",[825,8738,5053],{}," is an equivalence, not an implication, so ",[825,8741,8742],{},"status='cancelled'",[825,8744,896],{},[825,8746,1813],{}," and the cancel fails. It is the easiest line to leave out here, because the queued and running cases work without it and the waiting case is the one a person actually cancels. The same applies to ",[825,8749,1333],{},[825,8751,2628],{},", which the transition table also allows from ",[825,8754,889],{},[807,8756,8757,8760,8761,8763,8764,8766,8767,8769],{},[847,8758,8759],{},"Cancelling the root is one filter. Only a mid-tree cancel walks."," Cancelling a Run cancels its subtree, not its siblings and not its parent, so ",[825,8762,866],{}," is the wrong key for a mid-tree cancel: it would stop the whole tree, which is not what the person asked for. But when the Run ",[847,8765,5780],{}," the root, its subtree is exactly ",[825,8768,8502],{},", and every row carries that column. A person stopping a Run from a surface is always that case.",[807,8771,8772,8773,8775,8776,8779,8780,8782],{},"That matters because the walk cannot be a recursive CTE. ",[825,8774,1093],{}," reaches Postgres through PostgREST, which cannot express one, exactly as it cannot express the start transaction or a publish transaction. A mid-tree cancel therefore reads ",[825,8777,8778],{},"parent_run_id IN (:level)"," once per level, which the nesting depth cap bounds to a handful of round trips. Do not add a write RPC for it, and do not reach for ",[825,8781,866],{}," to avoid the walk.",[807,8784,8785,8786,8788],{},"A cancelled child fails its parent node, and ",[825,8787,5322],{}," decides the rest exactly as it does for any failed child.",[807,8790,8791,8794],{},[847,8792,8793],{},"The status is written before the control row, and the order is the whole guarantee."," They are two statements, and PostgREST gives them no transaction, so a crash lands between them.",[817,8796,8799],{"className":8797,"code":8798,"language":822,"meta":823},[820],"control row first, then a crash\n  control row set, status still `running`\n  nothing writes `cancelled`; the reaper reaches it later and writes `failed`\n  the person who pressed stop is shown a failure\n\nstatus first, then a crash\n  status `cancelled`, no control row\n  the worker misses its safe boundary and runs one step longer\n  its terminal write is refused, because the transition table stops at a terminal Run\n  the record is right and the work is wasted\n",[825,8800,8798],{"__ignoreMap":823},[807,8802,8803],{},"Wasted work is cheaper than a wrong status, so the status goes first. Cancel is idempotent, so the caller may simply run it again to place the missing control rows.",[807,8805,8806,8811,8812,1480,8814,8816,8817,8819,8820,8822,8823,8826],{},[847,8807,8808,8810],{},[825,8809,2225],{}," writes the status itself, and that is not redundant with the control row."," Inngest stops the function, so no worker reaches a finalize, and nothing else would ever move the Run out of ",[825,8813,1751],{},[825,8815,1062],{},". The reaper does not cover it either: the reaper writes ",[825,8818,1232],{},", and a Run a person stopped is ",[825,8821,1329],{},". The two writes answer two questions. The status is what every surface reads. The control row is what a ",[847,8824,8825],{},"still running"," step reads at its next safe boundary, and it stays a separate table because that check happens about forty times per Run.",[807,8828,8829,8832,8833,8835],{},[847,8830,8831],{},"The whole subtree moves in one pass, and terminal Runs are skipped."," A child that already succeeded keeps ",[825,8834,1240],{},"; cancelling a finished tree changes nothing and raises nothing. Cancel is therefore idempotent by construction: the second call matches zero rows.",[807,8837,8838],{},"Safe boundaries are: before a tool call, after a tool call, and between workflow nodes. Never stop an irreversible write or send in the middle. If an external call cannot be interrupted safely, let it settle, record its effect, then stop.",[807,8840,8841,8846,8847,8849,8850,8853,8854,1330,8856,8858],{},[847,8842,8843,8844,965],{},"Inside an agent segment the tool adapter is that boundary, and it reports ",[825,8845,1329],{}," Nothing else in the loop reaches a safe point: Agno owns the turns between the tool calls. So the adapter reads ",[825,8848,6127],{},", halts the loop the same way an approval halts it, and the segment returns ",[825,8851,8852],{},"stop = cancelled",". The status write already happened in ",[825,8855,2225],{},[825,8857,1359],{}," refuses a second transition on a terminal Run, so the report is a record rather than a decision. Without the member the adapter would have to call a cancel a failure, and the surfaces would disagree with the person who pressed stop.",[807,8860,8861,8864],{},[847,8862,8863],{},"Steering is deferred to V2."," A person who wants to correct a live Run cancels it, then starts it again with new input. Steering costs a control column, a read point at every safe boundary, and one more outcome branch on the front door, and cancel plus restart answers the same need in V1.",[828,8866,4197],{"id":8867},"ceilings",[807,8869,8870],{},"Every Run carries hard ceilings, from the definition budget and capped by the Policy limits.",[817,8872,8875],{"className":8873,"code":8874,"language":822,"meta":823},[820],"max_segments        the loop bound of the durable function          per Run\nmax_agent_turns     model turns in one Run                          per Run\nmax_tool_calls      tool calls in one Run                           per Run\nmax_run_duration    wall clock from started_at, including waits     per Run\nmax_cost_cents      reads the canonical usage meter at accrual      per TREE\n",[825,8876,8874],{"__ignoreMap":823},[807,8878,8879],{},[847,8880,8881],{},"Four ceilings are per Run. The cost ceiling is per tree, and it comes from the root.",[807,8883,8884,8885,8887,8888,8890],{},"A workflow creates a child Run for every agent and subworkflow step, and every one of those definitions carries its own ",[825,8886,6160],{},". If each child enforced its own, a workflow of ten children could spend ten budgets, and the meter sums by ",[825,8889,866],{}," anyway, so the two would disagree.",[817,8892,8895],{"className":8893,"code":8894,"language":822,"meta":823},[820],"a child Run inherits the root's max_cost_cents, and ignores its own\nthe other four ceilings stay the child's own\n",[825,8896,8894],{"__ignoreMap":823},[807,8898,8899,8900,8903],{},"The split is not arbitrary. Money is one bill, and the tree spends it together. Turns, tool calls, duration and segments bound a ",[847,8901,8902],{},"loop",", and each loop is its own, so a child that runs away must stop on its own count rather than on its siblings'.",[807,8905,8906],{},"This matches the two rules beside it. The child Principal is the parent Principal, and the child cost ceiling is the root's. Authority and money both come from the top; the loop bounds do not.",[807,8908,8909,8910,8912,8913,8916,8917,8919],{},"A ceiling settles the Run under ",[810,8911,6912],{"href":5530},": a ",[847,8914,8915],{},"successful partial Run"," when the Run produced output, and a failed Run when it produced none. Without ",[825,8918,2762],{}," a Run that waits on an event nobody sends never ends.",[2235,8921,8923],{"id":8922},"a-segment-receives-what-is-left-not-the-whole-ceiling","A segment receives what is left, not the whole ceiling",[807,8925,8926,937,8928,8930,8931,8933,8934,8937],{},[825,8927,4188],{},[825,8929,6885],{}," bound a ",[847,8932,1494],{},", and a Run can be five\nsegments. A segment handed the whole ceiling would spend it, and the next\nsegment would be handed it again, so a Run with ",[825,8935,8936],{},"max_agent_turns=20"," over five\nsegments could spend a hundred turns.",[807,8939,5328,8940,8942],{},[825,8941,4243],{}," subtracts before it builds the request, from the same durable\ncounts accrual reads.",[817,8944,8947],{"className":8945,"code":8946,"language":822,"meta":823},[820],"request.ceilings.max_agent_turns  =  ceiling  -  count of llm spans WHERE run_id = this Run\nrequest.ceilings.max_tool_calls   =  ceiling  -  count of tool spans WHERE run_id = this Run,\n                                                 attributes.replayed IS NOT true\nrequest.ceilings.max_run_duration =  ceiling  -  (now - started_at)\n",[825,8948,8946],{"__ignoreMap":823},[807,8950,8951,8961],{},[847,8952,8953,8954,8956,8957,1256,8959,965],{},"Every filter here is ",[825,8955,2974],{},". Only ",[825,8958,6160],{},[825,8960,866],{}," That is the same split as the section above, and it is the one line to get right: three of these bound a loop, and each loop is its own.",[807,8963,8964,8967,8968,8970],{},[847,8965,8966],{},"All three are remainders, including the duration."," The Run owns a wall clock\nfrom ",[825,8969,2754],{},", and a segment cannot apply that as a timeout without knowing\nwhat an earlier segment already spent. Every subtraction floors at zero, and a\nremainder of zero ends the Run before a segment starts.",[807,8972,8973,924,8976,8979,8980,8982,8983,965],{},[847,8974,8975],{},"The segment duration is not one of them.",[825,8977,8978],{},"MAX_SEGMENT_DURATION_S"," bounds the\nlargest step the worker runs, and it comes from the deploy rather than from a\ndefinition. The durable segment step applies that wall clock outside the agent\nexecutor. ",[825,8981,4061],{}," does not carry it. See ",[810,8984,8986],{"href":8985},"#the-inngest-boundary","the Inngest\nboundary",[807,8988,8989,924,8994,8996,8997,8999,9000,9002,9003,965],{},[847,8990,8991,8992,965],{},"The accrual check before a segment belongs here, not to ",[825,8993,7753],{},[825,8995,7753],{}," claims the Run and dispatches it by kind, once per function run, so it is not running when segment 4 starts. ",[825,8998,4243],{}," already subtracts the remainders at the top of every segment, and ",[825,9001,6731],{}," is the equivalent code at every node. Those two are the per-segment and per-node accrual callers. See ",[810,9004,1608],{"href":287},[807,9006,9007,9009,9010,9012,9013,9015],{},[825,9008,4203],{}," stays the ",[847,9011,4207],{}," count, which is what\nthe loop needs before the segment ends. Neither number becomes a column: the\nspan tree already holds both, and a counter would be one more thing to keep\ntrue. A remainder of zero ends the Run on ",[825,9014,4192],{},", exactly as a segment that\nreaches it mid loop does.",[807,9017,9018,9020,9021,9023,9024,9027],{},[825,9019,1263],{}," looks redundant beside the other four, and it is not. The others bound cost and wall clock from outside the code. ",[825,9022,1263],{}," is the bound on the ",[825,9025,9026],{},"while"," loop itself, and a durable function must never contain an unbounded loop. Keep it whatever the other ceilings say.",[807,9029,9030,9039],{},[847,9031,9032,9033,9035,9036,965],{},"The wall clock starts at ",[825,9034,2754],{},", not at ",[825,9037,9038],{},"created_at"," A Run queued behind an organization concurrency limit is not spending its budget. Measure from creation and 500 saved searches firing at 09:00 kill each other: the last one in the queue passes its ceiling before a worker ever claims it, and the failure looks like a timeout rather than the queue it is.",[807,9041,9042],{},"A Run that never starts is the reaper's case, not the ceiling's.",[807,9044,9045,9046,9048,9049,965],{},"Accrual checks ",[825,9047,6160],{}," at the top of each segment and each node, and a metered tool call checks it too. Without the second check a fan out inside one step can pass the ceiling by the width of the fan out. A metered call reads the run tree total only; the organization day belongs to the node boundary. See ",[810,9050,1608],{"href":287},[828,9052,9054],{"id":9053},"failure-retries-and-orphans","Failure, retries and orphans",[2270,9056,9057,9067],{},[2273,9058,9059],{},[2276,9060,9061,9064],{},[2279,9062,9063],{},"Case",[2279,9065,9066],{},"Handling",[2323,9068,9069,9077,9089,9097,9117,9135],{},[2276,9070,9071,9074],{},[2328,9072,9073],{},"A step raises a retryable error",[2328,9075,9076],{},"Inngest retries the step; completed matching journal entries return their stored results",[2276,9078,9079,9082],{},[2328,9080,9081],{},"A step raises a terminal error",[2328,9083,9084,9085,9088],{},"Raise ",[825,9086,9087],{},"NonRetriableError","; Inngest stops retrying at once",[2276,9090,9091,9094],{},[2328,9092,9093],{},"Policy denial, invalid definition, revoked connection",[2328,9095,9096],{},"Terminal. Retrying cannot change the answer",[2276,9098,9099,9102],{},[2328,9100,9101],{},"Retries are exhausted",[2328,9103,9104,9107,9108,9111,9112],{},[825,9105,9106],{},"inngest\u002Ffunction.failed"," reaches the ",[825,9109,9110],{},"run.failed"," handler, which fails the Run. ",[847,9113,9114,9115],{},"It filters on ",[825,9116,3541],{},[2276,9118,9119,9122],{},[2328,9120,9121],{},"The worker dies and no retry follows",[2328,9123,7711,9124,9126,9127,937,9129,9131,9132,9134],{},[825,9125,4476],{}," cron fails ",[825,9128,1751],{},[825,9130,1062],{}," Runs whose ",[825,9133,1105],{}," is stale, and closes their open spans",[2276,9136,9137,9140],{},[2328,9138,9139],{},"The worker dies while the Run waits",[2328,9141,9142,9143,9145,9146,9148,9149,9151],{},"The same cron runs the ",[847,9144,4480],{},", which fails a ",[825,9147,889],{}," Run past ",[825,9150,4473],{}," plus a grace window",[807,9153,9154,9155,9157,9158,9160,9161,9163],{},"Both backstops are needed. Without the ",[825,9156,9110],{}," handler a Run sits at ",[825,9159,1062],{}," forever after Inngest gives up. Without the reaper a Run that never reached Inngest sits at ",[825,9162,1751],{}," forever.",[807,9165,3746,9166,9169,9170,9173,9174,9177,9178,9180,9181,9183,9184,9186],{},[847,9167,9168],{},"The id in that event may carry the app id."," The SDK builds a function's fully qualified id as ",[825,9171,9172],{},"\u003Capp_id>-\u003Cfn_id>",", so the failure event is expected to read ",[825,9175,9176],{},"agencycore-agentic-run.execute",". Nothing measured yet says which form the ",[847,9179,4654],{}," carries, and a filter on the wrong one matches nothing while raising nothing — the Run simply stays ",[825,9182,1062],{}," until the reaper's window, hours later. The filter therefore accepts both exact strings until a live run narrows it. The filter also sits on the ",[847,9185,3696],{}," and not in the body, so a function that must not act never starts.",[807,9188,9189,9194,9195,9197,9198,9200,9201,9203],{},[847,9190,9191,9193],{},[825,9192,9106],{}," fires for every function in the app, so the handler filters on the one it owns."," The reaper emits it, and so does ",[825,9196,9110],{}," itself. Unfiltered, the handler tries to read a ",[825,9199,2974],{}," from an event that has none, and its own failure re-triggers it. One condition closes both: act only when the failed function is ",[825,9202,3541],{},", and read the Run ID from the original event data that the failure event carries.",[807,9205,9206],{},"A retry in progress needs no product state. The failed span is written before the retry starts, so the span tree shows the attempt that failed and the attempt that followed.",[807,9208,3746,9209,924,9215,9217,9218,9221],{},[847,9210,9211,9214],{},[825,9212,9213],{},"execution_ref.attempt"," does not move, so a surface cannot count retries from it.",[825,9216,1502],{}," runs inside a memoized step, so a later attempt of the same function run replays the first attempt's answer and writes nothing. The column records the attempt that claimed, and ",[825,9219,9220],{},"function_run_id"," is the field that correlates every attempt. A surface that wants a retry count reads the span tree, which does grow one failed span per attempt.",[2235,9223,2882],{"id":9224},"the-wait-sweep",[807,9226,1787,9227,9229,9230,5275,9233,937,9235,9237],{},[825,9228,889],{}," Run holds no worker, so it writes no heartbeat and the reaper's own read never sees it. ",[825,9231,9232],{},"iter_live_stale()",[825,9234,1751],{},[825,9236,1062],{}," and nothing else, and cross-document invariant 12 keeps it that way: a person answering an approval slowly must never be failed on a heartbeat.",[807,9239,9240,9241,2069,9243,9245,9246,9248,9249,937,9251,9253,9254,9257,9258,928,9260,928,9262,928,9264,937,9266,9268,9269,9272,9273,9276,9277,9279,9280,9282],{},"The Run row therefore carries ",[825,9242,4473],{},[825,9244,2632],{}," writes it, and every write that names a target other than ",[825,9247,889],{}," clears it, exactly as it clears ",[825,9250,896],{},[825,9252,900],{},". One rule in ",[825,9255,9256],{},"build_transition_values()"," owns all three, so ",[825,9259,1502],{},[825,9261,2623],{},[825,9263,1333],{},[825,9265,2628],{},[825,9267,2225],{}," repeat none of them. ",[825,9270,9271],{},"runs_waiting_deadline_shape"," is an ",[847,9274,9275],{},"equivalence",", not the weaker rule ",[825,9278,2859],{}," holds: a ",[825,9281,889],{}," Run with no deadline is a Run the sweep can never read, which is the fault the column exists to remove.",[807,9284,9285],{},"Three writers, and each one has a clock already.",[817,9287,9290],{"className":9288,"code":9289,"language":822,"meta":823},[820],"an agent segment      the approval's own expires_at\na wait node           the hold plan_wait() clamped to the run deadline\nthe admission hold    now(), on the first of its two marks\n",[825,9291,9289],{"__ignoreMap":823},[807,9293,9294,9300],{},[847,9295,9296,9297,9299],{},"The first admission mark writes ",[825,9298,2758],{},", and that is not a placeholder."," No approval row exists at that point, so no clock does either. The value states what the row means: this hold carries no clock, and if nothing replaces it inside the grace window then nothing ever will. The third step writes the approval's own expiry over it, milliseconds later. A Run stranded between the two is therefore recovered in one grace window rather than in one approval TTL.",[807,9302,9303,9306,9307,9310],{},[847,9304,9305],{},"The sweep reads a deadline plus a grace window, because no liveness lookup exists."," SDK 0.5.18 exposes no method that asks whether a function run is alive, which is the same limit that gives the reaper its six hour abandon window. The live waiter fires its timeout at the deadline and then writes the ending in two more steps, which take seconds even under retry. ",[825,9308,9309],{},"INNGEST_AGENTIC_WAIT_GRACE_SECONDS"," is ten minutes, far above that and far below any approval TTL.",[807,9312,9313,9316,9317,3011,9319,9321],{},[847,9314,9315],{},"The ending is one code, and the sweep reads no approval row."," A recovered Run ends ",[825,9318,1232],{},[825,9320,2886],{},". The sweep proves one fact — the Run passed its deadline and nothing wrote it — and that fact is the same for an approval wait, an event wait, a delay wait, and an admission hold whose approval row was never written. Two of those four hold no approval at all, so a code that named the approval would name nothing for them.",[807,9323,3746,9324,9329,9330,9333,9334,9336],{},[847,9325,9326,9327,965],{},"It is never ",[825,9328,1292],{}," That is a partial success the live waiter writes ",[847,9331,9332],{},"after it ran the timeout itself",". This sweep ran no timeout, and a person may have pressed Approve. ",[825,9335,1232],{}," states what happened: the platform lost the writer of a Run that was waiting. This is the recovery behaviour ENG-2120 chose for a Run whose approval was answered before the function run was lost — it takes the same ending as one nobody answered, because no cheaper reading of the row is honest.",[807,9338,9339,9342],{},[847,9340,9341],{},"The cost of that choice is written down here."," A person approves, the function run is then lost, and the Run stays dead until the approval's own deadline — up to the TTL. No earlier detection exists while the liveness lookup does not.",[807,9344,9345,9348,9349,9352,9353,9356,9357,9359,9360,9362,9363,9365],{},[847,9346,9347],{},"The sweep writes no approval row either."," Every pending read is already clocked: ",[825,9350,9351],{},"oldest_pending_for_run()"," filters ",[825,9354,9355],{},"expires_at > now",", and the inbox lists a ",[825,9358,4247],{}," row past its expiry in the expired page. So a row left ",[825,9361,4247],{}," by a lost function run is inert, in the inbox and in ",[825,9364,2623],{}," alike.",[807,9367,3746,9368,9371,9372,924,9375,924,9378,9381,9382,9384,9385,9388],{},[847,9369,9370],{},"The write is guarded by a filter, and never by a re-read."," It is conditional on ",[825,9373,9374],{},"status = 'waiting'",[847,9376,9377],{},"and",[825,9379,9380],{},"waiting_expires_at \u003C cutoff",", the same cutoff the page was read under. A person who answers in the gap either wakes the Run or gets it re-aimed, and ",[825,9383,9351],{}," reads a live row alone, so a re-aim always writes a deadline in the ",[847,9386,9387],{},"future",". That Run then matches zero rows. A re-read would cost one round trip for each Run and answer the same thing.",[807,9390,9391,924,9394,9396,9397,9399,9400,9402,9403,1867,9406,9409],{},[847,9392,9393],{},"One bounded read, and no keyset.",[825,9395,9232],{}," pages because a Run it leaves ",[825,9398,1751],{}," keeps its place and would fill every later page. This read has no such head: the sweep ends every Run it reads, and a Run it cannot end has left ",[825,9401,889],{}," already. The answer is ascending on the deadline, so the oldest go first and the next pass takes the rest. ",[825,9404,9405],{},"idx_runs_waiting_expires_at",[825,9407,9408],{},"(waiting_expires_at) WHERE status = 'waiting'",", so the filter and the order are one index range.",[807,9411,9412,924,9415,9418,9419,9421,9422,9425],{},[847,9413,9414],{},"It runs before the orphan span sweep, and the order carries the cause.",[825,9416,9417],{},"close_orphans()"," stamps ",[825,9420,2886],{}," on the spans of the Runs this sweep ended. Run it after, and the orphan sweep reads those same spans first and stamps the generic ",[825,9423,9424],{},"run_ended"," over the true code.",[807,9427,9428],{},"This sweep ends Runs. The two span sweeps close spans. They stay separate methods.",[2235,9430,9432],{"id":9431},"the-heartbeat-has-one-writer-per-moment","The heartbeat has one writer per moment",[807,9434,9435,9418,9437,9439,9440,9443],{},[825,9436,1502],{},[825,9438,1105],{}," once, and a segment may approach its 90 second wall clock. If nothing wrote it in between, the reaper would have no safe ",[825,9441,9442],{},"stale_after",": set it under the longest segment and it kills healthy work, set it over and a dead worker sits for that long.",[807,9445,9446,924,9449,9451,9452,9454],{},[847,9447,9448],{},"Two components write the column, and they cover moments that do not overlap.",[825,9450,1359],{}," stamps it at the insert and at each lifecycle write, because no span covers those two moments. ",[825,9453,6561],{}," owns everything between the claim and the finalize. Read the heading as one writer per moment, never as one statement in the code base.",[807,9456,9457,9462],{},[847,9458,9459,9461],{},[825,9460,6561],{}," writes the heartbeat."," It already fires on every span open, and it already carries the run ID, so it needs no new call site.",[817,9464,9467],{"className":9465,"code":9466,"language":822,"meta":823},[820],"span opened -> the process wrote this Run more than 10s ago\n               -> UPDATE agent.runs SET heartbeat_at = now() WHERE id = :run_id\n            -> the process wrote this root more than 60s ago\n               -> UPDATE agent.runs SET heartbeat_at = now() WHERE id = :root_run_id\n",[825,9468,9466],{"__ignoreMap":823},[807,9470,3746,9471,9474,9475,9478,9479,9481],{},[847,9472,9473],{},"The window is tested in the process, and it cannot be tested in the statement."," A PostgREST filter value is a literal Postgres casts, not SQL it evaluates, so ",[825,9476,9477],{},"heartbeat_at \u003C now() - 10s"," has no filter form. ",[810,9480,278],{"href":277}," carries the measurement and the reason an application clock cannot stand in for it.",[807,9483,3746,9484,9487],{},[847,9485,9486],{},"The reaper query below meets the same limit",", and it answers it differently: it is a scheduled read, so it computes its own cutoff and a clock that drifts changes when a dead Run is noticed rather than whether a live one survives.",[807,9489,9490],{},"The window keeps the write rate flat whatever the span rate. An agent turn, a tool call and a workflow node each open a span, so a live Run cannot go quiet for longer than one model call.",[807,9492,9493,9496,9497,9500,9501,9503,9504,9506],{},[847,9494,9495],{},"Two statements with two different guards, not one statement with one."," A Run's own row is touched by one worker, so 10 seconds is free. A root row is touched by ",[847,9498,9499],{},"every"," worker in its tree, and an email sequence has five hundred of them. At 10 seconds those five hundred contend on one row for a write that almost always matches nothing. At 60 seconds the root is touched at most once a minute however wide the tree grows, and ",[825,9502,9442],{}," is 120 seconds, so the reaper stays well clear. ",[810,9505,278],{"href":277}," owns the recorder that writes both.",[807,9508,9509,9512,9513,1867,9515,9517,9518,9520],{},[847,9510,9511],{},"The write covers the root as well as the Run, and that is not decoration."," A top level workflow suspended on ",[825,9514,3491],{},[825,9516,1062],{}," and writes no spans of its own, sometimes for ten minutes. Without the root row in the ",[825,9519,2782],{},", the reaper would see a stale heartbeat on a healthy parent whose child is working normally. A tree with any live work is live at its root.",[807,9522,9523,9526,9527,9529,9530,9532],{},[847,9524,9525],{},"It covers the root and the leaf, and not the levels between."," A subworkflow in the middle of a three deep tree is neither ",[825,9528,2974],{}," nor ",[825,9531,866],{}," for any span its descendants write. Two writes cannot be three, and walking every ancestor would put an unbounded loop on the hottest path in the schema. The reaper's Inngest liveness clause is what covers those rows instead.",[807,9534,9535,9542,9543,9545,9546,1867,9549,9551,9552,9554],{},[847,9536,9537,9539,9540,965],{},[825,9538,1105],{}," is set at insert, to ",[825,9541,9038],{}," The reaper reads ",[825,9544,1751],{}," too, and ",[825,9547,9548],{},"NULL \u003C now() - interval",[825,9550,8488],{},", so a Run that never reached a worker would never be reaped. That is the exact case the ",[825,9553,1751],{}," clause exists for.",[817,9556,9559],{"className":9557,"code":9558,"language":822,"meta":823},[820],"stale_after  >  the longest model call, plus a margin\n             >= 2 x the root heartbeat window\n             =  120 seconds in V1\n",[825,9560,9558],{"__ignoreMap":823},[807,9562,3746,9563,924,9566,9568,9569,9572,9573,9575],{},[847,9564,9565],{},"The second line is a floor, not a preference.",[825,9567,6561],{}," refreshes a ",[847,9570,9571],{},"root"," row at most once per 60 seconds per process, so a healthy root is quiet for that whole window just before each refresh. A ",[825,9574,9442],{}," one second above it leaves one second of margin, and any clock skew between the dyno and the database then fails live work. Two windows is what \"well clear\" means, and the settings field refuses less.",[807,9577,9578],{},"A Run that is genuinely stuck inside one call for longer than that is a Run we want the reaper to take.",[807,9580,9581,9584,9585,9587,9588,9590],{},[847,9582,9583],{},"A stale heartbeat is not enough on its own."," A Run held behind its own\nconcurrency lane is ",[825,9586,1751],{},", holds no worker, and writes no heartbeat — which\nis indistinguishable from a Run that never reached Inngest. 500 saved searches\nfiring at 09:00 is the designed case, and the four hundredth of them waits far\nlonger than any ",[825,9589,9442],{},". So the reaper also asks Inngest whether a\nfunction run is alive for that Run, and it only fails the ones with none.",[807,9592,9593,924,9599,9601,9602,9604],{},[847,9594,9595,9596,9598],{},"That question has two shapes, because a ",[825,9597,1751],{}," Run has no function run ID to ask about.",[825,9600,1054],{}," is written by ",[825,9603,1502],{},", so it is empty for exactly the Runs the clause was added to protect.",[2270,9606,9607,9620],{},[2273,9608,9609],{},[2276,9610,9611,9614,9617],{},[2279,9612,9613],{},"Status",[2279,9615,9616],{},"What the reaper has",[2279,9618,9619],{},"What it does",[2323,9621,9622,9636],{},[2276,9623,9624,9628,9633],{},[2328,9625,9626],{},[825,9627,1062],{},[2328,9629,9630],{},[825,9631,9632],{},"execution_ref.function_run_id",[2328,9634,9635],{},"one Inngest lookup; a live run means leave it",[2276,9637,9638,9642,9644],{},[2328,9639,9640],{},[825,9641,1751],{},[2328,9643,7503],{},[2328,9645,9646,9649],{},[847,9647,9648],{},"re-send the dispatch event",", and never fail it on age",[807,9651,9652,9653,9656],{},"The re-send is the cheaper answer and it needs no liveness API. The event ID is ",[825,9654,9655],{},"run.execute:\u003Crun_id>",", so Inngest drops it when the Run is already queued, and delivers it when the dispatch never landed. One send repairs the second case and costs nothing in the first.",[807,9658,3746,9659,9664,9665,9667,9668,9670,9671,9673],{},[847,9660,1787,9661,9663],{},[825,9662,1751],{}," Run is never failed on age, at any age."," An earlier draft failed one that had sat past the dedupe window. It cannot: a Run parked behind a saturated lane holds a ",[847,9666,8077],{}," function run, and its ",[825,9669,1105],{}," is frozen at the insert, so it is indistinguishable from a Run whose event was lost. Failing on age kills the healthy one and drops its work, and the Run then reads a reason that never happened — ",[825,9672,1502],{}," matches zero rows when the slot frees, and the function ends having done nothing.",[807,9675,9676,9677,9680],{},"The repair does not need that cutoff anyway. A send that never reached Inngest left no dedupe entry, so the ",[847,9678,9679],{},"first"," re-dispatch delivers, within a minute. Only an Inngest-side fault survives to the window, and no rule can tell it from a backlog.",[807,9682,9683,9686,9687,9689],{},[847,9684,9685],{},"Past the dedupe window the reaper stops sending, and still does not fail."," A re-send there is a new event: it would deliver a second function run beside a first that may still be queued, and ",[825,9688,1502],{}," is re-entrant so neither would refuse. The stuck-run alert reports what is left, and a person acts.",[807,9691,9692,9695,9696,9698,9699,9701],{},[847,9693,9694],{},"The event ID dedupes for 24 hours, and that bounds the repair."," Past the window a re-send is a new event, so a Run that has sat ",[825,9697,1751],{}," for a day is failed rather than re-sent. That is the correct answer anyway: nothing is coming for it. The same window bounds the stable ID that stops ",[825,9700,1359],{}," starting two workers on one Run, and it is the reason the ID is not a permanent guarantee.",[807,9703,9704,924,9707,9709,9710,9712],{},[847,9705,9706],{},"The middle of a deep tree is protected by this clause, and by nothing else.",[825,9708,6561],{}," refreshes two rows, the Run's own and its ",[847,9711,9571],{},". A workflow that invokes a subworkflow that invokes an agent has a middle parent that is neither: it writes no spans while it is suspended, and no descendant refreshes it. Its heartbeat goes stale on every pass. It survives only because it holds a live Inngest function run, so the liveness clause is not an optimisation and must not be dropped from the query.",[807,9714,9715,9720,9721,9723,9724,9727,9728,9731,9732,9734],{},[847,9716,1787,9717,9719],{},[825,9718,1751],{}," child is never re-dispatched, and its parent's status is the fact."," A child is invoked and never dispatched, so an event beside a live ",[825,9722,3491],{}," would execute it twice. A child under a parent that has ",[847,9725,9726],{},"ended"," is the opposite case: no invoke is coming and nothing else ends it, so the reaper fails it with ",[825,9729,9730],{},"parent_gone",". One read answers it — of the parents of the queued children this pass found, which have an ",[825,9733,908],{},". It mirrors the orphan span sweep exactly: a span running under a Run that ended, and a Run queued under a parent that ended.",[807,9736,9737,9740],{},[847,9738,9739],{},"One pass is bounded, and one Run's fault never ends it."," Every write is guarded and counted, because an unguarded raise on one tenant's Run aborts the whole pass — including the orphan sweep that follows the loop, in exactly the incident the reaper exists for. The pass also stops reading past a fixed number of Runs: a pass that runs past its own execution window closes no orphan span at all, and the next pass is a minute away.",[807,9742,9743,924,9746,9748,9749,4666,9751,965],{},[847,9744,9745],{},"The heartbeat read never takes a waiting Run.",[810,9747,2882],{"href":2854}," is the one part of this cron that ends one, and it reads ",[825,9750,4473],{},[825,9752,1105],{},[817,9754,9756],{"className":2254,"code":9755,"language":2256,"meta":823,"style":823},"WHERE status IN ('queued','running')\n  AND heartbeat_at \u003C now() - :stale_after\n  AND NOT EXISTS (a live Inngest function run for this Run)\n",[825,9757,9758,9763,9768],{"__ignoreMap":823},[1130,9759,9760],{"class":1132,"line":22},[1130,9761,9762],{},"WHERE status IN ('queued','running')\n",[1130,9764,9765],{"class":1132,"line":32},[1130,9766,9767],{},"  AND heartbeat_at \u003C now() - :stale_after\n",[1130,9769,9770],{"class":1132,"line":233},[1130,9771,9772],{},"  AND NOT EXISTS (a live Inngest function run for this Run)\n",[807,9774,9775,9776,1806,9778,9780],{},"A waiting Run holds no worker, so it writes no heartbeat. A read that only tested the heartbeat would fail every approval that a person answers slowly. A waiting Run has three owners instead: the Inngest wait timeout, computed from ",[825,9777,8269],{},[825,9779,2762],{},", which caps that timeout; and the wait sweep, which takes the Run when the first two lose their function run.",[817,9782,9784],{"className":2254,"code":9783,"language":2256,"meta":823,"style":823},"WHERE status = 'waiting'\n  AND waiting_expires_at \u003C now() - :wait_grace\nORDER BY waiting_expires_at\nLIMIT :wait_sweep_limit\n",[825,9785,9786,9791,9796,9801],{"__ignoreMap":823},[1130,9787,9788],{"class":1132,"line":22},[1130,9789,9790],{},"WHERE status = 'waiting'\n",[1130,9792,9793],{"class":1132,"line":32},[1130,9794,9795],{},"  AND waiting_expires_at \u003C now() - :wait_grace\n",[1130,9797,9798],{"class":1132,"line":233},[1130,9799,9800],{},"ORDER BY waiting_expires_at\n",[1130,9802,9803],{"class":1132,"line":244},[1130,9804,9805],{},"LIMIT :wait_sweep_limit\n",[807,9807,9808,9809,9811],{},"The reaper is a scheduled query, in the style of every other alert in ",[810,9810,4517],{"href":277},". It is not a second scheduler.",[807,9813,9814,9815,9817,9818,9821],{},"The reaper also closes the spans that died with the worker. A span is written open, so a crash leaves it ",[825,9816,1062],{}," with no end. In the same pass that fails the Run, the reaper closes its open spans with ",[825,9819,9820],{},"worker_lost",". Nothing else owns them.",[807,9823,9824,9827,9828,9830,9831,9833,9834,1480,9836,9838,9839,8212,9841,9843],{},[847,9825,9826],{},"It closes the spans of a Run that already ended, too."," Inngest cancels a Run by stopping its function, so the worker does not reach a finalize and its open spans stay ",[825,9829,1062],{}," under a ",[825,9832,1329],{}," Run. The heartbeat clause would never find them, because the Run is no longer ",[825,9835,1751],{},[825,9837,1062],{},". This sweep stamps ",[825,9840,9424],{},[825,9842,9820],{},": it reads every open span under an ended Run, so it cannot name a cause.",[817,9845,9847],{"className":2254,"code":9846,"language":2256,"meta":823,"style":823},"-- orphan spans, whatever ended the Run\nWHERE run.ended_at IS NOT NULL\n  AND span.status = 'running'\n",[825,9848,9849,9854,9859],{"__ignoreMap":823},[1130,9850,9851],{"class":1132,"line":22},[1130,9852,9853],{},"-- orphan spans, whatever ended the Run\n",[1130,9855,9856],{"class":1132,"line":32},[1130,9857,9858],{},"WHERE run.ended_at IS NOT NULL\n",[1130,9860,9861],{"class":1132,"line":233},[1130,9862,9863],{},"  AND span.status = 'running'\n",[807,9865,3746,9866,9869,9870,9872,9873,965],{},[847,9867,9868],{},"That is a read, and the write follows it by span id."," PostgREST ignores an embedded resource filter on an ",[825,9871,2244],{}," and still applies it to the response, so the one statement form closes every running span in the schema and reports only the rows it was asked for. See ",[810,9874,4517],{"href":277},[807,9876,9877,9878,9880],{},"That second clause also covers a Run failed by ",[825,9879,9106],{},". Every open span therefore has exactly one closer, and the orphan span alert stays a real alert rather than a permanent one.",[828,9882,9884],{"id":9883},"live-events","Live events",[807,9886,9887],{},"Durable state is Runs, spans and results. Live events are transient, best effort and possibly out of order. The Run row wins any disagreement.",[817,9889,9892],{"className":9890,"code":9891,"language":822,"meta":823},[820],"Executor \u002F SpanRecorder\n      -> LiveEventPublisher\n      -> Redis Pub\u002FSub\n      -> FastAPI SSE\n      -> Web \u002F Channel gateway\n",[825,9893,9891],{"__ignoreMap":823},[807,9895,9896],{},"This is the one live event vocabulary. A surface page links here. It does not restate it.",[817,9898,9901],{"className":9899,"code":9900,"language":822,"meta":823},[820],"run.updated        span.started       approval.requested\ntext.delta         span.completed     approval.resolved\ntool.updated       span.failed        result.item_ready\nrun.completed      run.failed\n",[825,9902,9900],{"__ignoreMap":823},[807,9904,9905,9906,937,9908,9910,9911,9914,9915,9917,9918,9921,9922,1480,9924,9926],{},"Every event carries ",[825,9907,2974],{},[825,9909,866],{},", and a span event carries ",[825,9912,9913],{},"span_id",". It carries no timestamp, because it needs none: every write to ",[825,9916,973],{}," is an insert, or an update filtered on ",[825,9919,9920],{},"status = 'running'",". A span therefore makes exactly one transition, and a client that holds a span as ",[825,9923,7482],{},[825,9925,3896],{}," ignores any later event about it.",[807,9928,9929,9932],{},[825,9930,9931],{},"result.item_ready"," carries one bounded product projection:",[817,9934,9938],{"className":9935,"code":9936,"language":9937,"meta":823,"style":823},"language-json shiki shiki-themes github-dark","{\n  \"type\": \"result.item_ready\",\n  \"run_id\": \"...\",\n  \"root_run_id\": \"...\",\n  \"item_kind\": \"prospect\",\n  \"item\": \"\u003CProspectSummary>\"\n}\n","json",[825,9939,9940,9946,9962,9974,9985,9997,10007],{"__ignoreMap":823},[1130,9941,9942],{"class":1132,"line":22},[1130,9943,9945],{"class":9944},"s95oV","{\n",[1130,9947,9948,9952,9955,9959],{"class":1132,"line":32},[1130,9949,9951],{"class":9950},"sDLfK","  \"type\"",[1130,9953,9954],{"class":9944},": ",[1130,9956,9958],{"class":9957},"sU2Wk","\"result.item_ready\"",[1130,9960,9961],{"class":9944},",\n",[1130,9963,9964,9967,9969,9972],{"class":1132,"line":233},[1130,9965,9966],{"class":9950},"  \"run_id\"",[1130,9968,9954],{"class":9944},[1130,9970,9971],{"class":9957},"\"...\"",[1130,9973,9961],{"class":9944},[1130,9975,9976,9979,9981,9983],{"class":1132,"line":244},[1130,9977,9978],{"class":9950},"  \"root_run_id\"",[1130,9980,9954],{"class":9944},[1130,9982,9971],{"class":9957},[1130,9984,9961],{"class":9944},[1130,9986,9987,9990,9992,9995],{"class":1132,"line":264},[1130,9988,9989],{"class":9950},"  \"item_kind\"",[1130,9991,9954],{"class":9944},[1130,9993,9994],{"class":9957},"\"prospect\"",[1130,9996,9961],{"class":9944},[1130,9998,9999,10002,10004],{"class":1132,"line":222},[1130,10000,10001],{"class":9950},"  \"item\"",[1130,10003,9954],{"class":9944},[1130,10005,10006],{"class":9957},"\"\u003CProspectSummary>\"\n",[1130,10008,10009],{"class":1132,"line":360},[1130,10010,2591],{"class":9944},[807,10012,10013,10014,928,10017,10020,10021,10024,10025,10028],{},"For ",[825,10015,10016],{},"item_kind = prospect",[825,10018,10019],{},"item"," has exactly the ",[825,10022,10023],{},"ProspectSummary"," fields in ",[810,10026,253],{"href":10027},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Finterfaces\u002Fsurfaces#the-projections",". It adds no detail fields, people, signals or provider data. The prospect write handler reads this projection from the durable row after its write completes. A read or publish fault changes no successful Tool result.",[807,10030,10031,10032,10035,10036,10039],{},"The item carries its own revision. A client upserts it by ",[825,10033,10034],{},"item.id"," and keeps the newer ",[825,10037,10038],{},"item.updated_at",". A repeated frame therefore changes nothing, and an older frame cannot replace a newer card. Equal revisions with different bodies are ambiguous, so the client refetches the durable row.",[807,10041,10042,10045,10046,10048],{},[847,10043,10044],{},"One channel per Run, and the publisher writes two."," Each event goes to the channel of the Run that produced it, and it is mirrored to the ",[847,10047,9571],{}," Run channel. A workflow parent therefore streams the work its child Runs do, and one subscription follows a whole tree. A root Run's own channel is the root channel, so a top level event is written one time.",[807,10050,10051,10054],{},[847,10052,10053],{},"The mirror stops at width."," A tree of five hundred children publishing every span event to one channel is a firehose no client can render, and the useful signal is the parent's progress, not five hundred simultaneous tool spans.",[817,10056,10059],{"className":10057,"code":10058,"language":822,"meta":823},[820],"tree under 20 live children   every child event is mirrored to the root channel\n20 or more                    the root channel carries child run.updated, run.completed\n                              and run.failed only\n                              the detail stays on each child's own channel\n",[825,10060,10058],{"__ignoreMap":823},[807,10062,10063,10065,10066,10069],{},[825,10064,9110],{}," is in that set for the reason ",[825,10067,10068],{},"run.completed"," is. A child that\nfailed and never reached the root would leave the parent showing a run that\nworks for the life of the page, and no later event corrects it.",[807,10071,10072,10073,10075],{},"The count is of ",[847,10074,8077],{}," children, and it is taken one time, when the run scope\nof a function run opens. A workflow that ran five hundred children one at a time\nfloods no channel, and the gate costs one statement per function run rather than\none per event. An unreadable count reads as wide: zero would open the root\nchannel to the tree the gate exists to stop.",[807,10077,3746,10078,10081],{},[847,10079,10080],{},"The subscriber reads the channel of the Run it opened, and it never rewrites the id."," A reader that resolved the root first would make the child channel unreachable, and the 20 child rule above would have no caller. The tree wide property is the publisher's mirror, not the reader's lookup. A client that opens one child therefore subscribes to it directly, which the Run Explorer does when a person expands a row.",[807,10083,10084],{},"The durable span tree is unchanged either way, so nothing is lost: the events are a live hint, and the record is the query.",[807,10086,10087,924,10090,2001,10093,10095,10096,10099,10100,10102,10103,10105],{},[847,10088,10089],{},"The reaper publishes, and its spans do not.",[825,10091,10092],{},"close_orphans",[825,10094,973],{}," through the repository, so no ",[825,10097,10098],{},"span.failed"," reaches a channel. The reaper therefore publishes ",[825,10101,9110],{}," after its last orphan page, and a client refetches the spans of a Run on each terminal Run event. Without that event a client keeps a reaped span on the screen as ",[825,10104,1062],{},", because no later event tells it to look again.",[807,10107,10108,924,10111,5275,10114,10117,10118,10120],{},[847,10109,10110],{},"One event per reaped Run, and it comes after the spans.",[825,10112,10113],{},"RunManager.fail",[825,10115,10116],{},"announce=False"," from the reaper. A client closes its stream on the first terminal event it reads, so an event published with the write would land while those spans still read ",[825,10119,1062],{},", and the refetch it triggers would read them that way. That publish also sat inside the page loop, one Run at a time, and one publish waits up to five seconds on a Redis that accepts a connection and answers nothing: a pass holding a thousand Runs would spend hours there, before the orphan sweep the pass bound exists to protect.",[807,10122,3746,10123,10126,10127,10129,10130,10132,10133,10136,10137,10139,10140,10142,10143,10146,10147,10149,10150,10153,10154,10156],{},[847,10124,10125],{},"A cancel closes no span, and the sweep is the only thing that may."," Inngest cancels a Run by stopping its function at the next step boundary, so the worker is alive while ",[825,10128,2321],{}," runs. A span ",[825,10131,2321],{}," stamped would lose the race the worker is about to win: ",[825,10134,10135],{},"SpanRecorder.close"," filters on ",[825,10138,9920],{},", so the worker's own close then matches zero rows and its ",[825,10141,1347],{}," and its ",[825,10144,10145],{},"usage_id"," are dropped. The row would read ",[825,10148,3896],{}," for a step that succeeded, and the link to its ",[825,10151,10152],{},"ai_usage_log"," row would be gone. The sweep runs long enough after the Run ends for the worker to have finished, which is why it, and not ",[825,10155,2321],{},", owns those spans.",[807,10158,10159,10160,10162,10163,10165],{},"The cost falls on the reader: a client that refetches on the Run's terminal event reads a cancelled Run's spans as ",[825,10161,1062],{},", and no later event corrects it, because the client closed its stream on that event. A live event is a hint and the durable record is the answer, so a client re-reads a Run it holds as terminal while any of its spans still reads ",[825,10164,1062],{},". No write races a live worker to make that true.",[807,10167,10168,10169,965],{},"On reconnect the client subscribes first, then refetches the Run and its spans, then applies what it buffered. The reverse order drops every event in the gap, and V1 has no event replay table. See ",[810,10170,253],{"href":249},[828,10172,10174],{"id":10173},"scenarios-that-shaped-this-design","Scenarios that shaped this design",[807,10176,10177],{},"Each row is a case the design was tested against. The right column names the mechanism that answers it.",[2270,10179,10180,10190],{},[2273,10181,10182],{},[2276,10183,10184,10187],{},[2279,10185,10186],{},"Scenario",[2279,10188,10189],{},"What answers it",[2323,10191,10192,10204,10214,10222,10232,10240,10248,10256,10269,10280,10288,10296,10306,10316,10324,10335,10347,10355,10365,10373,10381,10392,10402,10410,10418,10428,10436,10446,10460,10472,10482,10490,10500,10508,10516,10528,10536,10544,10552,10562,10570,10582],{},[2276,10193,10194,10201],{},[2328,10195,10196,10197,10200],{},"The worker dies after ",[825,10198,10199],{},"crm.update"," completed its journal entry, mid segment",[2328,10202,10203],{},"The replay journal returns the stored result on the retry",[2276,10205,10206,10209],{},[2328,10207,10208],{},"Slack redelivers the same message three times",[2328,10210,10211,10212],{},"The start key is unique on the Run row, so the second and third answer ",[825,10213,1735],{},[2276,10215,10216,10219],{},[2328,10217,10218],{},"A person approves 20 hours after the request",[2328,10220,10221],{},"The Run holds no worker; a fresh worker resumes from the snapshot",[2276,10223,10224,10227],{},[2328,10225,10226],{},"An approval expires while nobody looks",[2328,10228,10229,10230],{},"One clock: the Inngest timeout comes from ",[825,10231,4455],{},[2276,10233,10234,10237],{},[2328,10235,10236],{},"A reply arrives before the follow up timeout",[2328,10238,10239],{},"One durable wait with a timeout; the event wins the race",[2276,10241,10242,10245],{},[2328,10243,10244],{},"A person cancels a workflow with three live children",[2328,10246,10247],{},"Cancel writes the control row for the subtree and publishes per Run",[2276,10249,10250,10253],{},[2328,10251,10252],{},"An admin disables the definition mid Run",[2328,10254,10255],{},"The snapshot keeps the Run stable; new Run trees are refused, and a child of this tree still starts",[2276,10257,10258,10261],{},[2328,10259,10260],{},"An admin revokes a tool mid Run",[2328,10262,10263,10265,10266,10268],{},[825,10264,4915],{}," reads live tool state and returns a ",[825,10267,7653],{}," result",[2276,10270,10271,10274],{},[2328,10272,10273],{},"The budget runs out halfway through discovery",[2328,10275,10276,10277],{},"The Run succeeds with ",[825,10278,10279],{},"partial_reason=budget_exhausted",[2276,10281,10282,10285],{},[2328,10283,10284],{},"500 saved searches fire at 09:00",[2328,10286,10287],{},"Inngest concurrency keyed on the organization and the lane",[2276,10289,10290,10293],{},[2328,10291,10292],{},"A 500 person batch runs while a person waits in chat",[2328,10294,10295],{},"The batch lane and the interactive lane hold separate budgets",[2276,10297,10298,10303],{},[2328,10299,10300,10302],{},[825,10301,1359],{}," retries its dispatch send",[2328,10304,10305],{},"The stable event ID makes Inngest drop the duplicate",[2276,10307,10308,10311],{},[2328,10309,10310],{},"An agent task needs four minutes of model work",[2328,10312,10313,10314],{},"Each 90 second segment yields to the next durable segment until the task ends or reaches ",[825,10315,1263],{},[2276,10317,10318,10321],{},[2328,10319,10320],{},"A person wants to correct a live Run",[2328,10322,10323],{},"Cancel it, then start it again. Steering is V2",[2276,10325,10326,10329],{},[2328,10327,10328],{},"A child Run streams progress and the client watches the parent",[2328,10330,10331,10332,10334],{},"Every event goes to the ",[825,10333,866],{}," channel",[2276,10336,10337,10340],{},[2328,10338,10339],{},"Two prospect updates arrive out of order",[2328,10341,10342,10343,10035,10345],{},"The client upserts by ",[825,10344,10034],{},[825,10346,10038],{},[2276,10348,10349,10352],{},[2328,10350,10351],{},"A prospect event is dropped or its projection read fails",[2328,10353,10354],{},"The Tool still succeeds, and the terminal prospect-list refetch returns the durable projection",[2276,10356,10357,10360],{},[2328,10358,10359],{},"A workflow node starts a child Run",[2328,10361,10362,10364],{},[825,10363,1509],{}," skips the dispatch event, and the parent invokes it",[2276,10366,10367,10370],{},[2328,10368,10369],{},"A segment after approval has no message history on the new worker",[2328,10371,10372],{},"The Agno session is a row, keyed on the Run",[2276,10374,10375,10378],{},[2328,10376,10377],{},"A person approves and the model proposes something else",[2328,10379,10380],{},"The runtime executes the approved call from the approval row",[2276,10382,10383,10386],{},[2328,10384,10385],{},"A Run sits in the queue past its wall clock ceiling",[2328,10387,10388,10389,10391],{},"The clock starts at ",[825,10390,2754],{},"; the reaper owns a Run that never starts",[2276,10393,10394,10397],{},[2328,10395,10396],{},"Inngest gives up after the last retry",[2328,10398,10399,10401],{},[825,10400,9106],{}," fails the Run",[2276,10403,10404,10407],{},[2328,10405,10406],{},"A Run never reaches a worker at all",[2328,10408,10409],{},"The reaper fails it on a stale heartbeat",[2276,10411,10412,10415],{},[2328,10413,10414],{},"The worker dies part way through a start",[2328,10416,10417],{},"The insert is the last write, so the retry starts exactly one Run",[2276,10419,10420,10423],{},[2328,10421,10422],{},"Two webhook deliveries race on one start key",[2328,10424,10425,10426],{},"The unique index lets one insert; the other reads the row and answers ",[825,10427,1735],{},[2276,10429,10430,10433],{},[2328,10431,10432],{},"A duplicate delivery whose front door picks another definition",[2328,10434,10435],{},"The start key is hashed from the source, never from the model's choice",[2276,10437,10438,10441],{},[2328,10439,10440],{},"An Inngest retry re-claims a Run that has run for an hour",[2328,10442,10443,10445],{},[825,10444,2754],{}," is set once, so the wall clock ceiling still bites",[2276,10447,10448,10451],{},[2328,10449,10450],{},"A person cancels while the last tool call is settling",[2328,10452,10453,2001,10455,1330,10457,10459],{},[825,10454,2225],{},[825,10456,1329],{},[825,10458,1333],{}," is conditional, so it cannot overwrite",[2276,10461,10462,10465],{},[2328,10463,10464],{},"An approval resolves and two more segments follow",[2328,10466,10467,4467,10469,10471],{},[825,10468,2636],{},[825,10470,4937],{},", so only the woken segment rebuilds the brief",[2276,10473,10474,10477],{},[2328,10475,10476],{},"A node starts a child in the instant its parent is cancelled",[2328,10478,10479,10481],{},[825,10480,1509],{}," reads the parent Run status and refuses",[2276,10483,10484,10487],{},[2328,10485,10486],{},"500 triggers fire against a spent day cap",[2328,10488,10489],{},"The accrual gate refuses each before its resolve and freeze",[2276,10491,10492,10495],{},[2328,10493,10494],{},"A person rejects a run before it executes",[2328,10496,8152,10497,10499],{},[825,10498,1329],{},"; nobody waits on a dead approval",[2276,10501,10502,10505],{},[2328,10503,10504],{},"An admission approval is never answered",[2328,10506,10507],{},"The Run dispatched, so the wait timeout ends it",[2276,10509,10510,10513],{},[2328,10511,10512],{},"The worker dies inside a tool span",[2328,10514,10515],{},"The reaper closes the open span when it fails the Run",[2276,10517,10518,10521],{},[2328,10519,10520],{},"The user says \"stop that\" and two Runs are live",[2328,10522,10523,10524,10527],{},"The front door returns ",[825,10525,10526],{},"clarify","; it never guesses",[2276,10529,10530,10533],{},[2328,10531,10532],{},"A workflow needs people for the 12 companies that survived",[2328,10534,10535],{},"The fan out runs inside one step; the node set stays static",[2276,10537,10538,10541],{},[2328,10539,10540],{},"One branch of a parallel node waits three days for a person",[2328,10542,10543],{},"The branch step parks; its siblings settle without it",[2276,10545,10546,10549],{},[2328,10547,10548],{},"Ten workflows invoke ten children at a concurrency limit of ten",[2328,10550,10551],{},"A suspended parent holds no slot, so the children run",[2276,10553,10554,10557],{},[2328,10555,10556],{},"A segment approaches 90 seconds while the reaper sweeps",[2328,10558,10559,10561],{},[825,10560,6561],{}," refreshes the heartbeat on every span",[2276,10563,10564,10567],{},[2328,10565,10566],{},"A parent waits ten minutes on a child that is working",[2328,10568,10569],{},"The child's spans refresh the root heartbeat too",[2276,10571,10572,10575],{},[2328,10573,10574],{},"One branch approval resolves and another is still open",[2328,10576,10577,10579,10580],{},[825,10578,2623],{}," is conditional, so the Run stays ",[825,10581,889],{},[2276,10583,10584,10587],{},[2328,10585,10586],{},"An approval resolves and the next segment starts",[2328,10588,10589,10591,10592],{},[825,10590,2623],{}," moves the Run back to ",[825,10593,1062],{},[828,10595,10597],{"id":10596},"rules","Rules",[3103,10599,10600,10605,10634,10640,10649,10658,10661,10664,10667,10673,10681,10697,10705,10708,10711,10714,10721,10727,10730,10733,10736,10741,10744,10753,10756,10759,10762,10765,10768,10771,10776,10779,10791,10799,10809,10817,10825,10835,10838,10844,10855,10862,10869,10876,10881,10884,10887,10893,10899,10902,10905,10910,10913,10918,10924,10930,10933,10936,10939,10942,10945,10948,10951,10954,10957,10960,10963,10968,10971,10982,10985,10988,10999,11008,11013,11016,11019,11027,11030,11033],{},[3106,10601,10602,10604],{},[825,10603,1359],{}," owns the product state. Inngest owns the durability.",[3106,10606,10607,10609,10610,928,10612,928,10614,928,10616,928,10618,928,10620,928,10622,937,10624,2069,10626,10609,10628,10630,10631,10633],{},[825,10608,1359],{}," owns ",[825,10611,2644],{},[825,10613,896],{},[825,10615,900],{},[825,10617,908],{},[825,10619,2754],{},[825,10621,1054],{},[825,10623,2640],{},[825,10625,4937],{},[825,10627,6561],{},[825,10629,1105],{}," between the claim and the finalize; ",[825,10632,1359],{}," seeds it at the insert and stamps it on each lifecycle write, because those are the two moments no span covers. Nothing else writes a Run column.",[3106,10635,10636,10637,10639],{},"Every lifecycle write is one conditional ",[825,10638,2244],{}," against the legal from-states. Zero rows is a no-op, never an error.",[3106,10641,10642,10643,10645,10646,10648],{},"The transition primitive clears the waiting three whenever it names a target status other than ",[825,10644,889],{},". No method repeats the three lines, and ",[825,10647,2636],{}," names no status and touches none of the columns.",[3106,10650,10651,10652,10654,10655,10657],{},"Every write to the ",[825,10653,1801],{}," schema carries its own ",[825,10656,931],{}," filter. The service role bypasses RLS, so a policy filters nothing and raises nothing.",[3106,10659,10660],{},"The Run row carries its own start key, and the insert is the claim. There is no second table and no transaction on the start path.",[3106,10662,10663],{},"The start key names the delivery: the same intended start repeats it, the next intended start changes it. It never carries the definition the model chose.",[3106,10665,10666],{},"A duplicate start returns the Run the first one created. It is a success for every caller, and it sends no dispatch event. The reaper is the only repair for a send that never landed.",[3106,10668,10669,10670,10672],{},"The start key is a ",[825,10671,3303],{}," hex digest of the source and the delivery identity. A per-process hash gives one delivery two keys, and a digest of the delivery alone lets an API caller claim a trigger's key.",[3106,10674,10675,10677,10678,10680],{},[825,10676,1509],{}," calls four seams in one order. ",[825,10679,1903],{}," is one seam with two methods, one either side of the insert.",[3106,10682,1787,10683,10685,10686,10688,10689,10691,10692,10694,10695,965],{},[825,10684,1790],{}," definition never starts a Run. ",[825,10687,1509],{}," refuses it with ",[825,10690,1828],{},", before the insert can raise. The resolver answers tenancy alone, because ",[825,10693,2127],{}," from it means ",[825,10696,2101],{},[3106,10698,10699,10701,10702,10704],{},[825,10700,1509],{}," refuses a child whose parent Run reads ",[825,10703,1329],{},". It reads the status, never the control row.",[3106,10706,10707],{},"The organization day cap is checked before the resolve and the freeze, and its denial creates no Run.",[3106,10709,10710],{},"A rejected admission approval cancels the Run. An expired one fails it.",[3106,10712,10713],{},"The lane follows the actor kind, not the source alone.",[3106,10715,10716,10718,10719,965],{},[825,10717,1509],{}," dispatches only a top level Run. A child is invoked by its parent, and no HTTP caller supplies ",[825,10720,855],{},[3106,10722,10723,10724,10726],{},"The agent session is one row per Run, opaque above ",[825,10725,5859],{},", deleted with the Run.",[3106,10728,10729],{},"The Agno session identity is the Run ID. There is no second session identifier.",[3106,10731,10732],{},"A Run row carries its shape as constraints: the waiting pair, the waiting reference, the wait deadline and the ended pair.",[3106,10734,10735],{},"Span and session tenancy is a composite foreign key to the Run, never a copied column.",[3106,10737,10738,10740],{},[825,10739,6127],{}," stays its own table. A Run with no cancel request has no row, so the safe boundary check never reads the heap.",[3106,10742,10743],{},"A definition with Runs behind it is disabled, never deleted. The foreign key restricts.",[3106,10745,10746,10747,10749,10750,10752],{},"The runtime writes the session for every ",[825,10748,5839],{},", including the approval stop. A retryable failure writes nothing. The outer timer produces an ",[825,10751,3756],{}," and makes no session-write promise.",[3106,10754,10755],{},"The session row carries the composed instructions beside the Agno half, because Agno rebuilds the system block from them on every run.",[3106,10757,10758],{},"An approval is claimed on the same semantic key as the effect it guards, so a replayed segment reuses the pending row.",[3106,10760,10761],{},"An approval the segment did not stop on is superseded when the segment ends. A segment that raises supersedes nothing, because the replay reuses the row.",[3106,10763,10764],{},"A child Run inherits the root cost ceiling. The other four ceilings stay its own.",[3106,10766,10767],{},"A replayed model turn counts against the turn ceiling. A replayed tool call counts nothing.",[3106,10769,10770],{},"The reaper closes an open span on any Run that has ended, whatever ended it.",[3106,10772,10773,10774,965],{},"The wall clock ceiling runs from ",[825,10775,2754],{},[3106,10777,10778],{},"The Run snapshot is the stable execution input. Policy and tool state are read live.",[3106,10780,10781,937,10784,10787,10788,10790],{},[825,10782,10783],{},"RunResult.output",[825,10785,10786],{},"RunResult.refs"," are bounded at 32 KiB. The Run ",[825,10789,1420],{}," is refused above 32 KiB. The complete snapshot is refused above 256 KiB. Neither is trimmed.",[3106,10792,10793,10794,10796,10797,7881],{},"A successful write tool with an ",[825,10795,7871],{}," contributes ordered, unique refs to the workflow Run. Reads, failures and rows without an ",[825,10798,927],{},[3106,10800,10801,10803,10804,10806,10807,965],{},[825,10802,1494],{}," has a narrow read shape and a wide one. Only ",[825,10805,1502],{}," needs ",[825,10808,1450],{},[3106,10810,10811,10813,10814,10816],{},[825,10812,1502],{}," is a re-entrant conditional update, and it returns ",[825,10815,2127],{}," when the Run is no longer claimable.",[3106,10818,10819,10821,10822,10824],{},[825,10820,1502],{}," clears the waiting three, and it sets ",[825,10823,2754],{}," only once.",[3106,10826,10827,10829,10830,10832,10833,965],{},[825,10828,2225],{}," writes both the control row and the ",[825,10831,1329],{}," status, over the subtree it enumerates down ",[825,10834,855],{},[3106,10836,10837],{},"A mid-tree cancel writes each level before it reads the next, so no descendant is born unseen during the walk.",[3106,10839,10840,10841,10843],{},"Only the Runs a cancel actually moved get a control row. ",[825,10842,3094],{}," names them.",[3106,10845,10846,10848,10849,10851,10852,10854],{},[825,10847,2636],{}," is the one writer of ",[825,10850,2640],{},", and the one thing that clears ",[825,10853,4937],{}," between segments.",[3106,10856,10857,2001,10859,10861],{},[825,10858,6561],{},[825,10860,1105],{},": at most once every 10 seconds for the Run, and once every 60 for its root.",[3106,10863,10864,937,10866,10868],{},[825,10865,2632],{},[825,10867,2623],{}," are always written as a pair.",[3106,10870,10871,937,10873,10875],{},[825,10872,2640],{},[825,10874,4937],{}," live on the Run, because a fresh worker reads them.",[3106,10877,10878,10879,965],{},"A segment receives each remaining Run ceiling, including ",[825,10880,2762],{},[3106,10882,10883],{},"A remainder of zero ends the Run before a segment starts. The runtime is never called with a ceiling of zero.",[3106,10885,10886],{},"The segment duration limit is a 90 second deploy constant, not a definition ceiling.",[3106,10888,10889,10890,10892],{},"A timed segment leaves the Run ",[825,10891,1062],{},", advances once, and waits on nothing.",[3106,10894,10895,10896,10898],{},"A timed segment on the last allowed index ends with the existing ",[825,10897,1288],{}," result.",[3106,10900,10901],{},"A segment after a wall-clock yield continues a saved pause, continues saved history, or restarts from the original input when no session was saved.",[3106,10903,10904],{},"An approval proof survives a wall-clock yield only while the saved run remains paused; a fresh turn drops it.",[3106,10906,10907,10908,965],{},"Inside a segment the tool adapter is the cancellation boundary, and it reports ",[825,10909,1329],{},[3106,10911,10912],{},"The reaper fails a Run only when its heartbeat is stale AND no Inngest run is alive for it.",[3106,10914,10915,10917],{},[825,10916,2225],{}," writes the status before the control row, and cancelling the root needs no walk.",[3106,10919,10920,10921,10923],{},"A node that meets a ",[825,10922,1735],{}," uses the Run it gets back. It never starts a second child.",[3106,10925,10926,10927,10929],{},"A branch approval waits in place. It never raises out of a ",[825,10928,2681],{}," container.",[3106,10931,10932],{},"A suspended Inngest run holds no concurrency slot. A contract test pins it.",[3106,10934,10935],{},"One Inngest function per Run. One step per agent segment or workflow node.",[3106,10937,10938],{},"A wide tool runs ordered read calls inside one node step and one full-item timeout per call.",[3106,10940,10941],{},"A wide node reserves 10 seconds for work outside its item clocks and has one outer step-budget guard.",[3106,10943,10944],{},"Business failures are item values. A ceiling, approval or platform fault stops the node under its normal run rule.",[3106,10946,10947],{},"A wide node never returns more than Inngest's 4 MiB step-output limit.",[3106,10949,10950],{},"A tool idempotency key uses the run, the step path and the argument hash. It never uses the model tool call ID.",[3106,10952,10953],{},"An approval wait is durable data plus an Inngest wait. It is never an in memory object.",[3106,10955,10956],{},"One expiry clock per wait, and one waiter that owns it.",[3106,10958,10959],{},"An approval dispatches the function, whether it came from admission or from a tool call.",[3106,10961,10962],{},"A ceiling produces a successful partial Run when the Run produced output, and a failed Run when it produced none. A fault produces a failed Run.",[3106,10964,10965,10966,965],{},"A retry is telemetry. A Run under retry stays ",[825,10967,1062],{},[3106,10969,10970],{},"The dispatch event carries a stable ID, so one Run gets one function run, for the 24 hours Inngest dedupes an event ID.",[3106,10972,10973,10974,928,10976,937,10978,10981],{},"An invoked child carries ",[825,10975,2974],{},[825,10977,931],{},[825,10979,10980],{},"lane"," in its payload, because the concurrency key reads all three.",[3106,10983,10984],{},"Every child Run takes the batch lane, whatever started its tree.",[3106,10986,10987],{},"A child Run's start key is its parent Run and its node ID. It is the one start key that is not a delivery identity.",[3106,10989,7711,10990,10992,10993,10995,10996,10998],{},[825,10991,9110],{}," handler acts on ",[825,10994,3541],{}," only. Every function in the app emits ",[825,10997,9106],{},", including that handler.",[3106,11000,11001,11002,11004,11005,11007],{},"The reaper re-dispatches a ",[825,11003,1751],{}," Run before it fails one. It interrogates Inngest only for a ",[825,11006,1062],{}," Run.",[3106,11009,11010,11012],{},[825,11011,6561],{}," refreshes the Run and its root. The levels between are covered by the reaper's liveness clause, and by nothing else.",[3106,11014,11015],{},"Interactive work and batch work hold separate concurrency lanes.",[3106,11017,11018],{},"Every Inngest function runs on the Connect worker, never on a web request. There is no short-function exemption.",[3106,11020,11021,11022,937,11024,11026],{},"The reaper reads ",[825,11023,1751],{},[825,11025,1062],{}," only. A waiting Run has its own clock.",[3106,11028,11029],{},"Every Run has a wall clock ceiling.",[3106,11031,11032],{},"A durable span is written before the best effort live event.",[3106,11034,11035],{},"A web request handler never runs long agent work.",[828,11037,11039],{"id":11038},"minimum-contract-tests","Minimum contract tests",[3103,11041,11042,11045,11051,11057,11065,11068,11077,11085,11091,11099,11102,11107,11110,11116,11126,11131,11134,11137,11147,11150,11155,11163,11169,11174,11179,11184,11187,11190,11198,11205,11210,11213,11216,11221,11227,11234,11241,11244,11249,11252,11255,11258,11261,11274,11277,11280,11283,11293,11302,11305,11314,11320,11325,11328,11334,11339,11345,11354,11357,11360,11365,11368,11371,11374,11377,11380,11383,11389,11395,11404,11412,11417,11420,11423,11429,11432,11437,11440,11443,11446,11449,11456,11459,11466,11469,11474,11479,11484,11489,11497,11502,11513,11521,11524,11527,11530,11533,11536,11539,11547,11550,11555,11558,11561,11567,11570,11576,11579,11587,11590,11593,11596,11603,11610,11616,11622,11629,11632,11635,11638,11641,11644,11650,11655,11661,11664,11667,11670,11676,11679,11682,11688,11691,11694],{},[3106,11043,11044],{},"A front door command and a trigger command create the same Run shape.",[3106,11046,11047,11048,11050],{},"A start against another organization's definition returns ",[825,11049,2101],{},", exactly as a missing one does.",[3106,11052,11053,11054,11056],{},"A start against the caller's own draft returns ",[825,11055,1828],{},", whether or not it has a parent.",[3106,11058,11059,11060,11062,11063,965],{},"A start against a disabled definition returns ",[825,11061,1828],{}," at the top level, and starts normally with a ",[825,11064,855],{},[3106,11066,11067],{},"The Run snapshot is never truncated; an oversized freeze fails the start.",[3106,11069,11070,11071,11073,11074,11076],{},"A start whose ",[825,11072,1420],{}," is over 32 KB answers ",[825,11075,1437],{}," and creates no Run row.",[3106,11078,11079,11080,3007,11082,11084],{},"A start against a published ",[825,11081,1790],{},[825,11083,1828],{},", and the insert is never reached.",[3106,11086,11087,11088,11090],{},"A duplicate ",[825,11089,3484],{}," returns the same Run and the same result, and sends no dispatch event.",[3106,11092,11093,11094,937,11096,11098],{},"A start reads its parent once, and the child carries the parent's ",[825,11095,866],{},[825,11097,931],{}," from that read.",[3106,11100,11101],{},"A crash before the Run insert leaves no Run, and the retry starts exactly one.",[3106,11103,11104,11105,7997],{},"Twenty concurrent starts on one key produce one Run, and nineteen ",[825,11106,1735],{},[3106,11108,11109],{},"A daily trigger starts a new Run every day, and its key does not collide with yesterday's.",[3106,11111,11112,11113,11115],{},"Cancelling a Run that is ",[825,11114,889],{}," clears the waiting three, and the shape constraint accepts it.",[3106,11117,1787,11118,9148,11120,11122,11123,11125],{},[825,11119,889],{},[825,11121,4473],{}," plus the grace window is ended ",[825,11124,2886],{}," by the wait sweep.",[3106,11127,1787,11128,11130],{},[825,11129,889],{}," Run inside its deadline, or inside the grace window, is left alone.",[3106,11132,11133],{},"A Run re-aimed at a later wait between the sweep's read and its write matches zero rows, and the sweep leaves it.",[3106,11135,11136],{},"A duplicate delivery that resolves a different definition still returns the first Run.",[3106,11138,11139,3007,11142,11144,11145,965],{},[825,11140,11141],{},"POST \u002Fapi\u002Fv1\u002Fagentic\u002Fruns",[825,11143,4555],{}," with the existing Run for a repeated ",[825,11146,3255],{},[3106,11148,11149],{},"Every legal cell of the transition table moves the Run, and every dash is a no-op.",[3106,11151,11152,11154],{},[825,11153,1333],{}," cannot overwrite a Run that was cancelled a moment earlier.",[3106,11156,11157,11159,11160,11162],{},[825,11158,1502],{}," on a Run waiting on an admission approval clears ",[825,11161,896],{}," and does not break the shape constraint.",[3106,11164,11165,11166,11168],{},"A re-claim after an hour of running leaves ",[825,11167,2754],{}," unchanged.",[3106,11170,11171,11173],{},[825,11172,2225],{}," stops the subtree below the named Run, and leaves its parent and its siblings alone.",[3106,11175,11176,11178],{},[825,11177,2225],{}," on a finished Run changes nothing and raises nothing.",[3106,11180,11181,11183],{},[825,11182,2225],{}," with another organization's run ID changes no row.",[3106,11185,11186],{},"A child started against a level the cancel walk has already written is refused, so a deep tree loses nobody.",[3106,11188,11189],{},"A cancel of a tree whose children already succeeded writes no control row for them.",[3106,11191,11192,11193,11195,11196,965],{},"A child row cannot be written with a ",[825,11194,866],{}," that disagrees with its ",[825,11197,855],{},[3106,11199,11200,4467,11202,11204],{},[825,11201,2636],{},[825,11203,4937],{},", so segment 4 does not rebuild the brief that segment 3 rebuilt.",[3106,11206,11207,11209],{},[825,11208,2636],{}," run twice from the same index advances the Run once.",[3106,11211,11212],{},"A child started immediately after its parent is cancelled is refused.",[3106,11214,11215],{},"500 trigger runs over the day cap are denied before the freeze, and none creates a Run row.",[3106,11217,11218,11219,965],{},"A start denied for the organization day cap writes a decision with a null ",[825,11220,2974],{},[3106,11222,11223,11224,11226],{},"A rejected admission approval leaves the Run ",[825,11225,1329],{}," and sends nothing.",[3106,11228,11229,11230,5523,11232,965],{},"An expired admission approval leaves the Run ",[825,11231,1232],{},[825,11233,1292],{},[3106,11235,11236,11237,5523,11239,965],{},"An expired action approval leaves the Run ",[825,11238,1240],{},[825,11240,1221],{},[3106,11242,11243],{},"A batch actor starting through the API does not take the interactive lane.",[3106,11245,11246,11248],{},[825,11247,1502],{}," succeeds on a retry of the same Run.",[3106,11250,11251],{},"A worker restart during an approval resumes and executes the tool once.",[3106,11253,11254],{},"A resume executes the approved call when the model proposes nothing.",[3106,11256,11257],{},"A child Run executes once, and no dispatch event is sent for it.",[3106,11259,11260],{},"A workflow node walk over the same definition and the same memoized outputs produces the same node order and the same step IDs.",[3106,11262,11263,11264,11266,11267,11269,11270,5769,11272,965],{},"A node declaring ",[825,11265,5322],{}," still stops on ",[825,11268,7189],{},", on ",[825,11271,7201],{},[825,11273,7177],{},[3106,11275,11276],{},"A tool refused for the run cost ceiling stops its node, and the Run stays a partial success when an earlier node produced output.",[3106,11278,11279],{},"A handler that answers the money code itself marks nothing, and the Run fails.",[3106,11281,11282],{},"A child Run that spent a ceiling stops its parent node, and names the clock in a field.",[3106,11284,11285,11286,1480,11288,11290,11291,965],{},"A start refused for ",[825,11287,7562],{},[825,11289,3022],{}," stops its parent node; every other refusal answers ",[825,11292,7547],{},[3106,11294,11295,11296,11298,11299,11301],{},"A child ended by admission carries its code on ",[825,11297,7962],{},", and the node reads it. A fault stops the node whatever ",[825,11300,5322],{}," says.",[3106,11303,11304],{},"A tool handler that answers the money code leaves the child Run error unmarked, and the parent fails.",[3106,11306,11307,11308,11310,11311,11313],{},"A tool handler cannot write ",[825,11309,1221],{},"; the invoker rebuilds ",[825,11312,7441],{}," on the handler path.",[3106,11315,11316,11317,11319],{},"A child that succeeded partially leaves its parent Run ",[825,11318,1240],{}," with the same clock, in a sequence and in a parallel.",[3106,11321,1787,11322,11324],{},[825,11323,2681],{}," whose branches carry a marked ceiling and a bare platform stop fails the Run, in either branch order.",[3106,11326,11327],{},"A child Run that ended under a platform stop code stops its parent node, and the parent Run reports failed.",[3106,11329,11330,11331,11333],{},"A retried child node meets ",[825,11332,1735],{},", and the tree holds one child.",[3106,11335,1787,11336,11338],{},[825,11337,2681],{}," node of two branches, each of two steps, advances one branch while the other parks.",[3106,11340,11341,11342,11344],{},"A failing branch of a ",[825,11343,2681],{}," node settles its siblings and never raises out of the container.",[3106,11346,11347,11348,11350,11351,11353],{},"A width-one ",[825,11349,6457],{}," node produces one ",[825,11352,6457],{}," span and no run row.",[3106,11355,11356],{},"A wide tool preserves input order when calls finish out of order.",[3106,11358,11359],{},"An empty fan-out list succeeds without a call. An invalid or over-width list fails before a call.",[3106,11361,11362,11363,11301],{},"One business failure in a wide tool does not stop its siblings, whatever ",[825,11364,5322],{},[3106,11366,11367],{},"A call ceiling drains the current fan-out batch, keeps results through the first stopping input index and discards later results.",[3106,11369,11370],{},"A later item that finishes before an earlier ceiling still leaves no hole in the ordered output.",[3106,11372,11373],{},"A returned hard platform stop wins when its batch also contains a call or cost ceiling.",[3106,11375,11376],{},"The lowest input position selects between multiple returned hard stops.",[3106,11378,11379],{},"An exception observed with a ceiling uses the exception action and cancels the remaining workers.",[3106,11381,11382],{},"A raised platform fault cancels the current batch and retries the whole read-only step.",[3106,11384,11385,11386,11388],{},"A read approval cancels the batch and ends with non-tolerable ",[825,11387,4278],{},"; it spends no retry.",[3106,11390,11391,11392,11394],{},"A wide result above Inngest's 4 MiB step-output limit fails with ",[825,11393,7041],{}," before the step returns.",[3106,11396,11397,11398,11400,11401,11403],{},"A wide item stopped before ",[825,11399,4915],{}," starts writes no ",[825,11402,6457],{}," span; every started call writes one.",[3106,11405,6850,11406,11408,11409,11411],{},[825,11407,1801],{}," node opens no span of its own, and its child's ",[825,11410,860],{}," span names the node.",[3106,11413,1787,11414,11416],{},[825,11415,3945],{}," node at the cap refuses the child with the depth error, and reads the frozen child definition from a versioned parent snapshot. Legacy snapshots retain their original live-definition behavior.",[3106,11418,11419],{},"A workflow run cancelled between two nodes stops at the next node and writes no further effect.",[3106,11421,11422],{},"The workflow run result holds one key per node that produced an output, and none for a node that did not run.",[3106,11424,11425,11426,11428],{},"Successful write tools add ordered, unique refs to the workflow result. Reads, failures and rows without an ",[825,11427,927],{}," add none.",[3106,11430,11431],{},"A Run that waits in the queue does not consume its wall clock ceiling.",[3106,11433,11434,11435,7714],{},"A finished Run leaves no ",[825,11436,976],{},[3106,11438,11439],{},"A segment that ends on an approval leaves its session written.",[3106,11441,11442],{},"A segment that fails retryably leaves the earlier session untouched, so the replay starts clean.",[3106,11444,11445],{},"A second segment carries the brief with no rebuild, because the runtime stored what it composed.",[3106,11447,11448],{},"A segment that dies after persisting an approval reuses that row on the replay, and the inbox holds one item.",[3106,11450,11451,11452,5616,11454,965],{},"A run cancelled mid segment ends ",[825,11453,1329],{},[825,11455,1232],{},[3106,11457,11458],{},"A segment after approval reads one system block, and it holds the refreshed brief.",[3106,11460,11461,11462,1256,11464,965],{},"No component above ",[825,11463,5859],{},[825,11465,5819],{},[3106,11467,11468],{},"A workflow of ten children cannot spend ten cost budgets.",[3106,11470,11471,11472,965],{},"A cancelled Run leaves no span in ",[825,11473,1062],{},[3106,11475,11476,11477,965],{},"A retried segment's model turns count against ",[825,11478,4188],{},[3106,11480,1787,11481,11483],{},[825,11482,1337],{}," above 32 KB is truncated without breaking its shape, and the result still parses.",[3106,11485,11486,11488],{},[825,11487,1399],{}," replaces every item that is over the limit on its own, not the first one it meets.",[3106,11490,11491,11493,11494,11496],{},[825,11492,1399],{}," reports ",[825,11495,1355],{}," for a replacement as well as for a drop.",[3106,11498,11499,11501],{},[825,11500,1399],{}," on a dict of ten thousand short keys returns inside the limit on the first confirmation, because the envelope is counted.",[3106,11503,1787,11504,11506,11507,937,11509,11512],{},[825,11505,1403],{}," marker parses, and a payload that really holds ",[825,11508,1832],{},[825,11510,11511],{},"bytes"," is not read as one.",[3106,11514,1787,11515,11517,11518,11520],{},[825,11516,1337],{}," carrying five hundred refs is bounded, and the ",[825,11519,1392],{}," column stays inside the limit.",[3106,11522,11523],{},"A completed matching journal entry returns its stored tool result.",[3106,11525,11526],{},"A restarted segment replays completed calls; interrupted and new calls keep the claim and lease rules.",[3106,11528,11529],{},"An expired approval releases the Run, and the Run does not send.",[3106,11531,11532],{},"An approval resolved in the same instant as its timeout still executes.",[3106,11534,11535],{},"Cancelling a parent cancels every descendant Run.",[3106,11537,11538],{},"Cancelling the root of a wide tree issues one status update, not one per child.",[3106,11540,11541,11542,11544,11545,965],{},"A crash between the status write and the control row leaves the Run ",[825,11543,1329],{},", never ",[825,11546,1062],{},[3106,11548,11549],{},"A child started against a parent cancelled one statement ago is refused.",[3106,11551,11552,11554],{},[825,11553,2636],{}," does not advance a Run that has ended.",[3106,11556,11557],{},"A failed parallel branch does not stop its siblings.",[3106,11559,11560],{},"A parallel branch waiting on an approval does not stop its siblings.",[3106,11562,11563,11564,11566],{},"One of two branch approvals resolving leaves the Run ",[825,11565,889],{}," on the other.",[3106,11568,11569],{},"A workflow parent suspended on a child for ten minutes is not reaped.",[3106,11571,11572,11573,11575],{},"A Run that never reaches a worker is reaped, because ",[825,11574,1105],{}," was set at insert.",[3106,11577,11578],{},"Two branch approvals in one parallel node resolve in any order.",[3106,11580,11581,11582,5616,11584,11586],{},"A Run is ",[825,11583,1062],{},[825,11585,889],{},", while it executes the segment after an approval.",[3106,11588,11589],{},"A four minute agent task spans timed segments and is not reaped, and a dead worker is reaped inside 120 seconds.",[3106,11591,11592],{},"A parent Run that invokes a child releases its concurrency slot.",[3106,11594,11595],{},"A tree three deep runs to completion at a concurrency limit of one.",[3106,11597,11598,2785,11600,11602],{},[825,11599,1502],{},[825,11601,2127],{}," for a Run cancelled between dispatch and claim.",[3106,11604,11605,11606,5523,11608,965],{},"A budget stop after work produces ",[825,11607,1240],{},[825,11609,1221],{},[3106,11611,11612,11613,11615],{},"A budget stop before any work produces ",[825,11614,1232],{},", and the error carries the clock.",[3106,11617,11618,11619,11621],{},"A retried step leaves the Run at ",[825,11620,1062],{}," and writes a failed span for the earlier attempt.",[3106,11623,11624,11626,11627,965],{},[825,11625,9106],{}," moves the Run out of ",[825,11628,1062],{},[3106,11630,11631],{},"The reaper fails a Run with a stale heartbeat and no live Inngest run.",[3106,11633,11634],{},"The reaper closes the open spans of the Run it failed.",[3106,11636,11637],{},"A Run waiting three days on an approval survives every reaper pass.",[3106,11639,11640],{},"Two dispatch sends for one Run produce one Inngest function run.",[3106,11642,11643],{},"An invoked child lands in the batch concurrency bucket of its own organization, not in a shared one.",[3106,11645,11646,11647,11649],{},"A retried workflow node meets ",[825,11648,1735],{}," and the tree ends with one child, because the start key is the node ID.",[3106,11651,11001,11652,11654],{},[825,11653,1751],{}," Run whose event never landed, and fails it on the next pass if it did not move.",[3106,11656,11657,11658,11660],{},"A reaper failure does not trigger the ",[825,11659,9110],{}," handler.",[3106,11662,11663],{},"The middle Run of a three deep tree is not reaped while its descendant is working.",[3106,11665,11666],{},"A batch Run cannot consume the interactive concurrency budget.",[3106,11668,11669],{},"An admission approval that is never answered ends the Run at its timeout.",[3106,11671,11672,11673,11675],{},"A Run waiting on an admission approval stays ",[825,11674,889],{},", and the reaper leaves it alone.",[3106,11677,11678],{},"An SSE failure does not corrupt the durable Run or span state.",[3106,11680,11681],{},"A child Run event reaches the root Run channel.",[3106,11683,11684,11685,11687],{},"A Run of five segments cannot spend ",[825,11686,4188],{}," five times.",[3106,11689,11690],{},"A segment after approval rebuilds the brief. A completed segment replay does not, and a failed segment retry can.",[3106,11692,11693],{},"A Run queued behind its concurrency lane for ten minutes is not reaped.",[3106,11695,11696,11697,11699],{},"A node that re-runs its start meets ",[825,11698,1735],{},", uses that Run, and the tree ends with one child.",[11701,11702,11703],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":823,"searchDepth":32,"depth":233,"links":11705},[11706,11707,11708,11709,11712,11719,11724,11725,11743,11744,11745,11748,11752,11753,11754,11755],{"id":830,"depth":32,"text":831},{"id":1120,"depth":32,"text":1121},{"id":1516,"depth":32,"text":1517},{"id":2145,"depth":32,"text":1359,"children":11710},[11711],{"id":2237,"depth":233,"text":2238},{"id":2791,"depth":32,"text":2792,"children":11713},[11714,11715,11716,11717,11718],{"id":2814,"depth":233,"text":2815},{"id":2912,"depth":233,"text":2913},{"id":3047,"depth":233,"text":960},{"id":3336,"depth":233,"text":3337},{"id":3468,"depth":233,"text":3469},{"id":3601,"depth":32,"text":3602,"children":11720},[11721,11722,11723],{"id":4087,"depth":233,"text":4088},{"id":4300,"depth":233,"text":4301},{"id":5634,"depth":233,"text":5635},{"id":5909,"depth":32,"text":5910},{"id":6320,"depth":32,"text":6321,"children":11726},[11727,11729,11730,11731,11733,11734,11736,11737,11738,11739,11740,11741,11742],{"id":6333,"depth":233,"text":11728},"The walk lives in WorkflowExecutor, not in the function",{"id":6451,"depth":233,"text":6452},{"id":6501,"depth":233,"text":6502},{"id":6572,"depth":233,"text":11732},"A parallel node runs in race mode",{"id":6679,"depth":233,"text":6680},{"id":6879,"depth":233,"text":11735},"A tool node spends max_tool_calls",{"id":6938,"depth":233,"text":6939},{"id":7081,"depth":233,"text":7082},{"id":7698,"depth":233,"text":7699},{"id":7791,"depth":233,"text":7792},{"id":7897,"depth":233,"text":7898},{"id":8083,"depth":233,"text":8084},{"id":8100,"depth":233,"text":8101},{"id":8217,"depth":32,"text":8218},{"id":377,"depth":32,"text":8291},{"id":8867,"depth":32,"text":4197,"children":11746},[11747],{"id":8922,"depth":233,"text":8923},{"id":9053,"depth":32,"text":9054,"children":11749},[11750,11751],{"id":9224,"depth":233,"text":2882},{"id":9431,"depth":233,"text":9432},{"id":9883,"depth":32,"text":9884},{"id":10173,"depth":32,"text":10174},{"id":10596,"depth":32,"text":10597},{"id":11038,"depth":32,"text":11039},"md",{},[11759,11760,11761],"engineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime","engineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime\u002Fdefinitions","engineering\u002Fsystem-design\u002Fagentic-platform\u002Fplanes\u002Fidempotency",{"title":373,"description":374},"engineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime\u002Fexecution",[149,217,197,142,255,377,64],"0eVDGxv1oy0vcGU4BQYkmikvsPPOuVzXKfssxrSvhp0",1788650193258]