[{"data":1,"prerenderedAt":6900},["ShallowReactive",2],{"docs-nav":3,"docs-article-engineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime\u002Fdefinitions":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":366,"body":799,"customComponent":480,"description":367,"extension":6890,"group":139,"lastUpdated":201,"meta":6891,"navigation":2121,"order":368,"path":365,"related":6892,"section":181,"seo":6896,"stem":6897,"tags":6898,"__hash__":6899},"docs\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime\u002Fdefinitions.md",{"type":800,"value":801,"toc":6837},"minimark",[802,806,810,819,826,831,842,898,904,919,942,967,980,986,991,1004,1017,1041,1047,1057,1072,1076,1083,1126,1141,1145,1148,1216,1219,1223,1247,1280,1284,1287,1290,1297,1300,1303,1306,1327,1330,1340,1367,1373,1376,1380,1383,1393,1399,1402,1429,1445,1455,1458,1464,1470,1476,1485,1491,1502,1514,1525,1529,1632,1636,1643,1655,1658,1697,1725,1750,1762,1789,1810,1814,1824,1827,1867,1874,1880,1892,1896,1902,1912,1921,1938,1944,1947,1956,1959,1963,1969,1975,1986,2203,2224,2250,2259,2291,2312,2321,2346,2364,2378,2399,2408,2425,2428,2432,2445,2451,2474,2484,2502,2506,2513,2519,2552,2584,2588,2591,2594,2612,2615,2624,2629,2639,2669,2681,2685,2695,2702,2705,2708,2722,2729,2737,2743,2803,2823,2840,2843,2847,2853,2856,2862,2867,2873,2881,2890,2899,2917,2925,2959,2965,2996,3014,3049,3059,3063,3080,3098,3106,3109,3115,3139,3157,3175,3188,3200,3219,3223,3229,3236,3245,3261,3283,3286,3298,3306,3317,3321,3327,3341,3356,3359,3366,3370,3376,3383,3388,3430,3440,3478,3484,3490,3500,3535,3538,3547,3561,3569,3629,3636,3645,3683,3695,3715,3721,3739,3756,3762,3768,3858,3865,3950,3963,3970,3976,3989,3998,4010,4016,4026,4029,4032,4075,4078,4089,4105,4111,4118,4125,4131,4137,4144,4158,4168,4186,4189,4195,4198,4203,4209,4222,4243,4261,4270,4274,4285,4291,4297,4314,4320,4414,4417,4423,4429,4441,4444,4449,4455,4458,4490,4496,4500,4512,4517,4523,4529,4540,4556,4561,4567,4576,4600,4606,4621,4624,4702,4706,4728,4740,4760,4773,4801,4816,4825,4829,4843,4860,4864,4871,4878,4884,4890,4911,4932,4938,4941,4947,4960,4964,4986,4998,5002,5011,5017,5023,5034,5053,5085,5094,5153,5169,5189,5192,5211,5214,5254,5258,5273,5287,5293,5308,5312,5315,5396,5405,5427,5442,5454,5458,5461,5467,5472,5513,5516,5541,5546,5564,5578,5616,5628,5631,5634,5637,5697,5728,5731,5740,5751,5760,5768,5785,5793,5797,5803,5809,5812,5816,5832,5838,5851,5857,5863,5869,5884,5894,5903,5919,5922,5926,5932,5935,5969,5973,5991,5997,6003,6022,6032,6041,6044,6047,6051,6407,6411,6833],[803,804,366],"h1",{"id":805},"runtime-definitions",[807,808,809],"p",{},"A definition is the configuration the runtime may execute. A draft is its editable copy.",[811,812,813],"blockquote",{},[807,814,815],{},[816,817,818],"strong",{},"Draft -> validate -> publish -> the current runnable definition",[807,820,821,822,825],{},"Publishing is a safety boundary. It is ",[816,823,824],{},"not"," a revision system and not a history system.",[827,828,830],"h2",{"id":829},"one-lifecycle","One lifecycle",[832,833,839],"pre",{"className":834,"code":836,"language":837,"meta":838},[835],"language-text","agent.definitions\n  id\n  organization_id\n  kind: agent | workflow | skill\n  name\n  origin: platform | custom\n  source_definition_id\n  state: draft | active | disabled\n  draft_config\n  published_config\n  published_at\n  updated_at\n","text","",[840,841,836],"code",{"__ignoreMap":838},[843,844,845,858],"table",{},[846,847,848],"thead",{},[849,850,851,855],"tr",{},[852,853,854],"th",{},"State",[852,856,857],{},"Meaning",[859,860,861,872,886],"tbody",{},[849,862,863,869],{},[864,865,866],"td",{},[840,867,868],{},"draft",[864,870,871],{},"Never published. It cannot run.",[849,873,874,879],{},[864,875,876],{},[840,877,878],{},"active",[864,880,881,882,885],{},"It has a ",[840,883,884],{},"published_config",". New Runs use it.",[849,887,888,893],{},[864,889,890],{},[840,891,892],{},"disabled",[864,894,881,895,897],{},[840,896,884],{}," and it is switched off. New Run trees are refused.",[832,899,902],{"className":900,"code":901,"language":837,"meta":838},[835],"draft     --publish-->  active\nactive    --disable-->  disabled\ndisabled  --enable-->   active\ndisabled  --publish-->  active     a publish is a deliberate act, so it also enables\ndraft     --delete-->   gone       a draft only\n",[840,903,901],{"__ignoreMap":838},[807,905,906,915,916,918],{},[816,907,908,910,911,914],{},[840,909,892],{}," is reversible, and ",[840,912,913],{},"enable"," revalidates."," A definition this one references may have been disabled while this one was off, so ",[840,917,913],{}," runs the same validation a publish runs. Without that, an enable can return a broken definition to service.",[807,920,921,922,930,931,933,934,937,938,941],{},"⚠️ ",[816,923,924,926,927,929],{},[840,925,913],{}," validates the stored ",[840,928,884],{},", derived scope keys included."," The stale scope pair is the break it most needs to find, because the publish that caused it skipped this definition: referrer revalidation reads ",[840,932,878],{}," referrers only, and this one was switched off. The comparison is between a ",[816,935,936],{},"stored"," pair and a recomputed one, so validating a config with the pair stripped skips the check in silence and returns the definition to service with a ",[840,939,940],{},"required_scopes"," policy admission then trusts.",[807,943,944,947,948,951,952,955,956,959,960,963,964,966],{},[816,945,946],{},"A draft is the only row that deletes."," A published row is disabled, never deleted: ",[840,949,950],{},"runs_definition_fk"," takes ",[840,953,954],{},"ON DELETE RESTRICT",", and a Run's audit trail must not be deletable from under it. ",[840,957,958],{},"delete_draft"," therefore refuses any row whose ",[840,961,962],{},"state"," is not ",[840,965,868],{},". Conversational authoring creates a draft per attempt, so without this the abandoned ones accumulate with nothing to clear them.",[807,968,969,970,972,973,976,977,979],{},"A definition stays ",[840,971,878],{}," while an admin edits its draft. The UI shows unpublished work by comparing ",[840,974,975],{},"draft_config"," with ",[840,978,884],{},". There is no fourth state for it.",[807,981,982,983,985],{},"New Runs use ",[840,984,884],{},". Runs already in flight keep their frozen snapshot.",[987,988,990],"h3",{"id":989},"disable-stops-a-new-tree-and-not-a-tree-already-running","Disable stops a new tree, and not a tree already running",[807,992,993,999,1000,1003],{},[816,994,995,998],{},[840,996,997],{},"disable"," refuses a new Run tree. It does not refuse a child of a tree that was admitted while the definition was still active."," A workflow can wait three days for a person and then start its next agent node; refusing that child would fail a Run an admin never asked to stop, and ",[840,1001,1002],{},"POST \u002Fruns\u002F{id}\u002Fcancel"," is the control that stops one.",[807,1005,1006,1009,1010,1012,1013,1016],{},[840,1007,1008],{},"assert_run_shape()"," already holds this: it raises for a ",[840,1011,892],{}," definition only when ",[840,1014,1015],{},"parent_run_id IS NULL",".",[807,1018,1019,1025,1026,1029,1030,1033,1034,1037,1038,1040],{},[816,1020,1021,1024],{},[840,1022,1023],{},"RunManager"," must hold the same rule, and today it does not."," ",[840,1027,1028],{},"definition_not_published"," is documented for a draft ",[816,1031,1032],{},"or disabled"," definition on every start, and a workflow node starts a child through the same ",[840,1035,1036],{},"RunManager.start()",". ",[840,1039,1023],{}," runs before the trigger, so its check wins and the trigger's carve-out never fires. The result is a workflow that fails mid Run on a disable that was meant to stop new work only.",[832,1042,1045],{"className":1043,"code":1044,"language":837,"meta":838},[835],"parent_run_id IS NULL      draft -> refuse    disabled -> refuse\nparent_run_id IS NOT NULL  draft -> refuse    disabled -> allow\n",[840,1046,1044],{"__ignoreMap":838},[807,1048,1049,1050,1052,1053,1016],{},"A draft is refused on both sides. A draft has no ",[840,1051,884],{},", so there is nothing to run whatever started it. This is the same asymmetry the schema encodes, and the two must agree. See ",[1054,1055,1056],"a",{"href":372},"runtime execution",[807,1058,1059,1065,1066,1068,1069,1071],{},[816,1060,1061,1062,1064],{},"A definition cannot return to ",[840,1063,868],{}," once a Run references it."," A draft holds no ",[840,1067,884],{},", so a draft with a live Run is unmanageable: ",[840,1070,954],{}," refuses the delete, and the draft rule refuses every new Run. A trigger refuses the reversion instead. It locks the definition row while it reads, because this rule and the draft rule guard one invariant from opposite sides. Without the lock neither sees the other's uncommitted work, and both commit.",[827,1073,1075],{"id":1074},"product-capability-bindings","Product capability bindings",[807,1077,1078,1079,1082],{},"Phase 7 adds optional product metadata to tenant definitions. It does not add a fourth definition kind.\nThe ",[1054,1080,1081],{"href":315},"capability contract"," defines the five IDs and their public schemas.",[807,1084,1085,1086,1089,1090,1093,1094,1097,1098,1101,1102,1105,1106,1105,1109,1105,1112,1105,1115,1105,1118,1121,1122,1125],{},"A binding belongs to one definition row. Its executor UUID is that row's ",[840,1087,1088],{},"id",", never a second editable pointer.\nPersist nullable ",[840,1091,1092],{},"capability_binding"," metadata and a separate ",[840,1095,1096],{},"capability_active"," Boolean on ",[840,1099,1100],{},"agent.definitions",".\nThe closed metadata object holds ",[840,1103,1104],{},"capability_id",", ",[840,1107,1108],{},"contract_version",[840,1110,1111],{},"name",[840,1113,1114],{},"description",[840,1116,1117],{},"input_schema",[840,1119,1120],{},"output_schema"," and ",[840,1123,1124],{},"platform_managed",".\nPersist the derived required scopes with the published workflow configuration. The registry reads these values from one revision.\nCustom definitions without product metadata keep their existing lifecycle and Front Door path.\nPlatform templates have no active tenant binding and cannot execute for an organization.",[807,1127,1128,1129,1132,1133,1105,1135,1137,1138,1140],{},"Enforce one active ",[840,1130,1131],{},"(organization_id, capability_id)"," binding in storage, including concurrent installation and replacement.\nA binding's active state is separate from the definition's ",[840,1134,868],{},[840,1136,878],{}," or ",[840,1139,892],{}," state.\nThis permits an older executor to remain valid for published parent references while a newer executor serves direct product starts.\nAn active binding must name an active, published Workflow in the same organization.\nA draft schema, missing executor, disabled executor, stale scope set or duplicate binding is unavailable.\nDo not infer a binding from a display name, provider ID or mutable draft configuration.",[987,1142,1144],{"id":1143},"binding-storage-and-lifecycle","Binding storage and lifecycle",[807,1146,1147],{},"ENG-2287 owns these storage and Python persistence rules:",[1149,1150,1151,1163,1166,1169,1172,1179,1182,1185,1188,1194,1197,1200,1203,1213],"ul",{},[1152,1153,1154,1155,1158,1159,1162],"li",{},"An unbound row has SQL ",[840,1156,1157],{},"NULL"," metadata and ",[840,1160,1161],{},"capability_active = false",". Migration does not infer or install bindings.",[1152,1164,1165],{},"Metadata accepts only the five stable IDs, a positive integer version, bounded product text and valid closed root object schemas.",[1152,1167,1168],{},"Metadata is at most 65,536 UTF-8 bytes. Each schema is at most 32,768 bytes. Schema references must be local.",[1152,1170,1171],{},"Attach metadata only to an active, published tenant Workflow. Validate the stored graph and its derived scopes first.",[1152,1173,1174,1175,1178],{},"Attach with the tenant, unbound state and exact ",[840,1176,1177],{},"updated_at"," token. A stale token writes nothing.",[1152,1180,1181],{},"The first attachment cannot change the published configuration or dependency references in the same write.",[1152,1183,1184],{},"Once attached, metadata, definition identity, ownership, published configuration and dependency references cannot change in place.",[1152,1186,1187],{},"Draft edits remain separate. Generic republish refuses a bound definition. A normal fork creates an unbound draft.",[1152,1189,1190,1191,1193],{},"Activation uses the tenant and exact ",[840,1192,1177],{}," token. A partial unique index permits one active binding per tenant and stable ID.",[1152,1195,1196],{},"A concurrent activation conflict returns a bounded conflict result. The caller reloads before retrying.",[1152,1198,1199],{},"Disable clears activation in the same database write. Enable validates the executor but leaves its binding inactive.",[1152,1201,1202],{},"Deactivation keeps the executor active for published parents. Bound rows cannot be deleted, including inactive versions.",[1152,1204,1205,1206,1208,1209,1212],{},"Required scopes remain in ",[840,1207,884],{},". Python reads them with ",[840,1210,1211],{},"run.start","; missing or malformed scope data fails closed.",[1152,1214,1215],{},"Authenticated users can read binding metadata in their tenant through existing RLS. Only the service role can write it.",[807,1217,1218],{},"Binding immutability protects the owning definition row. It does not freeze unbound descendants.\nENG-2303 owns managed descendant revision protection. ENG-2298 owns capability metadata in the frozen Run tree.\nTheir exit tests must cover a child instruction or Skill change that preserves the same scopes.",[987,1220,1222],{"id":1221},"registry-reads","Registry reads",[807,1224,1225,1226,1229,1230,1233,1234,1236,1237,1121,1240,1242,1243,1246],{},"ENG-2275 owns these read rules. ",[840,1227,1228],{},"runtime\u002Fdefinitions\u002Fregistry.py"," holds ",[840,1231,1232],{},"CapabilityRegistry",", beside the repository it reads.\nIt sits in ",[840,1235,149],{},", so ",[840,1238,1239],{},"entry_control",[840,1241,253],{}," may both read it and ",[840,1244,1245],{},"services"," may not.",[1149,1248,1249,1255,1258,1261,1271,1274,1277],{},[1152,1250,1251,1254],{},[840,1252,1253],{},"DefinitionRepository.list_capability_bindings"," reads the bound rows of one organization. The active rows come first.",[1152,1256,1257],{},"It reads the bound rows and not the active ones alone, so a disabled executor does not read as a missing installation.",[1152,1259,1260],{},"The projection reads the two scope arrays by JSON path. A catalogue read never carries a published workflow graph.",[1152,1262,1263,1264,1037,1267,1270],{},"One row maps to one ",[840,1265,1266],{},"CapabilityRow",[840,1268,1269],{},"row_to_definition"," raises on an invalid binding, and one such row must not fail the whole read.",[1152,1272,1273],{},"The registry iterates the five stable IDs and never the rows. A stored ID outside that vocabulary names no capability.",[1152,1275,1276],{},"Two active bindings refuse. The registry picks no executor by row order.",[1152,1278,1279],{},"The registry holds no cache and no page. There are five IDs, and a disable must take effect at once.",[987,1281,1283],{"id":1282},"initial-install-and-reconciliation","Initial install and reconciliation",[807,1285,1286],{},"ENG-2277 owns the initial installer. One code manifest describes every managed definition and the five V1 bindings.\nEach manifest node has a stable key, revision, kind, name and configuration factory.\nEach binding has the exact product text and schemas for contract version 1.",[807,1288,1289],{},"The installer derives each definition UUID with UUIDv5 from the organization, stable key and revision.\nEach released node revision is immutable. A change to a node kind, name or configuration needs a new\nrevision of that node, and ENG-2303 owns the switch that activates it. The manifest keeps every released\nrevision of every node, oldest first, so a superseded row stays identifiable as a managed row.",[807,1291,1292,1293,1296],{},"Two tests hold the table honest. A released ",[840,1294,1295],{},"(key, revision)"," pair freezes the node kind, name and\nconfiguration. A node revision is never lower than the revision of a node it references, so a child bump\npropagates up to the bound root. That propagation is not a style rule: the binding trigger freezes the\npublished configuration of a bound row, so a parent cannot re-point at a new child in place.",[807,1298,1299],{},"Golden tests freeze the full manifest and every derived UUID.\nThe database primary key makes concurrent creation idempotent.\nThe manifest and deterministic IDs record managed definitions without another table.\nNames remain display text and never identify managed rows.",[807,1301,1302],{},"The installer uses a platform-only definition writer.\nIt takes an organization ID and cannot be reached from tenant authoring surfaces.\nIt confirms that the organization exists before its first write.\nIt reuses the existing definition validator and publish rules.\nIt never impersonates an organization user or trigger.",[807,1304,1305],{},"Reconciliation processes manifest nodes in dependency order:",[1307,1308,1309,1312,1315,1318,1321,1324],"ol",{},[1152,1310,1311],{},"Read the deterministic row before a write.",[1152,1313,1314],{},"Create a missing row as a draft.",[1152,1316,1317],{},"Publish an exact draft through the normal validator.",[1152,1319,1320],{},"Reuse an exact published row.",[1152,1322,1323],{},"Attach the exact binding to the root workflow.",[1152,1325,1326],{},"Activate the root only after the complete tree validates.",[807,1328,1329],{},"An existing row must match its manifest kind, name and resolved configuration.\nFor a published row, its stored references must also match the references derived from that configuration.\nA binding must exactly match the manifest version, text, schemas and managed marker.\nA mismatch is a conflict. The installer never changes the conflicting row.\nIt retains exact managed nodes that it created before it found the conflict.\nThis rule also protects a managed draft that an organization admin edited.",[807,1331,1332,1333,1335,1336,1339],{},"The installer never locates a row by name.\nIt reaches a row by its deterministic UUID and compares the whole binding, so it never changes a tenant binding.\n",[840,1334,1124],{}," is a field of that compared binding; no code reads it on its own.\nAny tenant binding for the same capability produces ",[840,1337,1338],{},"conflict",", whether it is active or inactive.\nThe installer then attaches or activates no managed binding for that capability.\nIt never replaces an active binding during initial installation.\nIt may activate an exact, inactive managed binding when no active binding exists.\nIt reports a disabled executor and does not enable it.\nIt reports invalid or duplicate bindings and does not select one by row order.",[807,1341,1342,1343,1105,1346,1105,1349,1105,1352,1105,1354,1105,1356,1137,1359,1362,1363,1366],{},"Each capability returns one bounded result: ",[840,1344,1345],{},"current",[840,1347,1348],{},"installed",[840,1350,1351],{},"missing",[840,1353,892],{},[840,1355,1338],{},[840,1357,1358],{},"invalid",[840,1360,1361],{},"blocked",".\nInfrastructure faults raise for retry. Stable tenant or data conflicts return a report and do not retry without a state change.\nAn organization that no longer exists returns the run-level result ",[840,1364,1365],{},"absent_organization"," and no capability results.",[807,1368,1369,1370,1372],{},"One durable reconciliation function owns an organization run.\nIts event trigger handles new organizations and operator backfills.\nA scheduled sweep pages all organization IDs and sends one reconcile event for each organization.\nEach sweep run handles one bounded page.\nA cursor continuation event starts the next page.\nThus, the step count for one run stays bounded, regardless of the organization count.\nComplete installations return ",[840,1371,1345],{}," without a write.\nThe database rules provide idempotency after Inngest's event deduplication window ends.\nThe two organization creation paths send the event after their organization write completes.\nA send failure does not roll back the organization. The scheduled sweep repairs it.",[807,1374,1375],{},"The durable function checkpoints each capability installation separately.\nA retry reloads every deterministic row and continues from stored state.\nThe operator command calls the same reconciler and prints the same structured report.",[987,1377,1379],{"id":1378},"upgrade-and-rollback","Upgrade and rollback",[807,1381,1382],{},"ENG-2303 owns explicit upgrades and rollback.\nBoth use the existing definition publish validation before they activate a binding.",[807,1384,1385,1388,1389,1392],{},[816,1386,1387],{},"An upgrade and a rollback are operator actions. The automatic paths never replace an active binding.","\nThe organization event and the scheduled sweep install and repair only. They report the new status\n",[840,1390,1391],{},"outdated"," when a prior released revision serves the capability, and they write nothing for it. This is\nwhat makes a rollback survive: the next sweep does not roll it forward.",[807,1394,1395,1398],{},[816,1396,1397],{},"The contract gate reads the basis: the contract the tenant moves away from."," That is the active row, or,\nwith nothing active, the newest released row that is not the manifest root. A switch cannot move away from\nits own target, so the root is never the basis. A gate that read the newest row would compare the target\nwith itself whenever an earlier release had already bound the root, and a downgrade would land.",[807,1400,1401],{},"An upgrade names the capabilities that may switch. For each of them:",[1307,1403,1404,1414,1417,1420,1423],{},[1152,1405,1406,1407,1410,1411,1413],{},"Compare the target binding with the basis binding, ",[816,1408,1409],{},"before any write",". An attach is irreversible,\nbecause the trigger freezes ",[840,1412,1092],{}," and refuses a delete on a bound row. A binding written\non a refused switch could never be taken back.",[1152,1415,1416],{},"Create or locate the inactive target revision and its tenant child definitions.",[1152,1418,1419],{},"Validate schemas, scopes, tenant references and the full dependency tree.",[1152,1421,1422],{},"Publish the target tree before activation. A failed publish leaves the old binding active.",[1152,1424,1425,1426,1016],{},"Switch the binding with two writes. The result is ",[840,1427,1428],{},"upgraded",[807,1430,1431,1025,1434,1437,1438,1440,1441,1444],{},[816,1432,1433],{},"The switch is two writes, not one transaction.",[840,1435,1436],{},"ac-python-api"," reaches Postgres through PostgREST,\nwhich cannot span two statements, and the design refuses a business-logic RPC. Each write carries the\nrow's expected ",[840,1439,1177],{},", so a concurrent writer loses its write instead of overwriting. The partial\nunique index ",[840,1442,1443],{},"uq_definitions_active_capability"," permits one active binding per organization and\ncapability, so the old row releases the slot before the new row claims it.",[807,1446,1447,1450,1451,1454],{},[816,1448,1449],{},"A crash between the two writes leaves the capability with no active binding."," The registry then answers\n",[840,1452,1453],{},"inactive_executor",", which is truthful, and no Run is harmed. The next reconcile pass finds the target\nbound and inactive with nothing active, and it activates the target. The half switch repairs itself, and\nthe failure window is one HTTP round trip.",[807,1456,1457],{},"Concurrent reconcilers either observe the desired active revision or retry from the current one.\nThey never disable the old binding before a replacement passes validation.",[807,1459,1460,1463],{},[816,1461,1462],{},"Deactivation runs no validation, so the release always succeeds and all the risk sits on the second\nwrite."," When the target refuses activation, the switch gives the slot back, so a healthy prior revision\nkeeps serving. Re-activation runs the full executor validation, so it can refuse too. The report then\nnames the empty slot: a capability never goes dark in silence.",[807,1465,1466,1469],{},[816,1467,1468],{},"A disabled released row with nothing active refuses every install."," A disable clears activation in the\nsame database write, and the row that was serving is not recorded, so the reconciler cannot tell an\noperator's switch-off from a retired revision. It refuses, and the reason names the way out: enable the\nrow, or roll back to a released revision.",[807,1471,1472,1475],{},[816,1473,1474],{},"A rollback runs before every manifest check",", and it validates its own target. It is the way out of a\ndisabled revision, and of an incumbent whose stored binding a later model refuses. Those are the two\nstates that make a rollback necessary, so a manifest check must not block it. It still refuses a disabled\ntarget, so it cannot restore what an operator switched off.",[807,1477,1478,1479,1481,1482,1016],{},"A rollback names one released revision of one capability. It reads that row and runs the same guarded\nswitch. It creates nothing, publishes nothing and deletes nothing, so every Run and every audit row stays.\nThe target must be a released revision, must already hold a binding, and must be ",[840,1480,878],{},". A disabled\nprevious executor is not a valid rollback target, and a disabled capability refuses a rollback outright: a\nrollback must not restore what an operator switched off. The result is ",[840,1483,1484],{},"rolled_back",[807,1486,1487,1490],{},[816,1488,1489],{},"A rollback moves direct starts only."," A published parent names its child by UUID and its Run reads a\nfrozen tree, so a parent keeps the child revision it published. Roll the parents back in the same command\nwhen the child revision is the fault.",[807,1492,1493,1498,1499,1501],{},[816,1494,1495,1496],{},"A rollback to a revision that is not the manifest revision gates its dependants as ",[840,1497,1391],{},", on the\npass that writes it and on every pass after. The report still reads ",[840,1500,1484],{}," for the audit, but a\ndependant reads the state it will find: the current root does not exist, so it cannot publish. A rollback\nto the manifest revision serves its dependants normally.",[807,1503,1504,1507,1508,1510,1511,1016],{},[816,1505,1506],{},"An outdated capability blocks its dependants."," It writes nothing, so its current-revision root does not\nexist, and a parent that names that root cannot publish. A dependant therefore reports ",[840,1509,1361],{}," until the\nchild upgrades. Upgrade in dependency order, or pass ",[840,1512,1513],{},"--upgrade-all",[807,1515,1516,1517,1520,1521,1524],{},"The contract version is a positive integer, independent of the executor revision.\nAn upgrade never lowers it. A rollback lowers it on purpose, and its report names the move.\n",[816,1518,1519],{},"One contract version holds exactly one pair of schemas."," No code can prove that two schemas carry the\nsame meaning, so the switch compares them for equality rather than for compatibility. An author who\nchanges an input or output schema raises the contract version. Product text stays free to change.\nAn incompatible change needs a new contract version and an explicit migration. Old-version clients receive ",[840,1522,1523],{},"contract_version_conflict",".\nThere is no side-by-side serving: one active binding per capability means one live contract version, and a\nstale client fails loudly at start rather than reaching a translated contract.\nA product update uses a new definition row and passes the same compatibility checks.\nThe generic definition editor cannot republish a bound row.\nDisable makes new starts unavailable. Delete is refused while a binding, published parent or retained Run still references the definition.",[987,1526,1528],{"id":1527},"reconciliation-results","Reconciliation results",[843,1530,1531,1540],{},[846,1532,1533],{},[849,1534,1535,1538],{},[852,1536,1537],{},"Result",[852,1539,857],{},[859,1541,1542,1551,1560,1569,1578,1587,1596,1605,1614,1623],{},[849,1543,1544,1548],{},[864,1545,1546],{},[840,1547,1345],{},[864,1549,1550],{},"The stored state matches the manifest. No write ran.",[849,1552,1553,1557],{},[864,1554,1555],{},[840,1556,1348],{},[864,1558,1559],{},"A write converged this capability on the manifest revision.",[849,1561,1562,1566],{},[864,1563,1564],{},[840,1565,1428],{},[864,1567,1568],{},"An operator upgrade replaced an active binding with the manifest revision.",[849,1570,1571,1575],{},[864,1572,1573],{},[840,1574,1484],{},[864,1576,1577],{},"An operator rollback activated a released prior revision.",[849,1579,1580,1584],{},[864,1581,1582],{},[840,1583,1391],{},[864,1585,1586],{},"A prior released revision is active. Only an operator upgrade replaces it.",[849,1588,1589,1593],{},[864,1590,1591],{},[840,1592,1351],{},[864,1594,1595],{},"Report mode found work to do.",[849,1597,1598,1602],{},[864,1599,1600],{},[840,1601,892],{},[864,1603,1604],{},"A released executor is disabled. An install would restore a capability an operator switched off.",[849,1606,1607,1611],{},[864,1608,1609],{},[840,1610,1338],{},[864,1612,1613],{},"A binding outside the released revisions exists, or managed state drifted. Tenant work is preserved.",[849,1615,1616,1620],{},[864,1617,1618],{},[840,1619,1358],{},[864,1621,1622],{},"Stored data or a contract fails validation. It is not repaired.",[849,1624,1625,1629],{},[864,1626,1627],{},[840,1628,1361],{},[864,1630,1631],{},"A dependency is not at its released revision.",[987,1633,1635],{"id":1634},"composition-and-frozen-runs","Composition and frozen Runs",[807,1637,1638,1639,1642],{},"Resolve stable child IDs when publishing a product workflow. Store the resolved UUIDs in ordinary ",[840,1640,1641],{},"subworkflow"," nodes.\nStore each child's capability ID and contract version with the published composition metadata.\nDo not resolve a new executor from the registry at each workflow step.",[807,1644,1645,1646,1649,1650,1654],{},"An upgrade does not retarget an already published parent.\nFor a bound managed parent, publish a replacement parent definition and switch its binding when its child version must change.\nUnbound parents retain the normal republish lifecycle.\nKeep old executors runnable for those parents until the references are migrated. Do not delete their audit history.\nA new root snapshot freezes the entire published dependency tree and its capability metadata.\nAn in-flight Run and its later child starts use that snapshot, even after a binding switch.\nChildren keep ",[840,1647,1648],{},"\u003Cparent_run_id>:\u003Cnode_id>"," start keys and inherit the root budget and narrowed grants.\nRun reads, spans and logs retain stable capability ID and contract version through API, CLI, chat and child starts.\nThe ",[1054,1651,1653],{"href":1652},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fplanes\u002Fobservability-and-operations#capability-attribution","attribution contract"," defines snapshot versions, legacy Runs and metric dimensions.",[827,1656,1657],{"id":313},"Templates",[843,1659,1660,1673],{},[846,1661,1662],{},[849,1663,1664,1667,1670],{},[852,1665,1666],{},"Definition",[852,1668,1669],{},"Owner",[852,1671,1672],{},"Editable",[859,1674,1675,1686],{},[849,1676,1677,1680,1683],{},[864,1678,1679],{},"Platform template",[864,1681,1682],{},"platform",[864,1684,1685],{},"no",[849,1687,1688,1691,1694],{},[864,1689,1690],{},"Organization custom definition",[864,1692,1693],{},"organization",[864,1695,1696],{},"admins",[807,1698,1699,1700,1703,1704,1706,1707,1711,1712,1105,1715,1105,1718,1720,1721,1724],{},"Customization forks the platform row. A later platform change does not silently change the fork. ",[840,1701,1702],{},"fork"," copies the source's ",[840,1705,884],{}," into the new row's ",[816,1708,1709],{},[840,1710,975],{},", with ",[840,1713,1714],{},"origin: custom",[840,1716,1717],{},"state: draft",[840,1719,884],{}," null and ",[840,1722,1723],{},"source_definition_id"," set to the source.",[807,1726,1727,1025,1736,1739,1740,1743,1744,1747,1748,1016],{},[816,1728,1729,1730,1732,1733,1735],{},"The copy lands in ",[840,1731,975],{},", and writing it to ",[840,1734,884],{}," fails the insert.",[840,1737,1738],{},"definitions_published_shape"," is the equivalence ",[840,1741,1742],{},"(state = 'draft') = (published_config IS NULL)",", so a draft carrying a published config raises ",[840,1745,1746],{},"23514",". A fork is unpublished work by definition: the admin edits it and publishes it, and that publish is what fills ",[840,1749,884],{},[807,1751,1752,1757,1758,1761],{},[816,1753,1754,1756],{},[840,1755,1723],{}," is the one reference that carries no organization",", because a platform row belongs to none and a paired key would refuse it. The service is therefore the only guard: a fork source must be a platform row, or a row in the caller's own organization. A database constraint cannot express that, so the check lives in ",[840,1759,1760],{},"DefinitionService.fork()"," and it is a contract test.",[807,1763,1764,1025,1769,1771,1772,1774,1775,1777,1778,1781,1782,1784,1785,1788],{},[816,1765,1766,1767,1016],{},"A fork source must be ",[840,1768,878],{},[840,1770,1702],{}," copies ",[840,1773,884],{},", and a draft holds none. ",[840,1776,975],{}," is ",[840,1779,1780],{},"NOT NULL",", so a fork of a draft would write ",[840,1783,1157],{}," into it and fail the insert. The service refuses it first and answers ",[840,1786,1787],{},"not_found",", because a draft of another organization must answer exactly as a missing row does and the caller's own draft needs no fork: it is already editable.",[807,1790,1791,1792,1794,1795,1798,1799,1802,1803,1806,1807,1809],{},"A ",[840,1793,892],{}," source is refused by the same method and the same answer. A disabled definition is configuration an admin withdrew, so a fork of one copies work that was switched off on purpose. The RLS policy says the same thing for a platform row: ",[840,1796,1797],{},"authenticated"," reads a platform template only while ",[840,1800,1801],{},"state = 'active'",", so a disabled one is a row no surface can list. ",[840,1804,1805],{},"get_fork_source"," therefore filters on ",[840,1808,1801],{},", and the service needs no second check.",[987,1811,1813],{"id":1812},"a-definition-references-its-own-organization-only","A definition references its own organization only",[807,1815,1816,1819,1820,1823],{},[816,1817,1818],{},"A custom definition may reference only definitions of its own organization. A platform template is forked before it is referenced."," So ",[840,1821,1822],{},"referenced_ids"," never holds a platform id, and the reverse lookup of a platform row is empty by construction.",[807,1825,1826],{},"This is the rule that keeps three others answerable.",[843,1828,1829,1839],{},[846,1830,1831],{},[849,1832,1833,1836],{},[852,1834,1835],{},"Without it",[852,1837,1838],{},"With it",[859,1840,1841,1849,1857],{},[849,1842,1843,1846],{},[864,1844,1845],{},"the disable guard of a platform template must ask every tenant, and the reverse lookup is org scoped on purpose",[864,1847,1848],{},"the guard is one org scoped read, and a platform row has no referrers to find",[849,1850,1851,1854],{},[864,1852,1853],{},"the referrer cap counts per organization, so a platform row carries 50 referrers per tenant and no global bound",[864,1855,1856],{},"one cap, one meaning",[849,1858,1859,1864],{},[864,1860,1861,1863],{},[840,1862,1822],{}," mixes tenants, so one indexed lookup answers a question the caller may not see the answer to",[864,1865,1866],{},"every id in the column belongs to the row's own organization",[807,1868,1869,1870,1873],{},"It costs the one thing that reads like a loss: a platform skill fix does not reach an organization that forked it. That is the same trade the fork already makes for every other field, and the section above states it — a later platform change does not silently change the fork. ",[816,1871,1872],{},"The shared-fix property below is an intra-organization one",": publishing a skill reaches every agent of that organization that references it, and reaches no other tenant.",[807,1875,1876,1877,1879],{},"A platform template is therefore reachable in exactly two ways: fork it, or run it after forking it. ",[840,1878,950],{}," already pairs a run with a definition of its own organization, so the same rule was already true of execution. This extends it to references, and the two now agree.",[807,1881,1882,1885,1886,1888,1889,1891],{},[816,1883,1884],{},"So a fork of a platform template is a deep fork."," A shallow copy of the source ",[840,1887,884],{}," carries its ids, so a platform workflow that names a platform agent would fork into a draft naming two rows of no organization, and this rule would refuse the publish. The admin could not repair it either: the ids name platform rows their organization does not own, so there is nothing to point them at. The alternatives are worse -- a platform template of one row forever, or a reference rule with a hole in it -- so ",[840,1890,1702],{}," copies the reachable set.",[987,1893,1895],{"id":1894},"the-deep-fork","The deep fork",[832,1897,1900],{"className":1898,"code":1899,"language":837,"meta":838},[835],"fork(platform workflow W)\n  W  -> subworkflow node -> platform workflow V\n     -> agent node       -> platform agent A -> skill S\n\n  copies V, A and S into this organization, then W,\n  and rewrites every id in every copy\n",[840,1901,1899],{"__ignoreMap":838},[807,1903,1904,1907,1908,1911],{},[816,1905,1906],{},"It crosses the organization boundary, and it stops there."," A reference is forked when its target belongs to ",[816,1909,1910],{},"no organization",", and it is left alone when the target is already this organization's. Under fork-first those are the only two cases a valid graph can hold: a platform template references platform rows only, and a custom definition references its own organization's rows only. So a fork of a platform template copies the whole set, and a fork of the caller's own definition copies one row and shares the references it already had. Deep-copying the second case would mint duplicates of definitions the admin can already point at.",[807,1913,1914,1917,1918,1920],{},[816,1915,1916],{},"Every deep fork mints a fresh set."," Fork the same template twice and the organization holds two independent copies of everything below it. Reusing an earlier fork by ",[840,1919,1723],{}," would put one row under two parents, so an edit made for one would change the other -- which is the coupling the fork exists to remove.",[807,1922,1923,1931,1932,1935,1936,1016],{},[816,1924,1925,1926,1928,1929,1016],{},"The walk reads ",[840,1927,884],{},", not ",[840,1930,1822],{}," One pair of functions reads the references out of a config and writes new ones back into it, and ",[840,1933,1934],{},"publish"," already needs the first to compute the column. Reading the column here would be a second answer to \"what does this definition reference\", and the two disagree the first time a seed is wrong. It is also why the seeded column stays unread: nothing walks a platform row's ",[840,1937,1822],{},[807,1939,1940,1943],{},[816,1941,1942],{},"The writes go leaves first and the root last."," There is no transaction, so the order is what bounds the damage of a crash: a root written first would name ids that do not exist yet, and a failure would leave a draft nobody can publish and nobody can diagnose. Written last, the root exists only when everything below it does.",[807,1945,1946],{},"A failure after the first insert is compensated, best effort: the drafts this fork wrote are deleted. A draft cannot run and it deletes cleanly, so the worst case left behind is rows an admin can remove. The retry mints a fresh set, exactly as a first attempt does.",[807,1948,1949,1952,1953,1955],{},[816,1950,1951],{},"A reachable definition that does not resolve refuses the whole fork",", and the answer is ",[840,1954,1358],{}," naming the id. The source template is broken -- it names a row that was removed or disabled -- and copying a subset of it would give the admin a draft they cannot publish and no reason why.",[807,1957,1958],{},"The walk carries a read budget, as the graph walk does. It is a runaway backstop and never a design limit: the workflow depth cap already bounds the shape a valid template can take.",[827,1960,1962],{"id":1961},"core-code","Core code",[832,1964,1967],{"className":1965,"code":1966,"language":837,"meta":838},[835],"runtime\u002Fdefinitions\u002F\n  models.py         Definition, DefinitionKind, DefinitionRef, ValidationResult\n  repository.py     DefinitionRepository\n  service.py        DefinitionService\n  validators\u002F\n    base.py         DefinitionValidator entry point\n    agent.py\n    skill.py\n    workflow.py\n  snapshot.py       SnapshotBuilder\n  references.py     the referrer set and the reference cap\n  authority.py      who may write a definition\n  model_registry.py the provider and model pairs a publish accepts\n  errors.py         DefinitionError, the closed outcome set\n",[840,1968,1966],{"__ignoreMap":838},[832,1970,1973],{"className":1971,"code":1972,"language":837,"meta":838},[835],"API \u002F Builder chat\n        │\n        ▼\n DefinitionService\n   ┌────┴─────────────┐\n   ▼                  ▼\nDefinitionValidator  DefinitionRepository\n",[840,1974,1972],{"__ignoreMap":838},[807,1976,1977,1978,1981,1982,1985],{},"Conversational authoring enters through the same service. The ",[1054,1979,1980],{"href":306},"front door builder chat"," uses one platform agent whose tools call ",[840,1983,1984],{},"DefinitionService",". It adds no second lifecycle path.",[832,1987,1991],{"className":1988,"code":1989,"language":1990,"meta":838,"style":838},"language-python shiki shiki-themes github-dark","class DefinitionRepository(Protocol):\n    async def get(self, definition_id: UUID, organization_id: UUID) -> Definition | None: ...\n    async def get_fork_source(self, definition_id: UUID,\n                              organization_id: UUID) -> Definition | None: ...\n    async def list_page(self, organization_id: UUID, *, limit: int,\n                       kind: DefinitionKind | None = None,\n                       origin: DefinitionOrigin | None = None,\n                       state: DefinitionState | None = None,\n                       cursor: tuple[datetime, UUID] | None = None\n                       ) -> tuple[list[Definition], bool]: ...\n    async def get_many(self, definition_ids: list[UUID],\n                       organization_id: UUID) -> list[Definition]: ...\n    async def create(self, definition: Definition) -> Definition: ...\n    async def save_draft(self, definition_id: UUID, organization_id: UUID, config: dict,\n                         expected_updated_at: str) -> Definition | None: ...\n    async def references_to(self, definition_id: UUID,\n                            organization_id: UUID) -> list[DefinitionRef]: ...\n    async def publish(self, definition_id: UUID, organization_id: UUID, config: dict,\n                      referenced_ids: list[UUID],\n                      expected_updated_at: str) -> Definition | None: ...\n    async def set_state(self, definition_id: UUID, organization_id: UUID,\n                        state: DefinitionState) -> Definition: ...\n    async def delete_draft(self, definition_id: UUID, organization_id: UUID) -> None: ...\n\n\nclass DefinitionValidator(Protocol):\n    async def validate(self, definition: Definition, config: dict) -> ValidationResult: ...\n\n\nclass DefinitionService:\n    async def create_draft(self, kind, name, config, actor): ...\n    async def fork(self, source_id, actor): ...\n    async def update_draft(self, definition_id, patch, expected_updated_at, actor): ...\n    async def validate_draft(self, definition_id, actor): ...\n    async def publish(self, definition_id, expected_updated_at, actor): ...\n    async def disable(self, definition_id, actor): ...\n    async def enable(self, definition_id, actor): ...\n    async def delete_draft(self, definition_id, actor): ...\n","python",[840,1992,1993,2000,2005,2010,2015,2020,2025,2030,2035,2040,2045,2050,2055,2060,2065,2071,2077,2083,2089,2095,2100,2105,2110,2116,2123,2128,2134,2140,2145,2150,2155,2161,2167,2173,2179,2185,2191,2197],{"__ignoreMap":838},[1994,1995,1997],"span",{"class":1996,"line":22},"line",[1994,1998,1999],{},"class DefinitionRepository(Protocol):\n",[1994,2001,2002],{"class":1996,"line":32},[1994,2003,2004],{},"    async def get(self, definition_id: UUID, organization_id: UUID) -> Definition | None: ...\n",[1994,2006,2007],{"class":1996,"line":233},[1994,2008,2009],{},"    async def get_fork_source(self, definition_id: UUID,\n",[1994,2011,2012],{"class":1996,"line":244},[1994,2013,2014],{},"                              organization_id: UUID) -> Definition | None: ...\n",[1994,2016,2017],{"class":1996,"line":264},[1994,2018,2019],{},"    async def list_page(self, organization_id: UUID, *, limit: int,\n",[1994,2021,2022],{"class":1996,"line":222},[1994,2023,2024],{},"                       kind: DefinitionKind | None = None,\n",[1994,2026,2027],{"class":1996,"line":360},[1994,2028,2029],{},"                       origin: DefinitionOrigin | None = None,\n",[1994,2031,2032],{"class":1996,"line":368},[1994,2033,2034],{},"                       state: DefinitionState | None = None,\n",[1994,2036,2037],{"class":1996,"line":375},[1994,2038,2039],{},"                       cursor: tuple[datetime, UUID] | None = None\n",[1994,2041,2042],{"class":1996,"line":157},[1994,2043,2044],{},"                       ) -> tuple[list[Definition], bool]: ...\n",[1994,2046,2047],{"class":1996,"line":182},[1994,2048,2049],{},"    async def get_many(self, definition_ids: list[UUID],\n",[1994,2051,2052],{"class":1996,"line":290},[1994,2053,2054],{},"                       organization_id: UUID) -> list[Definition]: ...\n",[1994,2056,2057],{"class":1996,"line":280},[1994,2058,2059],{},"    async def create(self, definition: Definition) -> Definition: ...\n",[1994,2061,2062],{"class":1996,"line":272},[1994,2063,2064],{},"    async def save_draft(self, definition_id: UUID, organization_id: UUID, config: dict,\n",[1994,2066,2068],{"class":1996,"line":2067},15,[1994,2069,2070],{},"                         expected_updated_at: str) -> Definition | None: ...\n",[1994,2072,2074],{"class":1996,"line":2073},16,[1994,2075,2076],{},"    async def references_to(self, definition_id: UUID,\n",[1994,2078,2080],{"class":1996,"line":2079},17,[1994,2081,2082],{},"                            organization_id: UUID) -> list[DefinitionRef]: ...\n",[1994,2084,2086],{"class":1996,"line":2085},18,[1994,2087,2088],{},"    async def publish(self, definition_id: UUID, organization_id: UUID, config: dict,\n",[1994,2090,2092],{"class":1996,"line":2091},19,[1994,2093,2094],{},"                      referenced_ids: list[UUID],\n",[1994,2096,2097],{"class":1996,"line":520},[1994,2098,2099],{},"                      expected_updated_at: str) -> Definition | None: ...\n",[1994,2101,2102],{"class":1996,"line":318},[1994,2103,2104],{},"    async def set_state(self, definition_id: UUID, organization_id: UUID,\n",[1994,2106,2107],{"class":1996,"line":327},[1994,2108,2109],{},"                        state: DefinitionState) -> Definition: ...\n",[1994,2111,2113],{"class":1996,"line":2112},23,[1994,2114,2115],{},"    async def delete_draft(self, definition_id: UUID, organization_id: UUID) -> None: ...\n",[1994,2117,2119],{"class":1996,"line":2118},24,[1994,2120,2122],{"emptyLinePlaceholder":2121},true,"\n",[1994,2124,2126],{"class":1996,"line":2125},25,[1994,2127,2122],{"emptyLinePlaceholder":2121},[1994,2129,2131],{"class":1996,"line":2130},26,[1994,2132,2133],{},"class DefinitionValidator(Protocol):\n",[1994,2135,2137],{"class":1996,"line":2136},27,[1994,2138,2139],{},"    async def validate(self, definition: Definition, config: dict) -> ValidationResult: ...\n",[1994,2141,2143],{"class":1996,"line":2142},28,[1994,2144,2122],{"emptyLinePlaceholder":2121},[1994,2146,2148],{"class":1996,"line":2147},29,[1994,2149,2122],{"emptyLinePlaceholder":2121},[1994,2151,2152],{"class":1996,"line":481},[1994,2153,2154],{},"class DefinitionService:\n",[1994,2156,2158],{"class":1996,"line":2157},31,[1994,2159,2160],{},"    async def create_draft(self, kind, name, config, actor): ...\n",[1994,2162,2164],{"class":1996,"line":2163},32,[1994,2165,2166],{},"    async def fork(self, source_id, actor): ...\n",[1994,2168,2170],{"class":1996,"line":2169},33,[1994,2171,2172],{},"    async def update_draft(self, definition_id, patch, expected_updated_at, actor): ...\n",[1994,2174,2176],{"class":1996,"line":2175},34,[1994,2177,2178],{},"    async def validate_draft(self, definition_id, actor): ...\n",[1994,2180,2182],{"class":1996,"line":2181},35,[1994,2183,2184],{},"    async def publish(self, definition_id, expected_updated_at, actor): ...\n",[1994,2186,2188],{"class":1996,"line":2187},36,[1994,2189,2190],{},"    async def disable(self, definition_id, actor): ...\n",[1994,2192,2194],{"class":1996,"line":2193},37,[1994,2195,2196],{},"    async def enable(self, definition_id, actor): ...\n",[1994,2198,2200],{"class":1996,"line":2199},38,[1994,2201,2202],{},"    async def delete_draft(self, definition_id, actor): ...\n",[807,2204,2205,1025,2212,2214,2215,2217,2218,2220,2221,2223],{},[816,2206,2207,2208,2211],{},"Seven of the eight are a lifecycle move, and ",[840,2209,2210],{},"validate_draft"," is the read.",[840,2213,997],{}," without ",[840,2216,913],{}," is a one way door, and the surfaces page tells an admin to use ",[840,2219,997],{}," to prevent new Runs, which reads as reversible. ",[840,2222,958],{}," is the only way an abandoned draft leaves.",[807,2225,2226,2232,2233,1121,2235,2237,2238,2241,2242,2245,2246,2249],{},[816,2227,2228,2231],{},[840,2229,2230],{},"create_draft"," is the eighth, and the surface cannot skip it."," A new\ndefinition is a lifecycle write: it sets ",[840,2234,962],{},[840,2236,600],{},", and both are\nfields the client never writes. A router that called ",[840,2239,2240],{},"DefinitionRepository","\ndirectly would be the second writer of those two columns, and the authority\ncheck would then live in two places. It takes the kind, the name and a starting\nconfiguration, refuses a non-admin with ",[840,2243,2244],{},"forbidden",", and answers the new draft.\nIt runs no validation: a new draft is empty by definition, and ",[840,2247,2248],{},"update_draft","\nalready reports what is missing on the first save.",[807,2251,2252,2255,2256,2258],{},[816,2253,2254],{},"Two drafts may carry one name."," No unique constraint pairs the name with the\norganization, and ",[840,2257,1702],{}," already mints a second copy of a template on a second\ncall, by design. A name is a label an admin reads, and the id is the identity.",[807,2260,2261,2271,2272,2274,2275,2277,2278,2280,2281,2283,2284,2286,2287,2290],{},[816,2262,2263,2266,2267,2270],{},[840,2264,2265],{},"list_page"," pages on ",[840,2268,2269],{},"(created_at, id)",", and the page is a union."," It reads\nthe caller's own rows in every state, plus the platform rows that are ",[840,2273,878],{},",\nbecause a fork starts from a template. RLS does not run on this path, so the\nread applies ",[840,2276,1801],{}," to the platform half itself, exactly as\n",[840,2279,1805],{}," does. Ordering on ",[840,2282,1111],{}," was the earlier shape; ",[840,2285,1111],{}," is not\nunique, so a keyset cursor on it needs an encoder of its own, and ",[840,2288,2289],{},"created_at","\nis already on the table and already paired with an id by the shared cursor. The\npage reads one row past the limit, so a full page is never a truncated one.",[807,2292,2293,2300,2301,1105,2304,1105,2306,1121,2309,2311],{},[816,2294,2295,2296,2299],{},"Every repository method takes an ",[840,2297,2298],{},"organization_id",", and it is never optional."," Each call reaches Postgres on the service role, so RLS filters nothing and raises nothing. A signature that omits the tenant is a cross-tenant read on a read method and a cross-tenant write on ",[840,2302,2303],{},"save_draft",[840,2305,1934],{},[840,2307,2308],{},"set_state",[840,2310,958],{},". Nothing else carries the tenant, so the signature does.",[807,2313,2314,2316,2317,2320],{},[840,2315,1805],{}," is the one read with a wider rule, and it is a separate method for that reason. A fork source is a platform row, which belongs to no organization, or a row of the caller's own organization. Expressing that as an argument to ",[840,2318,2319],{},"get"," would make the ordinary read permissive by default, and the ordinary read is the one that runs everywhere.",[807,2322,2323,2329,2330,2333,2334,2337,2338,2341,2342,2345],{},[816,2324,2325,2326,1016],{},"The list method is not called ",[840,2327,2328],{},"list"," A method of that name shadows the\nbuiltin for the whole class body, so every ",[840,2331,2332],{},"-> list[Definition]"," written below\nit resolves to the method. ",[840,2335,2336],{},"mypy"," measures it as ",[840,2339,2340],{},"Function ... is not valid as a type",", and ",[840,2343,2344],{},"RunRepository.list_page"," already avoids the same trap.",[807,2347,2348,2351,2352,2355,2356,2359,2360,2363],{},[840,2349,2350],{},"get_many"," exists because publish revalidation is a fan out. One publish loads up to 50 direct referrers and then the definitions each of them references, and a per-row read makes that hundreds of round trips inside one HTTP request. Batch the reads with one ",[840,2353,2354],{},"in.()"," filter per level, exactly as ",[840,2357,2358],{},"RunRepository"," does, and keep them inside the ",[840,2361,2362],{},"FILTER_BATCH_SIZE"," the run repository already measured.",[807,2365,2366,2369,2370,2373,2374,2377],{},[840,2367,2368],{},"expected_updated_at"," gives optimistic concurrency, ",[816,2371,2372],{},"on a draft save and on a publish, and on neither of the three state moves",". A stale writer reloads. It does not overwrite the work of another admin. The repository returns ",[840,2375,2376],{},"None"," for a stale write rather than raising, because the caller always reloads. No distributed lock and no revision subsystem is added.",[807,2379,2380,1025,2389,2391,2392,2395,2396,2398],{},[816,2381,2382,1105,2384,1121,2386,2388],{},[840,2383,997],{},[840,2385,913],{},[840,2387,958],{}," carry no token, and that is deliberate.",[840,2390,997],{}," is the emergency switch. An admin who presses it during an incident must not be answered ",[840,2393,2394],{},"stale"," because a colleague saved a draft a second earlier, and the token buys nothing there: the write sets one column and overwrites no authored work. The race it leaves is bounded and already covered. A publish that adds a referrer between the disable guard's read and the disable's write leaves an active referrer pointing at a disabled definition, and ",[840,2397,913],{}," revalidates, which is the same place every other deferred referrer break surfaces.",[807,2400,1791,2401,2404,2405,2407],{},[840,2402,2403],{},"BEFORE UPDATE"," trigger moves ",[840,2406,1177],{}," on every write, so a publish and a disable each invalidate an editor's token. That is correct: both change what a later save would build on.",[807,2409,2410,1025,2413,2415,2416,2419,2420,2422,2423,1016],{},[816,2411,2412],{},"The write is one conditional statement, because there is no transaction.",[840,2414,1436],{}," reaches Postgres through PostgREST, so a save is ",[840,2417,2418],{},"PATCH ...?id=eq.\u003Cid>&updated_at=eq.\u003Cts>"," and an empty result body means stale. This is the same shape ",[840,2421,1023],{}," uses for a lifecycle transition. See ",[1054,2424,1056],{"href":372},[807,2426,2427],{},"A draft may be incomplete. Full validation runs at publish.",[987,2429,2431],{"id":2430},"the-draft-patch-replaces-a-field-and-never-merges-into-it","The draft patch replaces a field, and never merges into it",[807,2433,2434,2436,2437,2440,2441,2444],{},[840,2435,2248],{}," takes a ",[816,2438,2439],{},"shallow patch over the top level fields of the kind",". A named field is replaced whole. An absent field is untouched. An explicit ",[840,2442,2443],{},"null"," clears the field.",[832,2446,2449],{"className":2447,"code":2448,"language":837,"meta":838},[835],"patch {tool_ids: ['crm.read_company']}   ->  tool_ids becomes that one element\npatch {}                                 ->  the surface refuses it with 400\npatch {context_policy: null}             ->  the field is cleared\n",[840,2450,2448],{"__ignoreMap":838},[807,2452,2453,1025,2456,2459,2460,2462,2463,2465,2466,2469,2470,2473],{},[816,2454,2455],{},"An empty patch is refused, and it is not a no-op.",[840,2457,2458],{},"definitions_updated_at","\nis a ",[840,2461,2403],{}," trigger, so it moves ",[840,2464,1177],{}," on a write that changes\nno value. A save of ",[840,2467,2468],{},"{}"," would therefore answer ",[840,2471,2472],{},"ok",", change nothing, and make\nevery other admin's token stale. The surface refuses the empty object, so the\none write this service makes always carries a change.",[807,2475,2476,2479,2480,2483],{},[816,2477,2478],{},"A deep merge cannot remove anything",", and removal is the first edit an admin asks for: drop a tool, drop a skill, shorten a node list. A recursive merge would make ",[840,2481,2482],{},"tool_ids"," grow on every patch and never shrink, and the builder agent would have no way to say what it means. A workflow node list is one field under this rule, so an edit to one node sends the list.",[807,2485,2486,2488,2489,1025,2492,2494,2495,2498,2499,2501],{},[840,2487,2248],{}," runs the ",[816,2490,2491],{},"same candidate validation",[840,2493,1934],{}," runs and returns its ",[840,2496,2497],{},"ValidationResult",", advisory: an invalid draft still saves. It is the candidate half alone. A draft is never in a referrer set, so the referrer revalidation and the referrer cap have nothing to read, and a save that ran them would fan out to fifty definitions on each keystroke of a builder agent. ",[840,2500,2210],{}," is that one validation with no write, for a surface that checks before it commits. One code path answers both, so a draft can never be told it is valid by one method and invalid by the other. A draft is expected to be incomplete, and the result is what tells the builder agent what is still missing.",[987,2503,2505],{"id":2504},"the-closed-outcome-set","The closed outcome set",[807,2507,2508,2509,2512],{},"Like ",[840,2510,2511],{},"StartRunResult",", the service answers with a value and never an exception, because four surfaces branch on it.",[832,2514,2517],{"className":2515,"code":2516,"language":837,"meta":838},[835],"ok                    the write landed\nstale                 expected_updated_at did not match; reload and retry\ninvalid               validation failed; the result names every error\ndefinition_in_use     disable refused; an active definition references this one\nreferrer_limit        publish refused; it would exceed the direct referrer cap\nnot_a_draft           delete refused; a published definition is disabled, never deleted\nnot_published         disable or enable refused; the definition has never published\nforbidden             the actor may not publish in this organization\nnot_found             no such definition for this organization\n",[840,2518,2516],{"__ignoreMap":838},[807,2520,2521,1025,2527,1121,2529,2531,2532,2534,2535,2538,2539,2541,2542,2544,2545,2548,2549,2551],{},[816,2522,2523,2526],{},[840,2524,2525],{},"not_published"," is the ninth member, and it closes the state moves the other eight could not answer.",[840,2528,997],{},[840,2530,913],{}," on a ",[840,2533,868],{}," are reachable from any surface that lists definitions of every state, and none of the other members says what happened: ",[840,2536,2537],{},"not_a_draft"," is the inverse, ",[840,2540,1787],{}," is a lie the Builder disproves on its next tab, and ",[840,2543,1358],{}," names errors that do not exist. It maps to ",[840,2546,2547],{},"409",", exactly as ",[840,2550,2537],{}," does.",[807,2553,2554,2555,2557,2558,2560,2561,2563,2564,2566,2567,2569,2570,2572,2573,2341,2575,2577,2578,2580,2581,2583],{},"Three more calls are refused by members already here. ",[840,2556,1702],{}," of a draft answers ",[840,2559,1787],{},", per the templates section above. ",[840,2562,1934],{}," of an empty ",[840,2565,975],{}," answers ",[840,2568,1358],{},", because schema validation is the first check and an empty config fails it. ",[840,2571,997],{}," of a definition already ",[840,2574,892],{},[840,2576,913],{}," of one already ",[840,2579,878],{},", answer ",[840,2582,2472],{}," and write nothing: both are idempotent, because the surface behind them is a toggle and a second press must not raise.",[827,2585,2587],{"id":2586},"publish-rules-for-references","Publish rules for references",[807,2589,2590],{},"A definition can reference other definitions. An agent references skills. A workflow references agents, tools and subworkflows.",[807,2592,2593],{},"Two rules keep the graph valid.",[1307,2595,2596,2606],{},[1152,2597,2598,2601,2602,2605],{},[816,2599,2600],{},"A referenced active definition cannot be disabled."," Return ",[840,2603,2604],{},"definition_in_use",". Never cascade the disable, and never defer the failure to Run time.",[1152,2607,2608,2611],{},[816,2609,2610],{},"A publish revalidates every definition that references it."," Publishing agent A can remove a tool that workflow W depends on. Publishing subworkflow S can push workflow W past the depth cap. Validating only the definition being published leaves the break to be found at Run time.",[807,2613,2614],{},"If a referring definition would break, the publish fails and names it. The admin then fixes both, or forks.",[807,2616,2617,2620,2621,2623],{},[816,2618,2619],{},"Only a regression refuses the publish."," Validate each referrer twice, against the current ",[840,2622,884],{}," and against the candidate, and refuse only a referrer that passes the first and fails the second. A referrer that already fails against the current config was broken before this publish, and this publish is not what broke it: a deploy that retires a tool from the registry, or a validator that gets stricter, breaks referrers in place with no write at all. Refuse on the raw result instead and one such deploy makes every shared definition in the organization unpublishable at once, and each refusal names a definition whose fix the publisher may not own.",[807,2625,2626,2627,1016],{},"This is the same rule as the two below it. Put the refusal on the write that breaks the bound, not on the write that stands next to a bound already broken. A referrer that is already invalid surfaces where every other deferred break surfaces: its own next publish, and ",[840,2628,913],{},[807,2630,2631,2634,2635,2638],{},[816,2632,2633],{},"Bound the fan out."," One shared agent may be referenced by many workflows, and validating all of them inside one HTTP request is how a publish becomes a timeout. Publish revalidates ",[816,2636,2637],{},"direct"," referrers only. Validation is deterministic and cheap, so the bound is a latency ceiling rather than a correctness one.",[807,2640,2641,2644,2645,2648,2649,2652,2653,2656,2657,2660,2661,2664,2665,2668],{},[816,2642,2643],{},"An indirect referrer is not covered, and the run time check is what covers it."," The claim that a break propagates one level per publish holds only when the middle definition republishes, and nothing forces it to. Take ",[840,2646,2647],{},"V -> W -> S"," with the depth cap at three. Publishing ",[840,2650,2651],{},"S"," at depth two makes ",[840,2654,2655],{},"depth(W)"," three, which is legal, so ",[840,2658,2659],{},"W","'s revalidation passes and the publish lands. ",[840,2662,2663],{},"depth(V)"," is now four and ",[840,2666,2667],{},"V"," is broken, and no write anywhere is scheduled to discover it.",[807,2670,2671,2672,2674,2675,2677,2678,2680],{},"So the bound is honest about what it buys: direct revalidation catches the break the publisher can act on, and ",[1054,2673,1056],{"href":372}," re-checks the depth when a ",[840,2676,1641],{}," node starts a child Run, which is where ",[840,2679,2667],{}," fails. That re-check is not an optimisation and it is not belt-and-braces. It is the only mechanism that sees this case.",[987,2682,2684],{"id":2683},"the-cap-is-enforced-where-the-count-grows","The cap is enforced where the count grows",[807,2686,2687,2690,2691,2694],{},[816,2688,2689],{},"A publish may not make its definition the 51st direct referrer of anything it references."," The refusal is ",[840,2692,2693],{},"referrer_limit",", and it names the definition that is already at the cap.",[807,2696,2697,2698,2701],{},"The obvious placement is the opposite one, and it is a trap. The count grows when a ",[816,2699,2700],{},"referrer"," publishes, not when the referenced definition does. Enforce the cap on the referenced definition and an organization reaches 51 referrers without that definition ever being published, and it can then never be published again. Its own fix is refused by the same rule. Nothing in the product releases it.",[807,2703,2704],{},"Checking it on the growing side moves the refusal to the exact write that would break the bound, and the shared definition stays publishable at every count.",[807,2706,2707],{},"An organization that reaches 50 has a shared definition that deserves a fork, and the refusal says so.",[807,2709,2710,2713,2714,2717,2718,2721],{},[816,2711,2712],{},"Count the referrers of the target excluding the definition being published, and check only the targets this publish adds."," A re-publish grows nothing: agent A already references skill S, so A is already in S's referrer set, and a naive ",[840,2715,2716],{},"count >= 50"," refuses A's next publish while S sits at exactly 50 with A among them. That is this section's own trap in a third shape — a definition that can never publish again, and nothing in the product releases it. So the check is over ",[840,2719,2720],{},"candidate_referenced_ids - current_referenced_ids",", and for each target it asks whether the referrers other than this one already number 50.",[987,2723,2725,2726,2728],{"id":2724},"the-referrer-set-is-published_config-only","The referrer set is ",[840,2727,884],{}," only",[807,2730,2731,2732,2736],{},"A referrer is a definition whose ",[816,2733,2734],{},[840,2735,884],{}," names this one. A draft is not runnable, so it is neither revalidated nor counted.",[807,2738,2739,2742],{},[816,2740,2741],{},"One set, three rules, three different filters."," They are easy to blur into one query, and each one answers a different question.",[843,2744,2745,2758],{},[846,2746,2747],{},[849,2748,2749,2752,2755],{},[852,2750,2751],{},"Rule",[852,2753,2754],{},"Which referrers",[852,2756,2757],{},"Why",[859,2759,2760,2772,2791],{},[849,2761,2762,2765,2769],{},[864,2763,2764],{},"the disable guard",[864,2766,2767,2728],{},[840,2768,878],{},[864,2770,2771],{},"a switched-off referrer is not in service, so nothing breaks",[849,2773,2774,2777,2786],{},[864,2775,2776],{},"the referrer cap",[864,2778,2779,1025,2781,1025,2784],{},[840,2780,878],{},[816,2782,2783],{},"and",[840,2785,892],{},[864,2787,2788,2790],{},[840,2789,913],{}," returns a disabled referrer to service, so it still holds a slot",[849,2792,2793,2796,2800],{},[864,2794,2795],{},"publish revalidation",[864,2797,2798,2728],{},[840,2799,878],{},[864,2801,2802],{},"see below",[807,2804,2805,2814,2815,2818,2819,2822],{},[816,2806,2807,2808,2811,2812,1016],{},"So the repository has one lookup and not two, and ",[840,2809,2810],{},"DefinitionRef"," carries ",[840,2813,962],{}," A signature that answers a bare list or a bare count can express none of the three rows above, and a caller that wants the cap would have to ask for the wrong set and hope. ",[840,2816,2817],{},"references_to"," answers every referrer with its state, the service applies the filter its rule names, and the cap is a ",[840,2820,2821],{},"len()"," over the two states it counts. Three filters in one place read as three rules. Three filters spread over two repository methods read as a bug.",[807,2824,2825,2833,2834,2836,2837,2839],{},[816,2826,1791,2827,2829,2830,2832],{},[840,2828,892],{}," referrer must not refuse a publish, and ",[840,2831,913],{}," is the check that covers it."," The lifecycle rule above already says ",[840,2835,913],{}," revalidates, so a disabled referrer cannot silently return to service broken. Refusing the publish as well is the trap the referrer cap section names, in a second shape: disable workflow W, and agent A can never again publish a change W would reject. A is live, W is switched off, and nothing in the product releases A. ",[840,2838,958],{}," cannot help, because W is published and a published row never deletes.",[807,2841,2842],{},"So the refusal moves to the write that would put the broken definition back in service, exactly as the cap moves to the write that grows the count.",[987,2844,2846],{"id":2845},"publish-validates-before-it-writes","Publish validates before it writes",[807,2848,2849,2850,2852],{},"There is no transaction. ",[840,2851,1436],{}," reaches Postgres through PostgREST, which cannot span two statements, so a publish that wrote first and validated second would have nothing to roll back. A failing referrer would leave an invalid graph behind and report an error at the same time.",[807,2854,2855],{},"So the order is fixed.",[832,2857,2860],{"className":2858,"code":2859,"language":837,"meta":838},[835],"publish(definition, expected_updated_at)\n  -> validate the CANDIDATE config\n  -> load the direct referrers, and validate each one AGAINST the candidate\n  -> any failure -> refuse, name the referrer, write nothing\n  -> PATCH ...?id=eq.\u003Cid>&updated_at=eq.\u003Cexpected>\n       published_config, published_at, referenced_ids, state\n       empty result -> 'stale'; reload and retry\n",[840,2861,2859],{"__ignoreMap":838},[807,2863,2864],{},[816,2865,2866],{},"The refusals before that walk are ordered, and the order is behaviour a test reads.",[832,2868,2871],{"className":2869,"code":2870,"language":837,"meta":838},[835],"1  not_found      the read is org scoped, so a platform row and another tenant's row land here\n2  forbidden      the actor is not an admin of this organization\n3  not_published  disable or enable on a draft\n4  invalid        the candidate, then a referrer that regresses against it\n5  referrer_limit the cap, over the targets this publish adds\n",[840,2872,2870],{"__ignoreMap":838},[807,2874,2875,2877,2878,2880],{},[840,2876,1787],{}," is first because every later answer says that a row exists. ",[840,2879,2244],{}," precedes each state answer for the same reason in the other direction: an actor who may not publish learns nothing about the state of a definition. The two cheap checks therefore run before the pass that loads fifty referrers.",[807,2882,2883,2886,2887,2889],{},[816,2884,2885],{},"The cap runs before the referrer revalidation",", because it is a ",[840,2888,2821],{}," over one indexed read per added target and the revalidation is the expensive pass. A publish refused by the cap costs one round trip per target and no validation at all.",[807,2891,2892,2895,2896,2898],{},[816,2893,2894],{},"One publish validates up to a hundred times, so it holds one loader for all of them."," Fifty direct referrers, each validated against the current config and against the candidate, is a hundred validation passes inside one HTTP request, and a workflow pass walks the reference graph. Build one loader at the top of ",[840,2897,1934],{},", give it one cache keyed by definition id, and pass it to every pass. Without the cache the same subworkflow is read once per pass, and the publish becomes the timeout the fan-out bound exists to prevent.",[807,2900,2901,1025,2907,1739,2910,2913,2914,2916],{},[816,2902,2903,2906],{},[840,2904,2905],{},"published_at"," is in that list, and leaving it out fails the first publish of every draft.",[840,2908,2909],{},"definitions_published_at_shape",[840,2911,2912],{},"(published_config IS NULL) = (published_at IS NULL)",", so a row that gains a config without a timestamp raises ",[840,2915,1746],{},". The column is not a display field; it is half of a constraint.",[807,2918,2919,1025,2922,2924],{},[816,2920,2921],{},"Every publish writes it, so it reads as \"last published\" and never as \"first published\".",[840,2923,2289],{}," already holds the other date. A publish that wrote the timestamp once would leave an admin looking at a month-old date beside configuration changed this morning, and the skill freeze rule below is the case where that date is exactly what an admin checks.",[807,2926,2927,2932,2933,2936,2937,2940,2941,2944,2945,2948,2949,2952,2953,1777,2955,2958],{},[816,2928,2929,2931],{},[840,2930,2368],{}," is an opaque token, and the layer that must not parse it is the HTTP surface."," The filter travels in the URL as text, and PostgREST parses it back to ",[840,2934,2935],{},"timestamptz"," before it compares. So an equivalent spelling matches: a different UTC offset, padded trailing zeros, a space in place of the ",[840,2938,2939],{},"T",", and a Python ",[840,2942,2943],{},"datetime"," round trip that re-pads ",[840,2946,2947],{},".18"," to ",[840,2950,2951],{},".180000"," all match the row. Measured against the local stack, on one row whose ",[840,2954,1177],{},[840,2956,2957],{},"2026-08-21T20:31:36.180302+00:00",":",[832,2960,2963],{"className":2961,"code":2962,"language":837,"meta":838},[835],"2026-08-21T20:31:36.180302+00:00     the value as returned      matches\n2026-08-21T22:31:36.180302+02:00     another offset             matches\n2026-08-21 20:31:36.180302+00        a space, a short offset    matches\n2026-08-21T20:31:36.180302000+00:00  padded                     matches\n2026-08-21T20:31:36.1803+00:00       four digits                MATCHES NOTHING\n2026-08-21T20:31:36.180+00:00        three digits               MATCHES NOTHING\n",[840,2964,2962],{"__ignoreMap":838},[807,2966,2967,2968,2971,2972,2974,2975,2978,2979,2982,2983,2986,2987,2989,2990,2992,2993,2995],{},"Only a ",[816,2969,2970],{},"loss of precision"," breaks it, and one client type loses precision by construction. ",[840,2973,2935],{}," carries microseconds and a JavaScript ",[840,2976,2977],{},"Date"," carries milliseconds, so ",[840,2980,2981],{},"new Date(token).toISOString()"," truncates to three digits and the filter then matches no row: ",[816,2984,2985],{},"every"," write answers ",[840,2988,2394],{}," while the data is fine. PostgREST also strips trailing zeros on the way out, so ",[840,2991,2951],{}," is returned as ",[840,2994,2947],{}," and the truncation is invisible in the value the client holds.",[807,2997,2998,2999,3002,3003,3009,3010,3013],{},"The Agent Builder is a browser client and ",[840,3000,3001],{},"PATCH \u002Fapi\u002Fv1\u002Fagentic\u002Fdefinitions\u002F{id}\u002Fdraft"," is its route, so this is the path the defect sits on, not the Python one. The rule therefore belongs on the surface: ",[816,3004,3005,3006,3008],{},"the API returns ",[840,3007,2368],{}," as a string, documents it as opaque, and no client parses it into a date type."," The repository types it ",[840,3011,3012],{},"str"," for the same reason. A Python service that does parse it stays correct, which is exactly why the rule cannot be tested by watching Python work.",[807,3015,3016,3026,3027,3030,3031,3034,3035,3038,3039,3041,3042,3044,3045,3048],{},[816,3017,3018,3019,3022,3023,3025],{},"The token carries a ",[840,3020,3021],{},"+",", and a query string reads ",[840,3024,3021],{}," as a space."," The filter goes in the URL as ",[840,3028,3029],{},"updated_at=eq.2026-08-21T20:31:36.180302+00:00",", so the client must send ",[840,3032,3033],{},"%2B"," or PostgREST parses ",[840,3036,3037],{},"2026-08-21T20:31:36.180302 00:00"," and the write answers ",[840,3040,2394],{}," every time. No repository in ",[840,3043,1436],{}," filters on a timestamp today, so there is no call site that proves the client library encodes it. ",[816,3046,3047],{},"Prove it with an integration test against the local stack",", on a token read straight back from the row. A unit test over a mocked client proves the mock.",[807,3050,3051,3054,3055,3058],{},[816,3052,3053],{},"One race survives, and it is bounded."," A referrer may publish between the check and the write, so it is validated against the old config and never against the new one. Two backstops catch what that leaves, and both already exist: the depth cap is re-checked at run time, and a reference to a step that did not run fails the node with ",[840,3056,3057],{},"unresolved_reference",". A third publish of either definition repairs the record. V1 does not add a lock for this, because the window is milliseconds and the run time answer is already correct.",[987,3060,3062],{"id":3061},"the-reference-set-is-a-column-not-a-scan","The reference set is a column, not a scan",[807,3064,3065,3066,3069,3070,3073,3074,1121,3077,3079],{},"A reference is to ",[816,3067,3068],{},"another definition",": the ",[840,3071,3072],{},"skill_ids"," of an agent, and the target of every ",[840,3075,3076],{},"agent",[840,3078,1641],{}," node of a workflow. Reading them back the other way is one query that four rules depend on — the disable guard, the referrer revalidation, the referrer cap, and cycle detection.",[807,3081,3082,3087,3088,3090,3091,3094,3095,1016],{},[816,3083,3084,3086],{},[840,3085,2482],{}," are not in it."," A tool is a registry entry keyed by name, not a row of ",[840,3089,1100],{},", and V1 has one scope vocabulary which is that name. Tool existence is a registry lookup in the validator, and a tool id can never enter a ",[840,3092,3093],{},"UUID[]",". See ",[1054,3096,3097],{"href":190},"tools and integrations",[807,3099,3100,1025,3103,3105],{},[816,3101,3102],{},"A JSONB scan cannot serve the reverse lookup.",[840,3104,1100],{}," has no GIN index, and a reverse lookup over a JSONB document has no index to use at any table size.",[807,3107,3108],{},"So publish computes the set and stores it.",[832,3110,3113],{"className":3111,"code":3112,"language":837,"meta":838},[835],"agent.definitions\n  referenced_ids  UUID[]   the definition ids published_config names, computed at publish\n",[840,3114,3112],{"__ignoreMap":838},[807,3116,3117,1025,3123,3126,3127,3130,3131,3134,3135,3138],{},[816,3118,3119,3120,1016],{},"The column is not called ",[840,3121,3122],{},"references",[840,3124,3125],{},"REFERENCES"," is a reserved word in PostgreSQL, so ",[840,3128,3129],{},"CREATE TABLE ... (references UUID[])"," is a syntax error and only ",[840,3132,3133],{},"\"references\""," parses. It would be the one quoted identifier in the schema, and every later index, grant, migration and hand query would have to keep quoting it. ",[840,3136,3137],{},"ac-backend"," writes unquoted lower case identifiers, so the column takes a name that needs no quotes.",[807,3140,3141,3144,3145,3147,3148,3150,3151,3153,3154,3156],{},[840,3142,3143],{},"GIN"," on ",[840,3146,1822],{}," makes ",[840,3149,2817],{}," one indexed query. The column is derived, so it is written only by ",[840,3152,1934],{},", and never by hand. ",[840,3155,975],{}," contributes nothing to it, which is what keeps a draft out of the referrer set by construction rather than by a filter someone must remember.",[807,3158,3159,3168,3169,1037,3171,3174],{},[816,3160,3161,3162,3164,3165,3167],{},"A seeded platform template is the one row ",[840,3163,1934],{}," never writes, so its ",[840,3166,1822],{}," is seeded too."," The seed writes the column, and a contract test proves each seeded row's column matches its own ",[840,3170,884],{},[816,3172,3173],{},"Nothing reads it today."," The deep fork walks the config rather than the column, and a platform row has no referrers to find, because a custom definition references its own organization only. The rule stands so that the column never disagrees with the row it belongs to, which is what a later reader would trust.",[807,3176,3177,3183,3184,3187],{},[816,3178,3179,3180,3182],{},"The lookup takes an ",[840,3181,2298],{},", and it is not optional."," Every read and write on this schema uses the service role, so RLS filters nothing and raises nothing. Without the argument, ",[840,3185,3186],{},"references_to(id)"," reads across every tenant: the cap becomes global, so one organization's fiftieth workflow refuses another's publish, and the refusal names a definition in an organization the caller cannot see. The signature carries the tenant because nothing else will. The fork-first rule above is what makes the answer complete as well as scoped — every id in the column belongs to the row's own organization, so an org scoped lookup can miss nothing.",[807,3189,3190,3196,3197,3199],{},[816,3191,3192,3193,3195],{},"A Postgres array takes no foreign key, so nothing stops ",[840,3194,1822],{}," dangling by itself."," Two existing rules do it instead: validation refuses a reference to anything but an ",[840,3198,878],{}," definition, and a published definition is disabled rather than deleted. A draft is the only row that leaves, and a draft can never be referenced. Do not add a trigger for this; add a contract test.",[807,3201,3202,3203,3206,3207,1037,3210,3212,3213,3215,3216,3218],{},"This column is additive and it is not in migration ",[840,3204,3205],{},"20260819120100",". It belongs to the ticket that builds this service, and that migration must ",[816,3208,3209],{},"decide the read grant explicitly",[840,3211,1100],{}," grants ",[840,3214,1797],{}," an enumerated column list, so a new column is unreadable until a migration names it. ",[840,3217,1822],{}," stays out of the list: it is derived, the service is its only reader, and the reverse graph of one organization's definitions is not something a member needs.",[827,3220,3222],{"id":3221},"agent-definition","Agent definition",[832,3224,3227],{"className":3225,"code":3226,"language":837,"meta":838},[835],"model\ninstructions\noutput_schema?       JSON Schema Draft 2020-12 for one object\ncontext_policy\ntool_ids[]\nskill_ids[]\nbudget_defaults      max_segments, max_agent_turns, max_tool_calls, max_run_duration_s, max_cost_cents\n",[840,3228,3226],{"__ignoreMap":838},[807,3230,3231,3232,1016],{},"The agent receives only its explicit tool set. A skill never widens it. The agent validator holds that rule, because a skill has no agent at publish time. See ",[1054,3233,3235],{"href":3234},"#skill-definition","the skill definition",[807,3237,3238,3244],{},[816,3239,3240,3243],{},[840,3241,3242],{},"model"," resolves in the model registry at publish."," A definition may otherwise publish any string, and the failure arrives at the first Run of a definition an admin believes is valid. The check is one lookup and it is deterministic, which is the standard every other check on this page meets.",[807,3246,3247,3250,3251,3254,3255,1121,3258,3260],{},[816,3248,3249],{},"The registry is a constant in the definitions package, and it is not a table."," V1 supports a fixed set of provider and model pairs, a deploy adds a pair, and no admin edits one. A table would need a lifecycle, a migration and a surface for a list that changes when the code changes. ",[840,3252,3253],{},"ModelConfig"," already carries ",[840,3256,3257],{},"provider",[840,3259,3242],{},", so the registry is the set of legal pairs plus the per-pair defaults the snapshot freezes.",[807,3262,3263,3268,3269,3272,3273,3276,3277,3279,3280,1016],{},[816,3264,3265,3267],{},[840,3266,1120],{}," is optional."," When it is present, it must be a valid JSON Schema Draft 2020-12 document with ",[840,3270,3271],{},"type: object",". Publish freezes the schema in the Run snapshot. The runtime requests JSON, validates the returned object locally against the frozen schema, and writes it to ",[840,3274,3275],{},"RunResult.output",". An ",[840,3278,3076],{}," workflow node then exposes that object as ",[840,3281,3282],{},"steps.\u003Cid>.output",[807,3284,3285],{},"The runtime does not send this plain schema as a provider-native response format. OpenAI and Anthropic use different response envelopes. JSON mode plus local validation keeps one portable definition contract.",[807,3287,3288,3289,3291,3292,2341,3295,3297],{},"When ",[840,3290,1120],{}," is absent, the agent keeps the text result contract. Its text becomes ",[840,3293,3294],{},"RunResult.summary",[840,3296,3275],{}," stays empty. This keeps existing agent definitions unchanged.",[807,3299,3300,3303,3304,1016],{},[840,3301,3302],{},"budget_defaults"," becomes the Run ceilings. Policy limits cap them. See ",[1054,3305,1056],{"href":372},[807,3307,3308,1025,3314,3316],{},[816,3309,3310,3313],{},[840,3311,3312],{},"max_run_duration_s"," is capped at 30 days by validation.",[1054,3315,270],{"href":269}," retains a tool claim for 30 days because the claim is the replay journal and it must outlive the longest Run any definition may set. That retention is a per scope constant, so it can only take the ceiling if a ceiling exists. Without this cap a definition could declare a 60 day Run whose journal is forgotten halfway through, and a later segment would repeat an effect it already made.",[827,3318,3320],{"id":3319},"skill-definition","Skill definition",[832,3322,3325],{"className":3323,"code":3324,"language":837,"meta":838},[835],"description\ninstructions\ntool_ids[]\n",[840,3326,3324],{"__ignoreMap":838},[807,3328,3329,3330,3333,3334,2341,3337,3340],{},"V1 stores short procedure text in Postgres. ",[840,3331,3332],{},"skill.tool_ids"," must be a subset of ",[840,3335,3336],{},"agent.tool_ids",[816,3338,3339],{},"the agent validator checks it",". A skill publishes on its own, with no agent in hand, so the check cannot live in the skill validator. The referrer rule closes the other direction: publishing a skill revalidates every agent that references it, and an agent that no longer covers the skill's tools fails there.",[807,3342,3343,1025,3346,3349,3350,3352,3353,3355],{},[816,3344,3345],{},"A skill renders at the freeze, so a skill publish reaches an agent that did not republish.",[840,3347,3348],{},"SnapshotBuilder"," reads the skill's current ",[840,3351,884],{},", so a new Run of agent A uses the skill published a minute ago, whatever A's own ",[840,3354,2905],{}," says. This is intended: a skill is shared procedure text, and forcing every referring agent to republish would make a wording fix an N-definition change.",[807,3357,3358],{},"State it to an admin, because it is the one place the publish boundary bends. An in flight Run is unaffected, since its snapshot already holds the rendered text.",[807,3360,3361,3362,3365],{},"Use a workflow when the order is fixed. Skill instructions are flexible guidance. They are not executable nodes, and the Run foreign key carries ",[840,3363,3364],{},"kind",", so a skill cannot be the definition of a Run.",[827,3367,3369],{"id":3368},"workflow-definition","Workflow definition",[807,3371,3372,3373,1016],{},"A small declarative tree. The node types are listed in ",[1054,3374,3375],{"href":379},"agentic runtime",[807,3377,3378,3379,3382],{},"Each node has a stable ID, a typed input or reference, and an optional ",[840,3380,3381],{},"continue_on_error"," flag.",[807,3384,3385],{},[816,3386,3387],{},"Two fields that were declared here are gone, and both were measured.",[843,3389,3390,3400],{},[846,3391,3392],{},[849,3393,3394,3397],{},[852,3395,3396],{},"Field",[852,3398,3399],{},"Why it is absent",[859,3401,3402,3418],{},[849,3403,3404,3408],{},[864,3405,3406],{},[840,3407,438],{},[864,3409,3410,3411,2341,3414,3417],{},"The Inngest Python SDK sets retries per ",[816,3412,3413],{},"function",[840,3415,3416],{},"step.run"," takes no count. A node cannot carry one. A transient fault already raises out of the node body and reaches the function retry, which replays every memoized step for free, so the field named a second retry policy the platform cannot honour.",[849,3419,3420,3425],{},[864,3421,3422],{},[840,3423,3424],{},"result_key",[864,3426,3427,3429],{},[840,3428,3282],{}," is keyed on the node ID, so a result key is a second name for one value.",[807,3431,3432,3435,3436,3439],{},[816,3433,3434],{},"A workflow may declare one result projection."," The optional top-level\n",[840,3437,3438],{},"result"," field is an object of literals and the same marked references that a\nnode input uses. The runtime resolves it only after the complete workflow\nsucceeds. For example:",[832,3441,3445],{"className":3442,"code":3443,"language":3444,"meta":838,"style":838},"language-json shiki shiki-themes github-dark","{\"result\": {\"smart_feed\": {\"$ref\": \"steps.compile-smart-feed-observations.output\"}}}\n","json",[840,3446,3447],{"__ignoreMap":838},[1994,3448,3449,3453,3457,3460,3463,3465,3468,3471,3475],{"class":1996,"line":22},[1994,3450,3452],{"class":3451},"s95oV","{",[1994,3454,3456],{"class":3455},"sDLfK","\"result\"",[1994,3458,3459],{"class":3451},": {",[1994,3461,3462],{"class":3455},"\"smart_feed\"",[1994,3464,3459],{"class":3451},[1994,3466,3467],{"class":3455},"\"$ref\"",[1994,3469,3470],{"class":3451},": ",[1994,3472,3474],{"class":3473},"sU2Wk","\"steps.compile-smart-feed-observations.output\"",[1994,3476,3477],{"class":3451},"}}}\n",[807,3479,3480,3481,3483],{},"This is not a second name on a node. It is the small public result of the whole\nworkflow. A workflow that omits it keeps the existing result map with one key\nper output-producing node. A partial success also keeps that diagnostic map,\nbecause the selected final node might not have run. A selected reference that\ncannot resolve after a complete walk fails the Run with\n",[840,3482,3057],{},"; it never becomes null.",[807,3485,3486,3487,3489],{},"Publish validation requires ",[840,3488,3438],{}," to be an object. It applies the same\nreference depth, namespace, known-node and output-producing-node checks as a\nnode input. The snapshot freezes the object with the root and ceilings.",[807,3491,3492,3495,3496,3499],{},[816,3493,3494],{},"A reference is marked, because a bare string cannot be."," A declared input holds literals, and ",[840,3497,3498],{},"\"input.campaign\""," is a legal literal. So a reference is a one key mapping and nothing else in the language is:",[832,3501,3503],{"className":3442,"code":3502,"language":3444,"meta":838,"style":838},"{\"query\": {\"$ref\": \"steps.classify.output.company_name\"}, \"limit\": 25}\n",[840,3504,3505],{"__ignoreMap":838},[1994,3506,3507,3509,3512,3514,3516,3518,3521,3524,3527,3529,3532],{"class":1996,"line":22},[1994,3508,3452],{"class":3451},[1994,3510,3511],{"class":3455},"\"query\"",[1994,3513,3459],{"class":3451},[1994,3515,3467],{"class":3455},[1994,3517,3470],{"class":3451},[1994,3519,3520],{"class":3473},"\"steps.classify.output.company_name\"",[1994,3522,3523],{"class":3451},"}, ",[1994,3525,3526],{"class":3455},"\"limit\"",[1994,3528,3470],{"class":3451},[1994,3530,3531],{"class":3455},"25",[1994,3533,3534],{"class":3451},"}\n",[807,3536,3537],{},"The rejected alternative was a template over the string, which is an expression language by another name. The page refuses one for a condition, and the same reason holds here.",[807,3539,3540,1025,3543,3546],{},[816,3541,3542],{},"A node ID is unique across the whole workflow, not inside its container.",[840,3544,3545],{},"steps.\u003Cid>"," carries no path, so a per container ID cannot resolve. A subworkflow keeps its own ID space: a parent sees one value at the subworkflow node, and never a node inside it.",[807,3548,3549,3552,3553,3556,3557,3560],{},[816,3550,3551],{},"A node ID is at most 410 characters, because it becomes a claim key."," A node ID is a step path, and two keys are built from one: ",[840,3554,3555],{},"\u003Crun_id>:\u003Cnode_id>:\u003Cargs_hash>",", which the tool journal writes and which an ",[840,3558,3559],{},"approval"," node writes. Both columns hold 512, and a UUID and a SHA-256 digest take 102 of them. Unbounded, a workflow publishes and then fails mid Run, because the key is built at the node and not at the publish.",[807,3562,3563],{},[816,3564,3565,3566,3568],{},"Three node types produce a ",[840,3567,3282],{},", and three do not.",[843,3570,3571,3582],{},[846,3572,3573],{},[849,3574,3575,3578],{},[852,3576,3577],{},"Node",[852,3579,3580],{},[840,3581,3282],{},[859,3583,3584,3597,3610],{},[849,3585,3586,3591],{},[864,3587,3588],{},[840,3589,3590],{},"tool",[864,3592,3593,3594],{},"the bounded ",[840,3595,3596],{},"ToolResult.output",[849,3598,3599,3605],{},[864,3600,3601,1105,3603],{},[840,3602,3076],{},[840,3604,1641],{},[864,3606,3607,3608],{},"the child Run's ",[840,3609,3275],{},[849,3611,3612,3623],{},[864,3613,3614,1105,3617,1105,3620],{},[840,3615,3616],{},"sequence",[840,3618,3619],{},"parallel",[840,3621,3622],{},"branch",[864,3624,3625,3628],{},[816,3626,3627],{},"nothing."," A container produces no value of its own",[807,3630,3631,3632,3635],{},"A container coordinates; it does not compute. Name the child that produced the value, not the container around it. Validation refuses ",[840,3633,3634],{},"steps.\u003Ccontainer_id>.output",", because the alternative is a run time null in a workflow that looked valid.",[807,3637,3638,3641,3642,3644],{},[816,3639,3640],{},"A retry cannot repeat an effect that already happened."," The idempotency journal keys on the node ID and the argument hash, so a replayed ",[840,3643,3590],{}," node with the same arguments reads the stored result rather than calling again. That is the correct behaviour, and it is why the retry the function already gives is enough.",[843,3646,3647,3657],{},[846,3648,3649],{},[849,3650,3651,3654],{},[852,3652,3653],{},"The node failed on",[852,3655,3656],{},"The function retry",[859,3658,3659,3670],{},[849,3660,3661,3664],{},[864,3662,3663],{},"a platform fault, or a retryable upstream error",[864,3665,3666,3669],{},[816,3667,3668],{},"helps."," The claim was released, so the call really runs again",[849,3671,3672,3677],{},[864,3673,3674,3675],{},"a business failure such as ",[840,3676,1787],{},[864,3678,3679,3682],{},[816,3680,3681],{},"cannot help."," The claim completed, so every attempt reads the same answer",[807,3684,3685,3686,3688,3689,3691,3692,3694],{},"A business failure needs ",[840,3687,3381],{}," or a ",[840,3690,3622],{},", and never a retry. That is why the node body returns it as a value rather than raising: a raise reaches the function retry, which cannot change the answer and which ends every sibling branch of a ",[840,3693,3619],{}," node.",[807,3696,3697,3700,3701,3703,3704,3706,3707,3710,3711,3714],{},[816,3698,3699],{},"A reference to a step that did not run fails the node."," A ",[840,3702,3622],{}," picks one child, and ",[840,3705,3381],{}," lets a node settle with no output, so ",[840,3708,3709],{},"steps.\u003Cid>.output.*"," can name a step that never produced anything. Validation checks that the name ",[816,3712,3713],{},"exists","; it does not prove the step is reachable on every path, and V1 does not add a reachability analysis to find out.",[807,3716,3717,3718,3720],{},"At run time the node fails with ",[840,3719,3057],{},", naming the step. It fails rather than substituting a null, because a workflow that silently sends an email with an empty body is worse than one that stops.",[807,3722,3723,3700,3726,1137,3728,3730,3731,3734,3735,3738],{},[816,3724,3725],{},"A fan out step declares two numbers.",[840,3727,3590],{},[840,3729,3076],{}," step carries ",[840,3732,3733],{},"max_fanout",", the total items, and ",[840,3736,3737],{},"fanout_concurrency",", how many items can be in flight.",[807,3740,3741,3742,3744,3745,3748,3749,3752,3753,3755],{},"Phase 4 runs wide ",[840,3743,3590],{}," steps only. A wide tool must have ",[840,3746,3747],{},"workflow_allowed = True"," and be read-only (",[840,3750,3751],{},"side_effects = \"read\"","). Write and send fan outs wait until partial effects and approvals have one explicit recovery rule. An ",[840,3754,3076],{}," step keeps both fields at one until the runtime can start and join many child Runs. Publish refuses every wider node that the executor cannot run.",[807,3757,3758,3761],{},[816,3759,3760],{},"A workflow that must write many rows uses a batch write tool, not a wide step."," One width-one call carries the whole bounded list. The handler writes each row on its own idempotent key and opens no transaction over the batch, so a retry of the same call resolves the same rows and completes the missing ones. That is the recovery rule a wide write still lacks.",[807,3763,3764,3767],{},[816,3765,3766],{},"A wide tool input is one reference to a list of complete argument mappings."," It does not add an item template or an item namespace:",[832,3769,3771],{"className":3442,"code":3770,"language":3444,"meta":838,"style":838},"{\n  \"type\": \"tool\",\n  \"id\": \"find_people\",\n  \"tool\": \"research.search_people\",\n  \"input\": {\"$ref\": \"steps.rank.output.people_search_inputs\"},\n  \"max_fanout\": 200,\n  \"fanout_concurrency\": 20\n}\n",[840,3772,3773,3778,3791,3803,3815,3832,3844,3854],{"__ignoreMap":838},[1994,3774,3775],{"class":1996,"line":22},[1994,3776,3777],{"class":3451},"{\n",[1994,3779,3780,3783,3785,3788],{"class":1996,"line":32},[1994,3781,3782],{"class":3455},"  \"type\"",[1994,3784,3470],{"class":3451},[1994,3786,3787],{"class":3473},"\"tool\"",[1994,3789,3790],{"class":3451},",\n",[1994,3792,3793,3796,3798,3801],{"class":1996,"line":233},[1994,3794,3795],{"class":3455},"  \"id\"",[1994,3797,3470],{"class":3451},[1994,3799,3800],{"class":3473},"\"find_people\"",[1994,3802,3790],{"class":3451},[1994,3804,3805,3808,3810,3813],{"class":1996,"line":244},[1994,3806,3807],{"class":3455},"  \"tool\"",[1994,3809,3470],{"class":3451},[1994,3811,3812],{"class":3473},"\"research.search_people\"",[1994,3814,3790],{"class":3451},[1994,3816,3817,3820,3822,3824,3826,3829],{"class":1996,"line":264},[1994,3818,3819],{"class":3455},"  \"input\"",[1994,3821,3459],{"class":3451},[1994,3823,3467],{"class":3455},[1994,3825,3470],{"class":3451},[1994,3827,3828],{"class":3473},"\"steps.rank.output.people_search_inputs\"",[1994,3830,3831],{"class":3451},"},\n",[1994,3833,3834,3837,3839,3842],{"class":1996,"line":222},[1994,3835,3836],{"class":3455},"  \"max_fanout\"",[1994,3838,3470],{"class":3451},[1994,3840,3841],{"class":3455},"200",[1994,3843,3790],{"class":3451},[1994,3845,3846,3849,3851],{"class":1996,"line":360},[1994,3847,3848],{"class":3455},"  \"fanout_concurrency\"",[1994,3850,3470],{"class":3451},[1994,3852,3853],{"class":3455},"20\n",[1994,3855,3856],{"class":1996,"line":368},[1994,3857,3534],{"class":3451},[807,3859,3860,3861,3864],{},"Each list item is the full public argument mapping for one ",[840,3862,3863],{},"ToolInvoker"," call. An empty list succeeds with an empty list. A non-mapping item fails the node before any call starts. A completed node returns one ordered envelope per input item. A ceiling returns the dense prefix through its first stopping input position, as runtime execution defines:",[832,3866,3868],{"className":3442,"code":3867,"language":3444,"meta":838,"style":838},"[\n  {\"ok\": true, \"data\": {\"company\": \"Example\"}},\n  {\"ok\": false, \"error\": {\"code\": \"not_found\", \"message\": \"No company matched\"}}\n]\n",[840,3869,3870,3875,3906,3945],{"__ignoreMap":838},[1994,3871,3872],{"class":1996,"line":22},[1994,3873,3874],{"class":3451},"[\n",[1994,3876,3877,3880,3883,3885,3888,3890,3893,3895,3898,3900,3903],{"class":1996,"line":32},[1994,3878,3879],{"class":3451},"  {",[1994,3881,3882],{"class":3455},"\"ok\"",[1994,3884,3470],{"class":3451},[1994,3886,3887],{"class":3455},"true",[1994,3889,1105],{"class":3451},[1994,3891,3892],{"class":3455},"\"data\"",[1994,3894,3459],{"class":3451},[1994,3896,3897],{"class":3455},"\"company\"",[1994,3899,3470],{"class":3451},[1994,3901,3902],{"class":3473},"\"Example\"",[1994,3904,3905],{"class":3451},"}},\n",[1994,3907,3908,3910,3912,3914,3917,3919,3922,3924,3927,3929,3932,3934,3937,3939,3942],{"class":1996,"line":233},[1994,3909,3879],{"class":3451},[1994,3911,3882],{"class":3455},[1994,3913,3470],{"class":3451},[1994,3915,3916],{"class":3455},"false",[1994,3918,1105],{"class":3451},[1994,3920,3921],{"class":3455},"\"error\"",[1994,3923,3459],{"class":3451},[1994,3925,3926],{"class":3455},"\"code\"",[1994,3928,3470],{"class":3451},[1994,3930,3931],{"class":3473},"\"not_found\"",[1994,3933,1105],{"class":3451},[1994,3935,3936],{"class":3455},"\"message\"",[1994,3938,3470],{"class":3451},[1994,3940,3941],{"class":3473},"\"No company matched\"",[1994,3943,3944],{"class":3451},"}}\n",[1994,3946,3947],{"class":1996,"line":244},[1994,3948,3949],{"class":3451},"]\n",[807,3951,3952,3953,3955,3956,3958,3959,3962],{},"A business failure settles one item and does not fail the node. ",[840,3954,3381],{}," does not change item handling. It applies only if the whole node fails. A ",[840,3957,3590],{}," step with ",[840,3960,3961],{},"max_fanout = 1"," keeps the existing mapping input and the existing single result.",[807,3964,3965,3966,3969],{},"This shape keeps the language small. The step before the fan out builds the argument list. The fan out does not need a template, an alias or an ",[840,3967,3968],{},"item.*"," reference namespace.",[832,3971,3974],{"className":3972,"code":3973,"language":837,"meta":838},[835],"ceil(max_fanout \u002F fanout_concurrency)  x  timeout_s\n    \u003C=  the step budget - FANOUT_STEP_HEADROOM_S\n",[840,3975,3973],{"__ignoreMap":838},[807,3977,3978,3984,3985,3988],{},[816,3979,3980,3983],{},[840,3981,3982],{},"timeout_s"," bounds one complete fan-out item."," The wide scheduler applies it around the item worker, including ",[840,3986,3987],{},"ToolNodeCaller.call"," and its result envelope. Argument validation, policy, metering, the handler and result handling all fit inside the number used by the formula. The inner handler timeout stays as a second guard. Without the outer guard, policy or persistence time would sit outside the proof.",[807,3990,3991,3997],{},[816,3992,3993,3996],{},[840,3994,3995],{},"FANOUT_STEP_HEADROOM_S"," is 10 seconds."," It covers list checks, task scheduling, batch transitions and final serialization. The executor also applies the full step budget around the node. The fixed reserve makes equality in the item formula safe and the outer guard protects a stale snapshot.",[807,3999,4000,1025,4003,4005,4006,4009],{},[816,4001,4002],{},"\"The step budget\" is the worker's step ceiling, and no node declares one.",[1054,4004,373],{"href":372}," owns the number and the rule that sets it. Do not restate the number here. This check puts a fan out inside the same ceiling. It is not ",[840,4007,4008],{},"max_run_duration",", which bounds the whole Run and not one step.",[807,4011,4012,4015],{},[816,4013,4014],{},"The retry count is not in the formula."," A retry replays the function and the fan out step. Each attempt is a separate step execution. The step budget bounds one execution. Multiplying by retries would reject a valid definition.",[807,4017,4018,4019,4022,4023,4025],{},"Retries increase the ",[816,4020,4021],{},"Run's"," latency. ",[840,4024,4008],{}," includes that elapsed time.",[807,4027,4028],{},"Registry validation already checks that one call fits the step budget. That is not enough for a step making hundreds of calls: a wide fan out passes every per-call check and then blows the budget as a whole.",[807,4030,4031],{},"Two numbers rather than one, because each answers a different question and a single number gets one of them wrong.",[843,4033,4034,4047],{},[846,4035,4036],{},[849,4037,4038,4041,4044],{},[852,4039,4040],{},"Number",[852,4042,4043],{},"Bounds",[852,4045,4046],{},"Read by",[859,4048,4049,4061],{},[849,4050,4051,4055,4058],{},[864,4052,4053],{},[840,4054,3733],{},[864,4056,4057],{},"how much work the step may take on",[864,4059,4060],{},"the step budget check above",[849,4062,4063,4067,4070],{},[864,4064,4065],{},[840,4066,3737],{},[864,4068,4069],{},"how hard the step hits a vendor at one instant",[864,4071,4072,4073],{},"the vendor arithmetic in ",[1054,4074,3097],{"href":190},[807,4076,4077],{},"A width of 200 is ten batches at the maximum concurrency of 20. Multiplying by the width alone would reject a valid step, and treating all 200 as one batch would overload one worker.",[807,4079,4080,4081,4084,4085,4088],{},"Both values are strict positive JSON integers; a boolean, string or decimal is invalid. Both default to one. ",[840,4082,4083],{},"MAX_FANOUT"," is 200. ",[840,4086,4087],{},"MAX_FANOUT_CONCURRENCY"," is 20. Concurrency cannot exceed the declared width. The defaults keep a step with no fan out declaration unchanged and do not turn one wide declaration into an unbounded burst.",[807,4090,4091,1025,4094,4100,4101,4104],{},[816,4092,4093],{},"The declared output must fit one memoized step.",[1054,4095,4099],{"href":4096,"rel":4097},"https:\u002F\u002Fwww.inngest.com\u002Fdocs\u002Fusage-limits\u002Finngest#platform-limits",[4098],"nofollow","Inngest accepts at most 4 MiB from one step",". Validation gives tool data 3 MiB and keeps 1 MiB for the item envelopes and node result. It uses the tool's ",[840,4102,4103],{},"max_output_bytes",", or the 32 KiB platform tool limit when the tool declares none:",[832,4106,4109],{"className":4107,"code":4108,"language":837,"meta":838},[835],"max_fanout  x  effective max_output_bytes  \u003C=  3 MiB\n",[840,4110,4108],{"__ignoreMap":838},[807,4112,4113,4114,4117],{},"The executor also measures the final serialized envelope before it returns. The\nenvelope must fit Inngest's 4 MiB step-output limit. A stale snapshot or an\nunexpectedly large error cannot turn into an Inngest 413. The executor fails the\nnode with ",[840,4115,4116],{},"fanout_output_too_large"," and no truncated success.",[807,4119,4120,4121,4124],{},"The item-time and 3 MiB node-data checks apply only when ",[840,4122,4123],{},"max_fanout > 1",". A width-one tool keeps the registry's existing timeout rule and its existing single-result shape.",[807,4126,4127,4130],{},[816,4128,4129],{},"The workflow must fit Inngest's 32 MiB function state too."," Validation gives declared node data 24 MiB and keeps 8 MiB for the event, item and node envelopes, and Inngest metadata. It sums every leaf conservatively, including both sides of a branch:",[832,4132,4135],{"className":4133,"code":4134,"language":837,"meta":838},[835],"one tool item       effective max_output_bytes\none wide tool       max_fanout x effective max_output_bytes\nagent\u002Fsubworkflow   the 32 KiB RunResult output limit\n\nsum of every leaf  \u003C= 24 MiB\n",[840,4136,4134],{"__ignoreMap":838},[807,4138,4139,4140,4143],{},"This is a publish check, not a run-time truncation. A workflow over the total returns ",[840,4141,4142],{},"workflow_state_too_large"," and does not publish.",[807,4145,4146,4149,4150,4153,4154,4157],{},[816,4147,4148],{},"ENG-2201 does not open the publish gate."," It adds the node fields and every static check above, then returns ",[840,4151,4152],{},"fanout_not_available"," for an otherwise valid wide node while ",[840,4155,4156],{},"WIDE_TOOL_EXECUTION_READY"," is false. ENG-2202 lands the scheduler and changes that constant to true in the same release. A declaration cannot reach a width-one executor between the two tickets.",[807,4159,4160,4163,4164,4167],{},[816,4161,4162],{},"A wide tool takes no item claim."," Phase 4 permits reads only, and a read never enters the tool journal. Every call keeps the node ID as its step path, and no item key is added. ",[840,4165,4166],{},"MAX_NODE_ID"," therefore stays 410. Equal argument mappings are equal read calls and can both run.",[807,4169,4170,4175,4176,4178,4179,4182,4183,4185],{},[816,4171,4172,4174],{},[840,4173,3733],{}," is a ceiling the step enforces, and a step over it fails."," A run time list longer than ",[840,4177,3733],{}," fails the node with ",[840,4180,4181],{},"fanout_exceeded",", naming the declared number and the number of items. It does not silently process the first ",[840,4184,3733],{}," of them. A truncation looks like a success, and a workflow that researches 200 of 500 companies and reports success is the failure this number exists to prevent. An author who expects a longer list raises the declaration, and the budget check tells them whether it fits.",[807,4187,4188],{},"Inputs may reference only allowed namespaces:",[832,4190,4193],{"className":4191,"code":4192,"language":837,"meta":838},[835],"input.*\nsteps.\u003Cid>.output.*\n",[840,4194,4192],{"__ignoreMap":838},[807,4196,4197],{},"V1 has no loops, no expression language, no dynamic fan out node, no private schedule and no private event trigger. Schedules and events are trigger rows.",[807,4199,4200],{},[816,4201,4202],{},"Depth counts workflows, and the cap is three.",[832,4204,4207],{"className":4205,"code":4206,"language":837,"meta":838},[835],"depth(a workflow with no subworkflow node)  = 1\ndepth(a workflow)                           = 1 + max(depth of each referenced subworkflow)\ncap                                         = 3\n",[840,4208,4206],{"__ignoreMap":838},[807,4210,4211,4214,4215,4218,4219,4221],{},[816,4212,4213],{},"Cycle detection runs before this function, and the order is not a preference."," The recursion has no cycle guard, so a workflow that reaches itself recurses until the stack ends. A publish that answers ",[840,4216,4217],{},"RecursionError"," instead of ",[840,4220,1358],{}," gives the admin nothing to fix. Detect the cycle, name it, and only then measure depth.",[807,4223,4224,4230,4231,4233,4234,4236,4237,4239,4240,4242],{},[816,4225,4226,4227,4229],{},"A cycle is possible between workflows and nowhere else, so the walk is over ",[840,4228,1641],{}," targets only."," An agent references skills, a skill references nothing, an ",[840,4232,3076],{}," node targets an agent and a ",[840,4235,1641],{}," node targets a workflow. No path leaves a workflow and returns to one except through a ",[840,4238,1641],{}," node. Write the walk over that one edge rather than a general traversal of ",[840,4241,1822],{},": the general one visits agents and skills to prove something their shape already proves, and it is the version that later grows a case nobody needs.",[807,4244,4245,4246,4248,4249,4251,4252,4251,4254,4256,4257,4260],{},"So a top level workflow may reach two levels of subworkflow below it, and no more. Container nesting is ",[816,4247,824],{}," counted here: a ",[840,4250,3616],{}," inside a ",[840,4253,3619],{},[840,4255,3616],{}," costs nothing, because it starts no child Run. Containers carry their own separate cap of ",[816,4258,4259],{},"10",", which exists only to refuse a document no person authored.",[807,4262,4263,4266,4267,4269],{},[816,4264,4265],{},"The same function is called twice, and it must be one function."," Publish checks it, and ",[1054,4268,1056],{"href":372}," checks it again when a subworkflow node starts a child Run, because a subworkflow published after the parent was validated can deepen a tree that is already running. Two implementations of one cap disagree once, and the disagreement is a workflow that publishes and then fails mid Run.",[987,4271,4273],{"id":4272},"the-branch-node","The branch node",[807,4275,4276,4278,4279,1121,4282,4284],{},[840,4277,3622],{}," shipped with Phase 1, and ",[840,4280,4281],{},"wait",[840,4283,3559],{}," with Phase 2. Their shapes are fixed here together, so the workflow model reads in one place.",[832,4286,4289],{"className":4287,"code":4288,"language":837,"meta":838},[835],"branch node\n  id\n  cases[]     condition  ->  child node\n  default     a child node, required; see the fourth rule below\n",[840,4290,4288],{"__ignoreMap":838},[832,4292,4295],{"className":4293,"code":4294,"language":837,"meta":838},[835],"# the email sequence chooses by what the reply said\ncases\n  - when  steps.classify.output.intent == 'interested'   ->  book_meeting\n  - when  steps.classify.output.intent == 'unsubscribe'  ->  suppress\ndefault                                                  ->  follow_up\n",[840,4296,4294],{"__ignoreMap":838},[807,4298,4299,4306,4307,4310,4311,1016],{},[816,4300,4301,4302,4305],{},"A condition is a ",[840,4303,4304],{},"ConditionEvaluator"," expression, and it is the same evaluator Policy and Triggers use."," There is no Python, SQL, CEL or Rego, and no model evaluated expression. A third caller of one evaluator is the whole reason it is shared, and it is why the evaluator sits at ",[840,4308,4309],{},"src\u002Fagentic\u002Fshared\u002Fconditions.py"," rather than inside any one plane. See ",[1054,4312,4313],{"href":287},"policy and governance",[807,4315,4316,4319],{},[816,4317,4318],{},"A condition is stored as data, not as text."," The line above is how the builder renders it to a person. What the definition holds is a small tree, and the evaluator walks it.",[832,4321,4323],{"className":3442,"code":4322,"language":3444,"meta":838,"style":838},"{\"op\": \"and\", \"of\": [\n  {\"op\": \"eq\", \"path\": \"steps.classify.output.intent\", \"value\": \"interested\"},\n  {\"op\": \"gt\", \"path\": \"steps.score.output.value\", \"value\": 70}\n]}\n",[840,4324,4325,4345,4378,4409],{"__ignoreMap":838},[1994,4326,4327,4329,4332,4334,4337,4339,4342],{"class":1996,"line":22},[1994,4328,3452],{"class":3451},[1994,4330,4331],{"class":3455},"\"op\"",[1994,4333,3470],{"class":3451},[1994,4335,4336],{"class":3473},"\"and\"",[1994,4338,1105],{"class":3451},[1994,4340,4341],{"class":3455},"\"of\"",[1994,4343,4344],{"class":3451},": [\n",[1994,4346,4347,4349,4351,4353,4356,4358,4361,4363,4366,4368,4371,4373,4376],{"class":1996,"line":32},[1994,4348,3879],{"class":3451},[1994,4350,4331],{"class":3455},[1994,4352,3470],{"class":3451},[1994,4354,4355],{"class":3473},"\"eq\"",[1994,4357,1105],{"class":3451},[1994,4359,4360],{"class":3455},"\"path\"",[1994,4362,3470],{"class":3451},[1994,4364,4365],{"class":3473},"\"steps.classify.output.intent\"",[1994,4367,1105],{"class":3451},[1994,4369,4370],{"class":3455},"\"value\"",[1994,4372,3470],{"class":3451},[1994,4374,4375],{"class":3473},"\"interested\"",[1994,4377,3831],{"class":3451},[1994,4379,4380,4382,4384,4386,4389,4391,4393,4395,4398,4400,4402,4404,4407],{"class":1996,"line":233},[1994,4381,3879],{"class":3451},[1994,4383,4331],{"class":3455},[1994,4385,3470],{"class":3451},[1994,4387,4388],{"class":3473},"\"gt\"",[1994,4390,1105],{"class":3451},[1994,4392,4360],{"class":3455},[1994,4394,3470],{"class":3451},[1994,4396,4397],{"class":3473},"\"steps.score.output.value\"",[1994,4399,1105],{"class":3451},[1994,4401,4370],{"class":3455},[1994,4403,3470],{"class":3451},[1994,4405,4406],{"class":3455},"70",[1994,4408,3534],{"class":3451},[1994,4410,4411],{"class":1996,"line":244},[1994,4412,4413],{"class":3451},"]}\n",[807,4415,4416],{},"Text would need a lexer and a parser, and a parser is the expression language this page already refuses. It also grows: the first request the grammar cannot serve arrives as a function, and the second as a cast. Data has no such edge, and a JSON schema validates it at publish with no code of its own.",[807,4418,4419,4420,4422],{},"The operator set is closed, and it is the one ",[1054,4421,4313],{"href":287}," names.",[832,4424,4427],{"className":4425,"code":4426,"language":837,"meta":838},[835],"eq  ne          equality\nlt  lte  gt  gte   numeric comparison, on numbers only\nin  not_in      membership in a declared list\nexists          the path resolves to a value that is not null\nand  or  not    composition\n",[840,4428,4426],{"__ignoreMap":838},[807,4430,4431,4434,4435,1121,4438,4440],{},[840,4432,4433],{},"path"," reads one fact through a dotted lookup. The three planes differ only in the map behind it: a workflow reads ",[840,4436,4437],{},"input.*",[840,4439,3709],{},", policy reads its principal, argument and target facts, and a trigger reads the event.",[807,4442,4443],{},"The fact map a branch reads is the workflow's own namespaces, and nothing else.",[832,4445,4447],{"className":4446,"code":4192,"language":837,"meta":838},[835],[840,4448,4192],{"__ignoreMap":838},[807,4450,4451,4452,4454],{},"It reads no CRM row, no policy rule and no clock. A branch that needs a business fact puts a ",[840,4453,3590],{}," node in front of it, so the fact is a step with a span behind it rather than a hidden read.",[807,4456,4457],{},"Four validation rules keep a branch honest.",[1149,4459,4460,4466,4472,4484],{},[1152,4461,4462,4465],{},[816,4463,4464],{},"Exactly one case is taken."," Cases are evaluated in order and the first match wins, so an unreachable case is a configuration error and not a run time surprise.",[1152,4467,4468,4471],{},[816,4469,4470],{},"Every condition resolves inside the allowed namespaces."," This is the same check every other reference gets.",[1152,4473,4474,4480,4481,4483],{},[816,4475,4476,4479],{},[840,4477,4478],{},"default"," is required when no case is provably total."," V1 proves nothing, so ",[840,4482,4478],{}," is required, always. A branch with no matching case and no default is a Run that stops with no result, and the author intended one of the children.",[1152,4485,4486,4489],{},[816,4487,4488],{},"A child is a node of this workflow."," A branch selects; it never starts something the workflow does not declare.",[807,4491,4492,4493,4495],{},"The taken child runs. The others do not, and their ",[840,4494,3282],{}," never exists. That is exactly the case the reference rule below covers.",[987,4497,4499],{"id":4498},"the-wait-node","The wait node",[811,4501,4502],{},[807,4503,4504,4511],{},[816,4505,4506,1121,4508,4510],{},[840,4507,4281],{},[840,4509,3559],{}," landed with Phase 2."," They need the approval plane and the Inngest wait helpers. Their shapes are fixed here so the workflow model is readable in one place, and the validator covers all eight node types.",[807,4513,1791,4514,4516],{},[840,4515,4281],{}," node holds for an event or for a delay. It is the only node an author configures with something the platform must correlate at run time, so its shape is fixed here.",[832,4518,4521],{"className":4519,"code":4520,"language":837,"meta":838},[835],"wait node\n  id\n  mode        event | delay\n  event_type  the platform event name, when mode is event\n  match       correlation keys, from input.* or steps.\u003Cid>.output.*\n  timeout_s   required whole seconds, at least one\n  on_timeout  continue | fail          default continue, mode = event only\n",[840,4522,4520],{"__ignoreMap":838},[832,4524,4527],{"className":4525,"code":4526,"language":837,"meta":838},[835],"# the email sequence waits for a reply on the thread it just sent\nmode        event\nevent_type  email.reply_received\nmatch       thread_id: steps.send.output.thread_id\ntimeout_s   259200                     # 3 days\non_timeout  continue                   # draft the follow up\n",[840,4528,4526],{"__ignoreMap":838},[807,4530,4531,1025,4534,4536,4537,4539],{},[816,4532,4533],{},"The timeout is whole seconds, and the key names the unit.",[840,4535,3302],{}," already writes ",[840,4538,3312],{},", so an author reads one spelling in both places, and the config carries no duration a reader must parse. The SDK also refuses a wait under one second and one that is not a whole number of seconds, so a fractional value is refused at publish rather than in a raise the Run cannot survive.",[807,4541,4542,4545,4546,2341,4549,4551,4552,4555],{},[840,4543,4544],{},"match"," becomes the Inngest wait expression over ",[840,4547,4548],{},"async.data.*",[1054,4550,266],{"href":261}," publishes every platform event under ",[840,4553,4554],{},"platform\u002F\u003Cevent_type>",". That page also owns the no-wait-index rule and the event type version rule.",[4557,4558,4560],"h4",{"id":4559},"what-the-expression-holds-and-what-the-router-owes","What the expression holds, and what the router owes",[832,4562,4565],{"className":4563,"code":4564,"language":837,"meta":838},[835],"async.data.organization_id == \"\u003Cthe Run's organization>\"\n  && async.data.data.\u003Cmatch key> == \u003Cthe resolved value, as JSON>\n",[840,4566,4564],{"__ignoreMap":838},[807,4568,921,4569,4575],{},[816,4570,4571,4572,4574],{},"The tenant clause is required, and the ",[840,4573,4544],{}," is not one."," An author can correlate on a low cardinality field — a stage, a status, a campaign name — and an expression holding only those matches another organization's event and wakes this Run. The approval wait carries the same clause for the same reason.",[807,4577,4578,1025,4581,4584,4585,4588,4589,4591,4592,4595,4596,4599],{},[816,4579,4580],{},"The two levels are the envelope.",[840,4582,4583],{},"EventRouter"," sends the whole ",[840,4586,4587],{},"PlatformEvent"," as the Inngest event data, so ",[840,4590,2298],{}," sits at the top of ",[840,4593,4594],{},"async.data"," and the business fields sit one level below it, under ",[840,4597,4598],{},"async.data.data",". A router that sends the business fields alone matches nothing, and every wait then times out reporting that nobody sent an event somebody did send.",[807,4601,4602,4605],{},[816,4603,4604],{},"Every value is placed as JSON, and never concatenated."," A correlation value is run time data, so a value holding a quote would otherwise close the literal and add a clause of its own.",[807,4607,921,4608,3700,4611,4613,4614,4617,4618,4620],{},[816,4609,4610],{},"A correlation is a scalar.",[840,4612,4544],{}," reference can resolve to a list or a mapping, and CEL equality over one compares the whole structure: a field the producer adds later makes the event stop matching, silently, on a wait that already ran for months. Validation cannot see it, because the value exists only at run time, so the node fails with ",[840,4615,4616],{},"wait_match_not_scalar",". A value that resolves to null fails earlier still, with ",[840,4619,3057],{}," — a correlation on null matches every event holding a null field.",[807,4622,4623],{},"Six validation rules keep a wait from leaking a Run:",[1149,4625,4626,4639,4645,4654,4674,4685],{},[1152,4627,4628,4631,4632,4635,4636,4638],{},[840,4629,4630],{},"mode = event"," needs an ",[840,4633,4634],{},"event_type",", at least one ",[840,4637,4544],{}," key, and a timeout.",[1152,4640,4641,4642,4644],{},"Every ",[840,4643,4544],{}," value must resolve inside the allowed namespaces. A correlation the workflow cannot produce never matches.",[1152,4646,4647,4650,4651,4653],{},[816,4648,4649],{},"A timeout is required, always."," A wait with no ceiling is a Run that never ends, and ",[840,4652,4008],{}," should be the backstop, not the mechanism.",[1152,4655,4656,4665,4666,4669,4670,4673],{},[816,4657,4658,4661,4662,3694],{},[840,4659,4660],{},"on_timeout"," is refused on a ",[840,4663,4664],{},"delay"," A delay ends on its timeout every time, so ",[840,4667,4668],{},"fail"," there reads \"always fail\" and ",[840,4671,4672],{},"continue"," states the default twice.",[1152,4675,4676,1025,4682,4684],{},[816,4677,4678,4661,4680,3694],{},[840,4679,3381],{},[840,4681,4281],{},[840,4683,4660],{}," already answers what happens when the wait does not resolve, and two knobs over one question disagree the first time an author sets both.",[1152,4686,4687,3700,4693,4695,4696,4698,4699,4701],{},[816,4688,4689,4690,4692],{},"At most one child subtree of a ",[840,4691,3619],{}," node parks.",[840,4694,4281],{}," may sit in one child subtree, and no other subtree of that container may hold a ",[840,4697,4281],{}," or an ",[840,4700,3559],{},". The reason is below, and it is the one rule of the six that is about the Run row rather than the node.",[4557,4703,4705],{"id":4704},"why-two-waits-under-one-parallel-are-refused","Why two waits under one parallel are refused",[807,4707,4708,4711,4712,4715,4716,4719,4720,4723,4724,4727],{},[840,4709,4710],{},"resume()"," keeps a Run asleep by re-reading the pending rows in ",[840,4713,4714],{},"agent.approvals",". That is what lets ",[1054,4717,4718],{"href":372},"an approval inside a parallel node"," work: two branch approvals are two rows, the first answer re-aims ",[840,4721,4722],{},"waiting_ref_id"," at the second, and the Run stays ",[840,4725,4726],{},"waiting"," until both are answered.",[807,4729,4730,4731,4733,4734,4736,4737,4739],{},"An event wait files no row of its own. That is the design ",[1054,4732,266],{"href":261}," states, and a provider job row does not change it: ",[840,4735,4710],{}," counts pending approvals and counts no provider job. So ",[840,4738,4710],{}," counts zero outstanding waits and wakes the Run on the first event, while the second branch is still parked.",[807,4741,921,4742,4745,4746,1121,4749,4752,4753,4755,4756,4759],{},[816,4743,4744],{},"That is not a wrong label. It ends the Run."," The reaper reads ",[840,4747,4748],{},"queued",[840,4750,4751],{},"running"," and nothing else, and it fails a ",[840,4754,4751],{}," Run that is quiet past the abandon window with ",[840,4757,4758],{},"worker_lost",". A parked branch writes no heartbeat, so a Run woken early is reaped inside the abandon window, and the wait a person is owed never resolves.",[807,4761,4762,4763,4765,4766,4768,4769,4772],{},"The rule is therefore structural and checked at publish: walk each ",[840,4764,3619],{}," node, and refuse it when a ",[840,4767,4281],{}," appears in more than one of its child subtrees. Two waits ",[816,4770,4771],{},"in one"," subtree are sequential, and they are allowed.",[807,4774,921,4775,4781,4782,4784,4785,4787,4788,4790,4791,4793,4794,4796,4797,1016],{},[816,4776,4777,4778,4780],{},"It reaches an ",[840,4779,3559],{}," in a sibling subtree too."," A container holding one ",[840,4783,4281],{}," and one ",[840,4786,3559],{}," has one wait in one subtree and passes the count. The person answers the approval, ",[840,4789,4710],{}," counts zero rows, and the parked branch is reaped exactly as above. So the refusal is over both node types: a ",[840,4792,3619],{}," whose subtrees hold a ",[840,4795,4281],{}," holds no other parking node beside it. See ",[1054,4798,4800],{"href":4799},"#a-wait-and-an-approval-never-share-one-parallel","a wait and an approval never share one parallel",[807,4802,4803,4806,4807,4809,4810,4812,4813,4815],{},[816,4804,4805],{},"It costs the author nothing, because the shape that needs many concurrent waits already has one."," A batch of people is a ",[840,4808,3619],{}," of ",[840,4811,1641],{}," nodes, one child Run each, and a child Run owns its own row and its own ",[840,4814,4726],{}," status. The email sequence is written that way for other reasons already.",[807,4817,4818,4819,4821,4822,4824],{},"The restriction is on the Run row and not on the node, so it lifts the day a wait is countable. A container of ",[840,4820,3559],{}," nodes alone is unrestricted for exactly that reason: every one of them is a row ",[840,4823,4710],{}," can count.",[4557,4826,4828],{"id":4827},"the-timeout-is-clamped-to-the-run-deadline","The timeout is clamped to the Run deadline",[807,4830,4831,4834,4835,4838,4839,4842],{},[840,4832,4833],{},"ApprovalService.create()"," caps ",[840,4836,4837],{},"expires_at"," at the Run's own deadline, and a wait node takes the same cap: the timeout handed to Inngest is ",[840,4840,4841],{},"min(timeout_s, run_deadline() - now())",", floored at one second.",[807,4844,921,4845,4848,4849,4851,4852,4854,4855,4857,4858,1016],{},[816,4846,4847],{},"Without the clamp nothing ends the Run."," A three day wait on a Run whose ",[840,4850,3312],{}," is one day parks past the ceiling. The reaper never reads a ",[840,4853,4726],{}," row, the wall clock gate runs before a node and not during one, and the Inngest timeout is the only writer left — three days after the Run should have stopped. A Run whose remainder is already spent settles the node at once. An event wait settles through ",[840,4856,4660],{},", and a delay wait ends and the walk goes on, because a delay declares no ",[840,4859,4660],{},[4557,4861,4863],{"id":4862},"a-wait-catches-only-what-arrives-after-it-registers","A wait catches only what arrives after it registers",[807,4865,4866,4867,4870],{},"The ",[1054,4868,4869],{"href":372},"segment approval wait"," reads the approval row once before it registers the wait, and once again on the timeout. Those two reads close the gap between the row being written and the waiter existing, because a person can answer inside it.",[807,4872,4873,4874,4877],{},"Most event waits have no row to read. The correlation comes from a step that already committed — ",[840,4875,4876],{},"steps.send.output.thread_id"," is written before the wait node starts — so a reply that lands in that gap reaches no waiter and Inngest drops it.",[807,4879,4880,4883],{},[816,4881,4882],{},"A wait whose correlation names a durable row does read it, twice."," The check\nstep runs before the wait registers and the timeout step runs after it ends, and\nboth read the row the correlation names. This is the pair the approval wait\nalready uses, over a second row type.",[832,4885,4888],{"className":4886,"code":4887,"language":837,"meta":838},[835],"step  \u003Cid>.mark      plan the hold, mark the run `waiting`, open the span\nstep  \u003Cid>.check     read the row; a terminal state skips the wait outright\n      \u003Cid>           wait_for_event\nstep  \u003Cid>.expire    on the timeout: read the row again; a terminal state\n                     settles the node as answered\nstep  \u003Cid>.resume    resume(), close the span\n",[840,4889,4887],{"__ignoreMap":838},[807,4891,921,4892,4899,4900,976,4903,4906,4907,4910],{},[816,4893,4894,4895,4898],{},"The second read is its own step, and ",[840,4896,4897],{},"\u003Cid>.resume"," is not it."," The\napproval wait pairs ",[840,4901,4902],{},"wait.check.n",[840,4904,4905],{},"wait.expire.n",", and both are reads. A\ntimeout is not proof that the job did not finish: the event can be lost between\nthe emit and the waiter. ",[840,4908,4909],{},"\u003Cid>.expire"," runs on the timeout alone, so a wait the\nevent answered pays for no extra read.",[807,4912,4913,4916,4917,4920,4921,4924,4925,4928,4929,4931],{},[816,4914,4915],{},"The node declares nothing extra, and the event type is what selects the\nreader."," A small map keyed on the platform event type answers which row a correlation\nnames. It holds one entry. ",[840,4918,4919],{},"agentic.provider_job.completed.v1"," reads\n",[840,4922,4923],{},"agent.provider_jobs"," by the ",[840,4926,4927],{},"job_id"," the ",[840,4930,4544],{}," already carries.",[832,4933,4936],{"className":4934,"code":4935,"language":837,"meta":838},[835],"mode        event\nevent_type  agentic.provider_job.completed.v1\nmatch       job_id: steps.submit.output.job_id\ntimeout_s   3600\non_timeout  fail\n",[840,4937,4935],{"__ignoreMap":838},[807,4939,4940],{},"A second field naming the same value is the same reference written twice. Two\nspellings of one fact disagree the first time an author edits one. An\nevent type with no entry in the map registers its wait with no check step, which\nis every wait shipped before this. So no published definition changes and the\nvalidator gains no rule.",[807,4942,4943,4944,1016],{},"See ",[1054,4945,4946],{"href":190},"asynchronous provider jobs",[807,4948,4949,4952,4953,4956,4957,4959],{},[816,4950,4951],{},"A wait on an event that names no row is still open, and the timeout is its\nbackstop."," An email reply is that case. The answer lives in an event that is already gone.\n",[840,4954,4955],{},"on_timeout = continue"," makes a missed reply cost a follow-up email, and not a\nstalled Run. An author who cannot tolerate that models the\nreply as a poll over a ",[840,4958,3590],{}," node instead.",[4557,4961,4963],{"id":4962},"a-wait-produces-no-output","A wait produces no output",[807,4965,4966,4968,4969,1105,4971,1121,4973,4975,4976,4978,4979,4982,4983,4985],{},[840,4967,3282],{}," exists for ",[840,4970,3590],{},[840,4972,3076],{},[840,4974,1641],{},". A ",[840,4977,4281],{}," joins the containers: a node that reads ",[840,4980,4981],{},"steps.\u003Cwait>.output"," is refused at publish, with the same message a ",[840,4984,3616],{}," earns.",[807,4987,4988,4991,4992,4994,4995,4997],{},[816,4989,4990],{},"The event body is deliberately not a fact."," A resumed workflow reads the reply through a ",[840,4993,3590],{}," node, which puts the read behind a span and inside the idempotency journal. Handed the event payload instead, the workflow would carry untrusted inbound content as a workflow fact with no span behind it, and a replay would read a payload that no longer matches the row. It is the same rule a ",[840,4996,3622],{}," obeys: a business fact is a step, never a hidden read.",[987,4999,5001],{"id":5000},"the-approval-node","The approval node",[807,5003,5004,5005,5007,5008,5010],{},"An ",[840,5006,3559],{}," node holds for a person. Like the ",[840,5009,4281],{}," node it needs one thing the platform must own at run time, so its shape is fixed here too.",[832,5012,5015],{"className":5013,"code":5014,"language":837,"meta":838},[835],"approval node\n  id\n  action        what a person is authorizing, one line\n  input         the facts a person judges, the same {\"$ref\": ...} mapping every node carries\n  summary       a static line a person reads, no interpolation\n  ttl_s         required whole seconds, at least one\n",[840,5016,5014],{"__ignoreMap":838},[832,5018,5021],{"className":5019,"code":5020,"language":837,"meta":838},[835],"# the email sequence stops before it sends to a new account\naction    send the outreach email\ninput     company: {\"$ref\": \"steps.classify.output.company_name\"}\n          subject: {\"$ref\": \"steps.draft.output.subject\"}\nsummary   Review the draft before it sends.\nttl_s     172800                       # 2 days\n",[840,5022,5020],{"__ignoreMap":838},[807,5024,5025,5028,5029,1121,5031,5033],{},[816,5026,5027],{},"The TTL is whole seconds, and the key names the unit."," It is the spelling ",[840,5030,3982],{},[840,5032,3312],{}," already use, so an author reads one unit in every duration the platform takes. The SDK refuses a wait under one second and one that is not whole, so a fractional value is refused at publish rather than in a raise the Run cannot survive. The ceiling is the wait node's ceiling, 30 days, for the same reason: a longer hold outlives every Run that could carry it.",[807,5035,5036,5042,5043,5046,5047,5049,5050,5052],{},[816,5037,1791,5038,5041],{},[840,5039,5040],{},"ttl_s"," is required, for the same reason a wait timeout is."," An approval raised by policy takes its expiry from the matching rule's ",[840,5044,5045],{},"approval_ttl",". A node approval has no rule behind it, so nothing would set ",[840,5048,4837],{},", and the Inngest wait timeout is computed from ",[840,5051,4837],{},". A node with no TTL is therefore a Run that waits for ever, and validation refuses it.",[807,5054,5055,5065,5066,1121,5068,5070,5071,5073,5074,1105,5077,5080,5081,5084],{},[816,5056,5057,5060,5061,5064],{},[840,5058,5059],{},"summary"," is a static line, and ",[840,5062,5063],{},"input"," carries the facts."," The declared shape said a template over ",[840,5067,4437],{},[840,5069,3709],{},". This page refuses an expression language for a condition and refuses a template for a reference, and a template in a summary is that same refused thing under a third name. It also has nowhere to go: ",[840,5072,4714],{}," stores ",[840,5075,5076],{},"preview",[840,5078,5079],{},"proposed_arguments"," and a NOT NULL ",[840,5082,5083],{},"arguments_hash",", and a rendered string fills one of the three.",[807,5086,5087,5088,5090,5091,5093],{},"So the node takes an ",[840,5089,5063],{}," mapping, resolved by the reference resolver every other node uses, and one static ",[840,5092,5059],{}," string. The row is then written the way an admission row is written.",[843,5095,5096,5108],{},[846,5097,5098],{},[849,5099,5100,5103],{},[852,5101,5102],{},"Node field",[852,5104,4866,5105,5107],{},[840,5106,4714],{}," column",[859,5109,5110,5121,5131,5142],{},[849,5111,5112,5117],{},[864,5113,5114],{},[840,5115,5116],{},"action",[864,5118,5119],{},[840,5120,5116],{},[849,5122,5123,5127],{},[864,5124,5125],{},[840,5126,5059],{},[864,5128,5129],{},[840,5130,5076],{},[849,5132,5133,5138],{},[864,5134,5135,5137],{},[840,5136,5063],{},", resolved",[864,5139,5140],{},[840,5141,5079],{},[849,5143,5144,5149],{},[864,5145,5146,5148],{},[840,5147,5063],{},", resolved and hashed",[864,5150,5151],{},[840,5152,5083],{},[807,5154,5155,5158,5159,5162,5163,5165,5166,1016],{},[816,5156,5157],{},"A node approval carries a claim, and the memoized step is not enough."," Inngest memoizes a step that ",[816,5160,5161],{},"returned",". A raise step that dies after its insert commits is retried whole, and an unclaimed insert then files a second pending row: a person sees one gate twice, and the walk parks on the second row while ",[840,5164,4722],{}," names the first. The person answers the card they are shown, and the node still ends ",[840,5167,5168],{},"approval_expired",[807,5170,5171,5174,5175,5178,5179,5181,5182,5185,5186,5188],{},[840,5172,5173],{},"uq_approvals_run_id_idempotency_key"," is partial on ",[840,5176,5177],{},"idempotency_key IS NOT NULL AND status = 'pending'",", so the key is what brings the row under that guard. The node writes ",[840,5180,3555],{},", which is the segment journal shape, and a workflow node id ",[816,5183,5184],{},"is"," a step path. ",[840,5187,4833],{}," then reads the first row back, and the retried step parks on the card a person already has.",[807,5190,5191],{},"The column still means \"the claim an approved call takes\" for the two policy checkpoints. A node takes no such claim, because it runs no call. It takes this one to be filed once.",[807,5193,5194,5197,5198,5200,5201,5204,5205,1137,5208,5210],{},[816,5195,5196],{},"An approval node produces no output."," It joins ",[840,5199,4281],{}," and the containers: a node reading ",[840,5202,5203],{},"steps.\u003Capproval>.output"," is refused at publish. A person's decision is ",[840,5206,5207],{},"yes",[840,5209,1685],{},", and both are already the node outcome.",[807,5212,5213],{},"Four validation rules keep an approval node honest.",[1149,5215,5216,5224,5232,5240],{},[1152,5217,5218,5223],{},[816,5219,1791,5220,5222],{},[840,5221,5040],{}," is required",", whole, at least one, and at most the 30 day cap.",[1152,5225,5226,5231],{},[816,5227,4641,5228,5230],{},[840,5229,5063],{}," reference resolves inside the allowed namespaces",", which is the check every other node input gets.",[1152,5233,5234,5239],{},[816,5235,5236,5238],{},[840,5237,3381],{}," is refused."," The flag says the author expects this step to fail sometimes, and a tolerated gate lets the workflow proceed on a decision a person refused. That is the one thing the node exists to prevent.",[1152,5241,5242,5253],{},[816,5243,1791,5244,5246,5247,5249,5250,5252],{},[840,5245,3619],{}," node that holds a ",[840,5248,4281],{}," holds no ",[840,5251,3559],{}," in another child subtree."," The reason is the wait node's own rule, read from the other side, and it is below.",[4557,5255,5257],{"id":5256},"a-wait-and-an-approval-never-share-one-parallel","A wait and an approval never share one parallel",[807,5259,5260,5263,5264,5266,5267,5269,5270,5272],{},[1054,5261,4705],{"href":5262},"#why-two-waits-under-one-parallel-are-refused"," states the mechanism: ",[840,5265,4710],{}," keeps a Run asleep by counting the pending rows in ",[840,5268,4714],{},", and a ",[840,5271,4281],{}," node files no row. Two approvals are therefore safe, and two waits are not.",[807,5274,921,5275,3700,5278,5280,5281,5283,5284,5286],{},[816,5276,5277],{},"One of each is not safe either, and the wait rule alone does not catch it.",[840,5279,3619],{}," holding an ",[840,5282,3559],{}," in one subtree and a ",[840,5285,4281],{}," in another has one wait in one subtree, so it passes that rule.",[832,5288,5291],{"className":5289,"code":5290,"language":837,"meta":838},[835],"parallel\n  ├─ branch A   approval      a person answers it\n  └─ branch B   wait(event)   still parked\nresume() counts 0 pending rows, so the Run reads `running`\nbranch B writes no heartbeat, and the reaper fails it `worker_lost`\n",[840,5292,5290],{"__ignoreMap":838},[807,5294,5295,5296,5305,5306,4824],{},"That is the same ending the two-wait case has, reached from the other direction. The rule is therefore one rule over both node types, checked at publish: ",[816,5297,5298,5299,5301,5302,5304],{},"a ",[840,5300,3619],{}," node whose child subtrees hold a ",[840,5303,4281],{}," holds no other parking node in any other subtree."," A container of approvals alone stays unrestricted, because every one of them is a row ",[840,5307,4710],{},[4557,5309,5311],{"id":5310},"what-a-decision-does-to-the-node","What a decision does to the node",[807,5313,5314],{},"Five endings, and one of them runs the next node.",[843,5316,5317,5327],{},[846,5318,5319],{},[849,5320,5321,5324],{},[852,5322,5323],{},"The row, when the wait ends",[852,5325,5326],{},"The node",[859,5328,5329,5341,5356,5369,5384],{},[849,5330,5331,5336],{},[864,5332,5333],{},[840,5334,5335],{},"approved",[864,5337,5338,5340],{},[840,5339,2472],{},", and the walk goes on",[849,5342,5343,5348],{},[864,5344,5345],{},[840,5346,5347],{},"rejected",[864,5349,5350,1105,5353],{},[840,5351,5352],{},"failed",[840,5354,5355],{},"approval_rejected",[849,5357,5358,5363],{},[864,5359,5360,5361],{},"nobody answered by ",[840,5362,4837],{},[864,5364,5365,1105,5367],{},[840,5366,5352],{},[840,5368,5168],{},[849,5370,5371,5376],{},[864,5372,5373],{},[840,5374,5375],{},"cancelled",[864,5377,5378,1037,5380,5383],{},[840,5379,5375],{},[840,5381,5382],{},"RunManager.cancel()"," already wrote the Run",[849,5385,5386,5389],{},[864,5387,5388],{},"the row is gone",[864,5390,5391,1105,5393],{},[840,5392,5352],{},[840,5394,5395],{},"approval_row_missing",[807,5397,5398,5404],{},[816,5399,5400,5401,5403],{},"A rejection fails the node, and ",[840,5402,3381],{}," cannot tolerate it."," A failed branch does not cancel its siblings, so the container settles at its last branch and the Run fails after it. That is the ordinary failed-branch path, and a gate needs no second one.",[807,5406,5407,1025,5410,5413,5414,5416,5417,5419,5420,5422,5423,5426],{},[816,5408,5409],{},"No authority is checked at the decision, and none is granted by it.",[840,5411,5412],{},"ApprovalService.authorizes()"," tests the approver against the ",[840,5415,5116],{}," as a scope, and a node ",[840,5418,5116],{}," is an author's own words rather than a scope name. Nothing calls it here, because the node executes no call: it releases the walk, and every ",[840,5421,3590],{}," node after it runs its own checkpoints against the Run's principal. So a node approval is an acknowledgement gate. ",[1054,5424,5425],{"href":344},"Human review"," already states the V1 rule this follows: visibility alone decides who may answer.",[807,5428,5429,5434,5435,5438,5439,5441],{},[816,5430,5431,5433],{},[840,5432,4722],{}," names the oldest unresolved approval, so the raise reads it rather than writing its own id."," Two branches raise in two Inngest steps, in either order, and each one would otherwise leave the field naming itself. The raise marks the Run from ",[840,5436,5437],{},"oldest_pending()",", which is the read ",[840,5440,4710],{}," already makes, and the field is then right whichever step commits last.",[807,5443,5444,5445,5447,5448,3094,5451,1016],{},"The node writes the same ",[840,5446,4714],{}," row the two policy checkpoints write, with ",[840,5449,5450],{},"raised_by = node",[1054,5452,5453],{"href":344},"human review inbox",[987,5455,5457],{"id":5456},"declared-scopes","Declared scopes",[807,5459,5460],{},"A workflow publishes the set of tool scopes it needs. Policy admission reads it, because a workflow node cannot ask the model for another way when a scope is missing.",[832,5462,5465],{"className":5463,"code":5464,"language":837,"meta":838},[835],"required_scopes = the tool names on every tool node\n                ∪ the required scopes of every referenced subworkflow\n\ndeclared_scopes = required_scopes\n                ∪ the tool names of every referenced agent\n                ∪ the declared scopes of every referenced subworkflow\n",[840,5466,5464],{"__ignoreMap":838},[807,5468,5469],{},[816,5470,5471],{},"Two sets, because admission and the catalogue ask different questions.",[843,5473,5474,5486],{},[846,5475,5476],{},[849,5477,5478,5481,5484],{},[852,5479,5480],{},"Set",[852,5482,5483],{},"Answers",[852,5485,4046],{},[859,5487,5488,5500],{},[849,5489,5490,5494,5497],{},[864,5491,5492],{},[840,5493,940],{},[864,5495,5496],{},"what fails if the principal lacks it",[864,5498,5499],{},"policy admission",[849,5501,5502,5507,5510],{},[864,5503,5504],{},[840,5505,5506],{},"declared_scopes",[864,5508,5509],{},"everything this workflow could touch",[864,5511,5512],{},"the connections UI, and an admin reviewing a fork",[807,5514,5515],{},"An agent node contributes to the second and not the first. A tool node cannot ask the model for another way, so a missing scope is fatal. An agent adapts wherever it runs, and refusing the workflow would deny a principal who can run that same agent alone.",[807,5517,5518,1025,5524,5527,5528,5530,5531,5533,5534,5536,5537,1016],{},[816,5519,5520,5521,5523],{},"Both sets reach through a subworkflow, and ",[840,5522,5506],{}," has to.",[840,5525,5526],{},"PrincipalFactory"," mints a workflow Run's grant from ",[840,5529,5506],{},", and a child Run's grant is the intersection of its own definition with its parent's. So a tool two levels down that the top-level set did not name is denied inside a Run that admission said was safe. A workflow that declares no ",[840,5532,2482],{}," of its own is the reason the grant reads this field and not that one: read ",[840,5535,2482],{}," for a workflow and the grant is empty, which the invoker reads as ",[5538,5539,5540],"em",{},"deny everything",[807,5542,5543,5544,1016],{},"It is computed at publish and stored on ",[840,5545,884],{},[807,5547,5548,5554,5555,5557,5558,5560,5561,5563],{},[816,5549,5550,5551,5553],{},"Both sets are derived, so ",[840,5552,1934],{}," removes them from the candidate before it validates it."," The candidate is ",[840,5556,975],{},", and a fork copies a ",[840,5559,884],{}," that already carries them, so an author can hand a stale pair straight back. ",[840,5562,1934],{}," therefore drops the two keys, validates, recomputes them from the config it is about to write, and writes the recomputed pair. An author who hand-writes either key changes nothing.",[807,5565,5566,5574,5575,5577],{},[816,5567,5568,5569,1121,5571,5573],{},"And the two keys are why ",[840,5570,975],{},[840,5572,884],{}," never compare equal."," The surfaces page reads that comparison to show unpublished work, so a definition published a second ago would show as edited for ever. One exported constant names the derived keys, and every comparison drops them first. ",[840,5576,1822],{}," needs no such rule: it is a column and not a config key.",[807,5579,5580,5583,5584,5587,5588,5590,5591,5593,5594,5596,5597,5599,5600,5590,5602,5604,5605,1236,5610,5612,5613,5615],{},[816,5581,5582],{},"Revalidation validates a referrer. It never rewrites one, so the stored set does go stale, and one half of it is load-bearing for policy."," Publishing agent ",[840,5585,5586],{},"A"," without a tool leaves workflow ",[840,5589,2659],{},"'s stored ",[840,5592,5506],{}," still naming it. Publishing subworkflow ",[840,5595,2651],{}," with a new ",[840,5598,3590],{}," node leaves ",[840,5601,2659],{},[840,5603,940],{}," missing it — and ",[816,5606,5607,5608],{},"policy admission reads ",[840,5609,940],{},[840,5611,2659],{}," is admitted for a principal that lacks a scope ",[840,5614,2659],{}," now needs, and the node fails deep inside a Run that admission said was safe.",[807,5617,5618,5619,5627],{},"So the recomputation is a validation rule, not a side effect: ",[816,5620,5621,5622,1137,5624,5626],{},"a referrer whose stored ",[840,5623,940],{},[840,5625,5506],{}," differs from the set recomputed against the candidate fails validation, and the publish is refused naming it."," The admin republishes the referrer, which is the write that stores the corrected set. This keeps one property the page depends on everywhere else — publish establishes validity, and no Run recomputes it — and it costs one comparison inside a validation pass that already loads everything the comparison needs.",[807,5629,5630],{},"The regression rule above applies here too. A referrer whose stored set is already wrong against its own current config was broken before this publish, and it does not refuse one.",[827,5632,5633],{"id":329},"Validation",[807,5635,5636],{},"One validator entry point dispatches the kind specific checks internally.",[843,5638,5639,5649],{},[846,5640,5641],{},[849,5642,5643,5646],{},[852,5644,5645],{},"Kind",[852,5647,5648],{},"Checks",[859,5650,5651,5663,5681,5689],{},[849,5652,5653,5656],{},[864,5654,5655],{},"All",[864,5657,5658,5659,5662],{},"Schema, ownership, active references ",[816,5660,5661],{},"in the caller's own organization",", publish authority, frozen size",[849,5664,5665,5668],{},[864,5666,5667],{},"Agent",[864,5669,5670,5671,1105,5674,5676,5677,5680],{},"Tool and skill existence, ",[816,5672,5673],{},"each referenced skill's tools are a subset of this agent's",[840,5675,3242],{}," resolves, ",[840,5678,5679],{},"ContextPolicy"," sources, prompt and budget limits",[849,5682,5683,5686],{},[864,5684,5685],{},"Skill",[864,5687,5688],{},"Tool existence, size",[849,5690,5691,5694],{},[864,5692,5693],{},"Workflow",[864,5695,5696],{},"Unique node IDs across the whole workflow, references, target existence, type compatibility, no cycles, depth cap, branch shape, fan out budget, declared scopes",[807,5698,5699,1025,5702,5705,5706,5709,5710,5713,5714,5716,5717,5720,5721,5724,5725,5727],{},[816,5700,5701],{},"Four of these checks already have code, and the validator calls it rather than writing a second copy.",[840,5703,5704],{},"workflow_depth()"," in ",[840,5707,5708],{},"shared\u002F"," walks the cycle guard and the depth in one function. ",[840,5711,5712],{},"parse_workflow()"," reads the node tree and holds the unique ids, the reference namespaces, the container-output refusal, the branch ",[840,5715,4478],{}," and the container nesting cap. ",[840,5718,5719],{},"validate_condition()"," reads a branch condition. ",[840,5722,5723],{},"ToolRegistry"," answers tool existence. Two implementations of one rule disagree once, and the disagreement is a definition that publishes and then fails mid Run. So the parser and the graph walk live in ",[840,5726,5708],{},", where the validator and the executor read one copy.",[807,5729,5730],{},"Validation is deterministic and model free. There is no validator plugin architecture in V1.",[807,5732,5733,5736,5737,5739],{},[816,5734,5735],{},"The skill validator does not check capability widening."," It has no agent in hand. That check is on the agent, and the referrer rule reaches it from the other side. A table that lists it under ",[840,5738,5685],{}," describes a check nobody can implement.",[807,5741,5742,1105,5745,5748,5749,1016],{},[816,5743,5744],{},"Publish authority is one scope",[840,5746,5747],{},"definition.publish",", and it is granted by the role mapping exactly as a tool name is. The builder chat reaches publishing through a tool of that name, and the API route checks the same scope, so the two paths cannot diverge. See ",[1054,5750,4313],{"href":287},[807,5752,921,5753,5756,5757,5759],{},[816,5754,5755],{},"The scope gates every write to a definition, and not the publish alone."," A definition is shared configuration, so a member who could rewrite an agent's draft could change what every colleague's next publish makes runnable. ",[840,5758,1984],{}," reads one answer through a seam, which is what stops the surface growing a second authorization path.",[807,5761,5762,5763,1121,5765,5767],{},"Phase 1 built the agent, skill and workflow validators, and that workflow validator covered six node types. Phase 2 added the ",[840,5764,4281],{},[840,5766,3559],{}," rules above, so it now covers all eight.",[807,5769,5770,5773,5774,1137,5776,5778,5779,5781,5782,5784],{},[816,5771,5772],{},"The fan out budget check has nothing to measure before Phase 4."," The parser refuses any ",[840,5775,3733],{},[840,5777,3737],{}," above one. Phase 4 accepts a wide ",[840,5780,3590],{}," step and keeps the width-one rule for an ",[840,5783,3076],{}," step.",[807,5786,5787,5792],{},[816,5788,5789,5791],{},[840,5790,5679],{}," source names resolve in the process context registry."," Publish checks every declared source, including a disabled source. It also checks each source's options against the strict schema that the registry accepted. An unknown source, an unknown option, a wrong option type, or one source name used twice fails publish. The Phase 3 process registry is empty until a source lands, so the production deploy still accepts only an empty source list.",[827,5794,5796],{"id":5795},"run-snapshot","Run snapshot",[807,5798,5799,5800,5802],{},"Before dispatch, ",[840,5801,3348],{}," freezes the effective execution configuration.",[832,5804,5807],{"className":5805,"code":5806,"language":837,"meta":838},[835],"Frozen in the Run snapshot\n  published definition config\n  rendered skill text\n  model facing tool contracts\n  ContextPolicy\n  model configuration\n  ceilings            a child takes max_cost_cents from the root\n\nFrozen in the Run, beside the snapshot\n  principal grant     agent.runs.principal, its own column\n\nRead live at each checkpoint\n  tool enabled or revoked state\n  policy rules\n  the actor's current rights, which can only narrow the frozen grant\n  credentials and connection status\n  handler implementation\n  business data and the fresh ContextBrief\n",[840,5808,5806],{"__ignoreMap":838},[807,5810,5811],{},"Skills are rendered here, once. Execution reads the rendered text.",[987,5813,5815],{"id":5814},"what-the-snapshot-must-hold","What the snapshot must hold",[807,5817,5818,5821,5822,5824,5825,5827,5828,5831],{},[816,5819,5820],{},"The executor reads fixed keys, so the builder writes those keys and no others."," A snapshot that names ",[840,5823,4008],{}," where the reader looks for ",[840,5826,3312],{}," fails every Run of that definition with ",[840,5829,5830],{},"invalid_snapshot",", and the definition validated cleanly. The two shapes are one contract.",[832,5833,5836],{"className":5834,"code":5835,"language":837,"meta":838},[835],"an agent run                    a workflow run\n  model                           root\n    provider, model               ceilings\n    temperature?                    the same five fields\n    max_output_tokens?\n  instructions                  read by parse_workflow() and _read_ceilings\n    the definition text plus\n    the rendered skills\n  tools[]\n    the model facing contracts\n  context_policy\n  ceilings\n    max_segments\n    max_agent_turns\n    max_tool_calls\n    max_run_duration_s          seconds\n    max_cost_cents\n",[840,5837,5835],{"__ignoreMap":838},[807,5839,5840,5846,5847,5850],{},[816,5841,5842,5843,5845],{},"A workflow definition declares ",[840,5844,3302],{}," too."," A workflow run is a run, and its executor reads ",[840,5848,5849],{},"snapshot[\"ceilings\"]"," exactly as the agent one does. One shape rather than two: a second ceiling type would be a second thing for the builder to fill in and a second thing for the meter to read.",[807,5852,5853,5856],{},[816,5854,5855],{},"A skill renders as a titled block under the definition text",", in the order the agent names its skills. The order is part of the prompt, so it is the declared order and never a set: a republish that changed nothing an author wrote must build the same string, or a replayed segment changes the arguments hash of every tool call below it.",[832,5858,5861],{"className":5859,"code":5860,"language":837,"meta":838},[835],"\u003Cthe agent instructions>\n\n## Skill: \u003Cname>\n\u003Cdescription>\n\n\u003Cinstructions>\n",[840,5862,5860],{"__ignoreMap":838},[807,5864,5865,5868],{},[816,5866,5867],{},"The builder refuses rather than drops."," Every reason it can refuse was checked at publish, so a refusal at the freeze names a deploy or a seed that moved out from under a published definition: a tool the registry no longer holds, a skill that no longer resolves, or a config with no ceilings. Each one dropped instead would be a truncation of the kind this page forbids -- an agent that silently lost a tool it was published with, and no reader able to tell.",[807,5870,5871,5879,5880,5883],{},[816,5872,5873,5875,5876,5878],{},[840,5874,194],{}," freezes the agent's own ",[840,5877,2482],{}," and no skill's."," A skill never widens an agent's tool set, so adding a referenced skill's tools here would grant at the freeze what the agent validator refuses at the publish. ",[840,5881,5882],{},"agent.runs.principal"," reads the same field, and the two must name one list.",[807,5885,5886,1105,5891,5893],{},[816,5887,5888,5890],{},[840,5889,3302],{}," uses the snapshot's own key names",[840,5892,3312],{}," included, so the builder copies the mapping and converts nothing. An earlier pass had the definition name a duration and the snapshot a count of seconds, which put one unit conversion between two JSON documents and no reader on either side of it. A definition holds JSON, and a person authors it through a builder, so nothing is gained by a second spelling.",[807,5895,5896,5902],{},[816,5897,5898,5901],{},[840,5899,5900],{},"freeze()"," takes the definition and nothing else."," The principal is not in the snapshot, so a builder that took one would never read it, and publish must build the same snapshot with no principal in hand. One signature therefore serves both callers: publish measures what it is about to make runnable, and the run freezes it. Two functions computing one value disagree the first time either changes, and the disagreement is a definition that passes the size check and then fails every start.",[807,5904,5905,1025,5908,2811,5911,5914,5915,5918],{},[816,5906,5907],{},"The principal grant is frozen, and it is not in the snapshot.",[840,5909,5910],{},"agent.runs",[840,5912,5913],{},"principal"," as its own column, and ",[840,5916,5917],{},"PrincipalFactory.for_run()"," is its one writer. Listing it inside the snapshot as well would give one fact two homes, and the two disagree the first time either writer changes. The snapshot holds execution configuration; the column holds authority.",[807,5920,5921],{},"The snapshot is why V1 needs no revision history to keep in flight work stable. It is also why an emergency tool revocation or policy change still takes effect at once.",[987,5923,5925],{"id":5924},"snapshot-storage","Snapshot storage",[807,5927,5928,5931],{},[816,5929,5930],{},"V1 stores the snapshot inline, on the Run row."," This is the decision, not a placeholder. One row holds everything a fresh worker needs, and a resume reads one row.",[807,5933,5934],{},"Three guards keep it affordable.",[1307,5936,5937,5950,5959],{},[1152,5938,5939,5945,5946,5949],{},[816,5940,5941,5942,1016],{},"A Run list query never selects ",[840,5943,5944],{},"snapshot"," The repository exposes a narrow list projection and a separate ",[840,5947,5948],{},"get_snapshot()",". A list endpoint that selects the whole row pulls megabytes of frozen tool contracts to render a status column.",[1152,5951,5952,5955,5956,1016],{},[816,5953,5954],{},"A size check at publish."," The frozen size is deterministic, so validation computes it and refuses a definition whose complete snapshot passes ",[816,5957,5958],{},"256 KiB",[1152,5960,5961,5964,5965,5968],{},[816,5962,5963],{},"A width trip-wire."," Track the widest live fan out. If more than ",[816,5966,5967],{},"50 live child Runs"," share one definition, move to the content addressed table below. This one is an operations metric and not a check in the write path. Nothing refuses a publish or a start on it, and no code in the definitions package reads it.",[987,5970,5972],{"id":5971},"the-snapshot-is-checked-and-never-truncated","The snapshot is checked, and never truncated",[807,5974,5975,5978,5979,5982,5983,5986,5987,5990],{},[840,5976,5977],{},"bound()"," is the one payload boundary, and it works by ",[816,5980,5981],{},"dropping whole top level items",". That is right for a ",[840,5984,5985],{},"RunResult",", a ",[840,5988,5989],{},"ToolResult"," and a span. It is wrong for a snapshot: the dropped item is the tool contracts or the rendered skill text, and the Run then executes an agent that silently lost a tool it was published with. No error is raised and no reader can tell.",[807,5992,5993,5994,5996],{},"So the snapshot is not a ",[840,5995,5977],{}," caller.",[832,5998,6001],{"className":5999,"code":6000,"language":837,"meta":838},[835],"at publish   the complete frozen size is computed and refused above 256 KiB\nat freeze    an oversized snapshot fails the start with 'snapshot_too_large'\nat freeze    a contract the builder cannot assemble fails the start with\n             'snapshot_unbuildable'\nnever        an item is dropped from a snapshot\n",[840,6002,6000],{"__ignoreMap":838},[807,6004,6005,6008,6009,6011,6012,6014,6015,6018,6019,6021],{},[816,6006,6007],{},"A refusal at the freeze arrives as an outcome, never as an exception."," The builder refuses a tool the registry no longer holds, a skill that no longer resolves, and a config with no ceilings. ",[840,6010,1036],{}," answers a closed set and four callers branch on it, one of them an Inngest step: an exception left to escape leaves that step, the function retries it to exhaustion, and every sibling branch of an enclosing ",[840,6013,3619],{}," node ends with it. ",[840,6016,6017],{},"snapshot_unbuildable"," maps to ",[840,6020,2547],{},", because the request is well formed and a retry cannot help until someone fixes the deploy.",[807,6023,6024,6025,6027,6028,6031],{},"The check belongs at publish because the size is a pure function of ",[840,6026,884],{},", the rendered skills and the registry contracts. An admin then learns it at the write they made, rather than at the first Run. The start time failure is the backstop for the two inputs that move between the two moments: a tool contract that grew, and a ",[816,6029,6030],{},"skill that was republished with longer text",". The skill is the likelier of the two, because the rule directly above says a skill publish reaches a referring agent that never republished.",[807,6033,6034,6035,6037,6038,1016],{},"This replaces the earlier 64 KB size trip-wire, which could not fire: ",[840,6036,5977],{}," had already cut the payload to 32 KB, so a p95 above 64 KB was unreachable. See ",[1054,6039,6040],{"href":203},"the payload boundary",[807,6042,6043],{},"The width trip-wire matters as much, and it is easy to miss. Size measures one row. A batch measures the count. An email sequence over 500 people creates 500 child Runs of one agent definition, and each one freezes the same tool contracts and the same rendered skill text. Every row is small, so the size trip-wire never fires, and the storage is still 500 copies of one blob.",[807,6045,6046],{},"The deduplication table is a lookup, not a revision system. Every Run of the same published definition shares one row, and no other contract changes. Do not build it before a trip-wire fires. Repeated identical blobs are cheap to compress and expensive to design around early.",[827,6048,6050],{"id":6049},"rules","Rules",[1149,6052,6053,6056,6059,6062,6065,6073,6076,6092,6099,6109,6112,6117,6130,6135,6138,6141,6152,6158,6166,6171,6174,6183,6188,6191,6194,6202,6205,6208,6211,6214,6217,6222,6229,6234,6237,6242,6248,6251,6261,6264,6273,6284,6293,6299,6302,6310,6313,6321,6326,6329,6332,6338,6341,6344,6349,6355,6361,6364,6367,6370,6378,6387,6395,6398,6401,6404],{},[1152,6054,6055],{},"One repository shape serves agents, workflows and skills.",[1152,6057,6058],{},"One validator entry point. The kind specific checks stay internal.",[1152,6060,6061],{},"Publish establishes validity. Do not revalidate a definition on every Run.",[1152,6063,6064],{},"Publish revalidates the definitions that reference the published one, and only a regression refuses it.",[1152,6066,6067,6068,1137,6070,6072],{},"A referrer whose stored ",[840,6069,940],{},[840,6071,5506],{}," no longer match the recomputed set fails validation.",[1152,6074,6075],{},"Publish validates the candidate config before it writes. There is no transaction to roll back.",[1152,6077,6078,6080,6081,1105,6083,6085,6086,6088,6089,6091],{},[840,6079,2368],{}," guards a draft save and a publish, and neither ",[840,6082,997],{},[840,6084,913],{}," nor ",[840,6087,958],{},". A stale write returns ",[840,6090,2394],{},", and never raises.",[1152,6093,6094,6096,6097,1016],{},[840,6095,2368],{}," is an opaque string end to end. A millisecond-precision client that re-formats it makes every write answer ",[840,6098,2394],{},[1152,6100,4866,6101,6103,6104,6106,6107,1016],{},[840,6102,3021],{}," of the token's offset travels as ",[840,6105,3033],{},". An unencoded one reads as a space and every write answers ",[840,6108,2394],{},[1152,6110,6111],{},"A draft patch replaces a named field. It never merges into one.",[1152,6113,6114,6116],{},[840,6115,1984],{}," owns the lifecycle and the authorization.",[1152,6118,6119,6120,1105,6122,1105,6124,1105,6126,1105,6128,1016],{},"The refusal order is ",[840,6121,1787],{},[840,6123,2244],{},[840,6125,2525],{},[840,6127,1358],{},[840,6129,2693],{},[1152,6131,6132,6134],{},[840,6133,2248],{}," runs the candidate validation alone. A draft is in no referrer set.",[1152,6136,6137],{},"One publish holds one loader with one cache, and every validation pass shares it.",[1152,6139,6140],{},"The API and the UI never write lifecycle fields directly.",[1152,6142,6143,6144,910,6146,6148,6149,1016],{},"No cascading disable. ",[840,6145,997],{},[840,6147,913],{}," revalidates the config ",[816,6150,6151],{},"as stored",[1152,6153,6154,6155,6157],{},"A refusal at the freeze answers ",[840,6156,6017],{},". The start never raises.",[1152,6159,1791,6160,6162,6163,6165],{},[840,6161,892],{}," referrer counts against the cap and never refuses a publish. ",[840,6164,913],{}," is where its break surfaces.",[1152,6167,2295,6168,6170],{},[840,6169,2298],{},". Service-role reads apply no RLS.",[1152,6172,6173],{},"A custom definition references its own organization only. A platform template is forked before it is referenced.",[1152,6175,6176,6177,6179,6180,6182],{},"Publish writes ",[840,6178,2905],{}," beside ",[840,6181,884],{},". The two are one constraint, and every publish moves it.",[1152,6184,6185,6186,4229],{},"Cycle detection runs before the depth function, which has no cycle guard. It walks ",[840,6187,1641],{},[1152,6189,6190],{},"An indirect referrer is not revalidated. The run time depth re-check is the only mechanism that sees it.",[1152,6192,6193],{},"A draft is the only row that deletes. A published definition is disabled.",[1152,6195,6196,6197,6199,6200,1016],{},"A fork source is ",[840,6198,878],{},". A draft and a disabled row both answer ",[840,6201,1787],{},[1152,6203,6204],{},"A fork of a platform template is a deep fork. It copies the reachable set and rewrites every id.",[1152,6206,6207],{},"The deep walk crosses the organization boundary only. A reference already in this organization is shared, never copied.",[1152,6209,6210],{},"Every deep fork mints a fresh set, and it writes the leaves before the root.",[1152,6212,6213],{},"Disable refuses a new Run tree. It never refuses a child of a running tree.",[1152,6215,6216],{},"The Run snapshot is stored inline. A list query never selects it.",[1152,6218,6219,6221],{},[840,6220,5900],{}," takes the definition alone. Publish and the run start call one function.",[1152,6223,6224,6225,6228],{},"The snapshot writes the keys the executor reads, ",[840,6226,6227],{},"ceilings.max_run_duration_s"," in seconds among them.",[1152,6230,5842,6231,6233],{},[840,6232,3302],{},", because a workflow run spends the same five bounds.",[1152,6235,6236],{},"The builder refuses a tool or a skill it cannot assemble. It never drops one.",[1152,6238,6239,6240,5996],{},"The snapshot is size checked at publish. It is never truncated, and it is not a ",[840,6241,5977],{},[1152,6243,6244,6245,6247],{},"The principal grant lives on ",[840,6246,5882],{},", and not in the snapshot.",[1152,6249,6250],{},"A node ID is unique across the whole workflow. A container produces no output of its own.",[1152,6252,6253,6254,6256,6257,1121,6259,1016],{},"A branch declares a ",[840,6255,4478],{},", and its conditions read only ",[840,6258,4437],{},[840,6260,3709],{},[1152,6262,6263],{},"Depth counts workflows, the cap is three, and one function serves publish and run time.",[1152,6265,6266,6267,6269,6270,6272],{},"A wait node always declares ",[840,6268,3982],{},", and an approval node always declares ",[840,6271,5040],{},". Both are whole seconds, capped at 30 days.",[1152,6274,1791,6275,6277,6278,6280,6281,6283],{},[840,6276,3619],{}," node parks in at most one of its child subtrees. A ",[840,6279,4281],{}," beside an ",[840,6282,3559],{}," is refused for the same reason two waits are.",[1152,6285,6286,6287,6289,6290,6292],{},"An approval node takes ",[840,6288,5063],{}," and a static ",[840,6291,5059],{},". There is no template anywhere in the language.",[1152,6294,6295,6296,6298],{},"An approval node refuses ",[840,6297,3381],{},". A tolerated gate is no gate.",[1152,6300,6301],{},"A rejected or expired approval fails its node. An approval node produces no output.",[1152,6303,6304,6305,1121,6307,6309],{},"A tool fan out declares ",[840,6306,3733],{},[840,6308,3737],{},", and its worst case wall clock fits the step budget.",[1152,6311,6312],{},"Phase 4 permits a wide tool only when it is workflow allowed and read only.",[1152,6314,6315,6317,6318,6320],{},[840,6316,4083],{}," is 200, ",[840,6319,4087],{}," is 20, and both fields default to one.",[1152,6322,6323,6325],{},[840,6324,3995],{}," reserves 10 seconds outside the item batches.",[1152,6327,6328],{},"A wide tool's declared result data fits the 3 MiB fan-out data budget.",[1152,6330,6331],{},"All declared leaf output data fits the 24 MiB workflow state budget.",[1152,6333,6334,6335,6337],{},"Publish returns ",[840,6336,4152],{}," until the wide executor is present.",[1152,6339,6340],{},"A wide tool input is one reference to an ordered list of complete argument mappings.",[1152,6342,6343],{},"An agent fan out stays at width one until the runtime can start and join many child Runs.",[1152,6345,4641,6346,6348],{},[840,6347,5679],{}," source resolves in the process context registry, and its options pass the source's strict schema.",[1152,6350,6351,6352,6354],{},"One ",[840,6353,5679],{}," names each source once, including disabled sources.",[1152,6356,6357,6358,6360],{},"A run time list longer than ",[840,6359,3733],{}," fails the node. It is never truncated.",[1152,6362,6363],{},"A retry covers transient faults, and the function owns the count. It cannot repeat a completed effect.",[1152,6365,6366],{},"A reference to a step that did not run fails the node, and never resolves to null.",[1152,6368,6369],{},"A publish revalidates direct referrers only, and refuses to become the 51st referrer of anything. The count excludes the publisher and covers the targets this publish adds.",[1152,6371,2725,6372,6374,6375,6377],{},[840,6373,884],{}," only, and it is read from ",[840,6376,1822],{},", an indexed column that holds no tool id and no platform id.",[1152,6379,6380,6381,6383,6384,6386],{},"A seeded platform template carries a seeded ",[840,6382,1822],{},", because ",[840,6385,1934],{}," never writes one.",[1152,6388,6389,6390,1121,6392,6394],{},"A workflow publishes ",[840,6391,940],{},[840,6393,5506],{},". Admission reads the first, and the Run's principal grant reads the second.",[1152,6396,6397],{},"Both scope sets reach through a subworkflow. A tool two levels down is named at the top, or it is denied at run time.",[1152,6399,6400],{},"Both scope sets are derived. Publish drops them from the candidate, recomputes them, and writes the recomputed pair.",[1152,6402,6403],{},"The draft-versus-published comparison drops the derived keys, or a published definition reads as edited for ever.",[1152,6405,6406],{},"No revision, history or rollback subsystem for a tenant-authored definition in V1. Managed capability\nexecutors carry released revisions and an operator rollback; see \"Upgrade and rollback\".",[827,6408,6410],{"id":6409},"minimum-contract-tests","Minimum contract tests",[1149,6412,6413,6419,6427,6432,6438,6446,6452,6457,6460,6463,6466,6469,6474,6477,6480,6486,6489,6492,6495,6498,6500,6508,6513,6518,6523,6526,6529,6532,6535,6538,6541,6549,6561,6569,6575,6584,6590,6593,6596,6599,6602,6605,6608,6611,6614,6620,6623,6626,6629,6634,6637,6640,6643,6646,6649,6655,6658,6664,6674,6677,6680,6688,6691,6694,6697,6700,6703,6711,6719,6727,6739,6745,6748,6753,6762,6765,6768,6771,6785,6791,6794,6799,6802,6807,6813,6819,6824,6830],{},[1152,6414,6415,6416,6418],{},"A stale ",[840,6417,2368],{}," fails cleanly on a draft save and on a publish.",[1152,6420,6421,6422,6424,6425,1016],{},"A publish of a never-published draft writes ",[840,6423,2905],{},", and the row satisfies ",[840,6426,2909],{},[1152,6428,5004,6429,6431],{},[840,6430,2368],{}," re-formatted at microsecond precision still matches the row, and one truncated to milliseconds matches nothing.",[1152,6433,6434,6435,6437],{},"The API returns ",[840,6436,2368],{}," as a string, and no surface parses it into a date type.",[1152,6439,6440,6441,6443,6444,1016],{},"A fork lands the source config in ",[840,6442,975],{},", and the new row satisfies ",[840,6445,1738],{},[1152,6447,6448,6449,6451],{},"A publish is not refused by a ",[840,6450,892],{}," referrer that would fail against it.",[1152,6453,6454,6456],{},[840,6455,913],{}," on that referrer fails, and names the definition that broke it.",[1152,6458,6459],{},"A disabled referrer still counts toward the referrer cap.",[1152,6461,6462],{},"A referrer lookup for a platform template returns nothing, because a platform template is forked before it is referenced.",[1152,6464,6465],{},"A publish naming a definition of another organization fails validation.",[1152,6467,6468],{},"A fork of a draft is refused.",[1152,6470,6471,6472,1016],{},"A workflow that references itself fails with a cycle error, and never a ",[840,6473,4217],{},[1152,6475,6476],{},"A three-workflow chain whose deepest link is published last publishes, and the parent's fourth level is caught by the run time depth re-check and not by the publish.",[1152,6478,6479],{},"An agent naming a provider and model pair the registry does not hold fails validation.",[1152,6481,6482,6483,6485],{},"A draft patch that sends a shorter ",[840,6484,2482],{}," removes the tools it omits.",[1152,6487,6488],{},"An invalid tool or skill reference blocks an agent publish.",[1152,6490,6491],{},"An agent naming a model the registry does not hold fails validation.",[1152,6493,6494],{},"A skill cannot grant a tool the agent does not have.",[1152,6496,6497],{},"A workflow cycle, depth or type error blocks the publish.",[1152,6499,2600],{},[1152,6501,6502,6504,6505,6507],{},[840,6503,913],{}," returns a disabled definition to ",[840,6506,878],{},", and revalidates it first.",[1152,6509,6510,6512],{},[840,6511,913],{}," finds a stored scope pair that went stale while the definition was off.",[1152,6514,6515,6517],{},[840,6516,913],{}," fails when a definition it references was disabled while it was off.",[1152,6519,6520,6522],{},[840,6521,958],{}," removes a draft, and refuses an active or disabled definition.",[1152,6524,6525],{},"A fork of another organization's custom definition is refused.",[1152,6527,6528],{},"A disabled definition refuses a new Run tree, and allows a child of a running tree.",[1152,6530,6531],{},"Publishing an agent that drops a tool fails when a published workflow needs that tool.",[1152,6533,6534],{},"Publishing a subworkflow that breaks the depth cap of a referring workflow fails.",[1152,6536,6537],{},"A Run snapshot stays stable after a later draft edit and publish.",[1152,6539,6540],{},"The Run list projection does not read the snapshot column.",[1152,6542,6543,6544,6546,6547,1016],{},"A wait node with no ",[840,6545,3982],{}," fails validation, and so does an approval node with no ",[840,6548,5040],{},[1152,6550,1791,6551,6553,6554,6556,6557,6280,6559,1016],{},[840,6552,3619],{}," node with a ",[840,6555,4281],{}," in two child subtrees fails validation, and so does one with a ",[840,6558,4281],{},[840,6560,3559],{},[1152,6562,6563,6564,6566,6567,1016],{},"An approval node declaring ",[840,6565,3381],{}," fails validation, and so does a node reading ",[840,6568,5203],{},[1152,6570,6571,6572,6574],{},"An approval node whose ",[840,6573,5040],{}," passes the Run deadline expires at the deadline, and no later.",[1152,6576,6577,6578,6580,6581,6583],{},"Two branch approvals of one ",[840,6579,3619],{}," node resolve in either order, and the Run reads ",[840,6582,4726],{}," until the second one is answered.",[1152,6585,6586,6587,6589],{},"A wait node whose ",[840,6588,3982],{}," passes the Run deadline parks until the deadline, and no longer.",[1152,6591,6592],{},"A fan out whose worst case wall clock passes the step budget fails validation.",[1152,6594,6595],{},"A fan out whose item batches leave less than 10 seconds of step headroom fails validation.",[1152,6597,6598],{},"A fan out concurrency above its maximum width fails validation.",[1152,6600,6601],{},"A fan out above either platform cap fails validation.",[1152,6603,6604],{},"A boolean, string or decimal fan-out limit fails validation.",[1152,6606,6607],{},"A write, send or workflow-refused tool cannot publish at width above one.",[1152,6609,6610],{},"A fan out whose declared result data passes 3 MiB fails validation.",[1152,6612,6613],{},"A workflow whose declared leaf result data passes 24 MiB fails validation.",[1152,6615,6616,6617,6619],{},"An otherwise valid wide tool returns ",[840,6618,4152],{}," while the execution gate is false.",[1152,6621,6622],{},"A wide tool input that is not one whole reference fails validation.",[1152,6624,6625],{},"A wide agent node fails validation in Phase 4.",[1152,6627,6628],{},"A retried tool node with unchanged arguments produces one vendor effect.",[1152,6630,6631,6632,1016],{},"A node reading the output of an untaken branch fails with ",[840,6633,3057],{},[1152,6635,6636],{},"A width of 200 and concurrency of 20 validates against ten tool timeouts, when both its time and data budgets fit.",[1152,6638,6639],{},"A publish that would become the 51st direct referrer of one definition is refused.",[1152,6641,6642],{},"A re-publish of a definition already among a target's 50 referrers is allowed, because it grows no count.",[1152,6644,6645],{},"A publish whose referrer validation fails writes nothing, and the referrer is named.",[1152,6647,6648],{},"A referrer already invalid against its own published config does not refuse an unrelated publish.",[1152,6650,6651,6652,6654],{},"Publishing an agent that drops a tool fails when a referring workflow's stored ",[840,6653,5506],{}," still names it.",[1152,6656,6657],{},"A draft that references a definition is not counted as a referrer, and is not revalidated.",[1152,6659,6660,6661,6663],{},"A branch with no matching case and no ",[840,6662,4478],{}," fails validation.",[1152,6665,6666,6667,1105,6669,1137,6671,6673],{},"A reference to a ",[840,6668,3616],{},[840,6670,3619],{},[840,6672,3622],{}," output fails validation.",[1152,6675,6676],{},"Two nodes in different containers cannot share one node ID.",[1152,6678,6679],{},"A subworkflow chain three workflows deep publishes, and four deep is refused.",[1152,6681,6682,6683,6685,6686,1016],{},"A fork of a ",[840,6684,892],{}," definition is refused, and answers ",[840,6687,1787],{},[1152,6689,6690],{},"A fork of a platform workflow that names a platform agent lands two drafts, and the copy names the copy.",[1152,6692,6693],{},"A fork of the caller's own definition copies one row, and its references still name the original rows.",[1152,6695,6696],{},"The same platform template forked twice lands two independent sets, sharing no row.",[1152,6698,6699],{},"A fork whose reachable set holds a definition that does not resolve is refused, and names it.",[1152,6701,6702],{},"A deep fork writes every leaf before the root, so no draft ever names an id that does not exist.",[1152,6704,5004,6705,6707,6708,6710],{},[840,6706,2368],{}," whose offset carries a ",[840,6709,3021],{}," matches the row, against the live stack.",[1152,6712,6713,6714,1121,6716,6718],{},"A publish drops ",[840,6715,940],{},[840,6717,5506],{}," from the candidate, and writes the recomputed pair.",[1152,6720,6721,6722,1121,6724,6726],{},"A definition published a moment ago compares equal on ",[840,6723,975],{},[840,6725,884],{},", the derived keys dropped.",[1152,6728,6729,6731,6732,6734,6735,6738],{},[840,6730,5900],{}," writes ",[840,6733,6227],{}," in seconds, and ",[840,6736,6737],{},"AgentExecutor"," reads the snapshot it wrote.",[1152,6740,6741,6744],{},[840,6742,6743],{},"parse_workflow"," reads the snapshot the builder wrote for a workflow.",[1152,6746,6747],{},"A tool the registry no longer holds refuses the freeze, and no snapshot is short a contract.",[1152,6749,6750,6751,6663],{},"A workflow with no ",[840,6752,3302],{},[1152,6754,6755,6756,6758,6759,6761],{},"Publish answers ",[840,6757,2244],{}," for a non-admin before it answers any state outcome, and ",[840,6760,1787],{}," before that.",[1152,6763,6764],{},"An agent naming a context source the process registry does not hold fails validation.",[1152,6766,6767],{},"An agent naming a registered context source with valid options passes validation, enabled or disabled.",[1152,6769,6770],{},"An agent naming one context source twice, an unknown option, or an option with the wrong type fails validation.",[1152,6772,6773,1121,6775,6777,6778,6780,6781,2566,6783,1016],{},[840,6774,997],{},[840,6776,913],{}," on a draft answer ",[840,6779,2525],{},", and a repeated ",[840,6782,997],{},[840,6784,2472],{},[1152,6786,6357,6787,4178,6789,1016],{},[840,6788,3733],{},[840,6790,4181],{},[1152,6792,6793],{},"A snapshot over the size limit fails the publish, and no snapshot is ever truncated.",[1152,6795,6796,6797,2551],{},"The frozen snapshot holds no principal grant; ",[840,6798,5882],{},[1152,6800,6801],{},"Publishing a skill changes the rendered text a new Run of a referring agent freezes.",[1152,6803,6586,6804,6806],{},[840,6805,4544],{}," names an unreachable value fails validation.",[1152,6808,6809,6810,6812],{},"A node reading ",[840,6811,4981],{}," fails validation, exactly as one reading a container does.",[1152,6814,6815,6816,6818],{},"A workflow's ",[840,6817,940],{}," cover every tool node and every referenced subworkflow, and no agent node.",[1152,6820,6815,6821,6823],{},[840,6822,5506],{}," also cover every referenced agent's tools, and every referenced subworkflow's declared set.",[1152,6825,6826,6827,6829],{},"A workflow Run's principal grant is its ",[840,6828,5506],{},", so a tool node of a published workflow is never denied.",[1152,6831,6832],{},"A skill is size checked at publish, and refused at the freeze of a Run.",[6834,6835,6836],"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":838,"searchDepth":32,"depth":233,"links":6838},[6839,6842,6850,6854,6858,6865,6866,6867,6882,6883,6888,6889],{"id":829,"depth":32,"text":830,"children":6840},[6841],{"id":989,"depth":233,"text":990},{"id":1074,"depth":32,"text":1075,"children":6843},[6844,6845,6846,6847,6848,6849],{"id":1143,"depth":233,"text":1144},{"id":1221,"depth":233,"text":1222},{"id":1282,"depth":233,"text":1283},{"id":1378,"depth":233,"text":1379},{"id":1527,"depth":233,"text":1528},{"id":1634,"depth":233,"text":1635},{"id":313,"depth":32,"text":1657,"children":6851},[6852,6853],{"id":1812,"depth":233,"text":1813},{"id":1894,"depth":233,"text":1895},{"id":1961,"depth":32,"text":1962,"children":6855},[6856,6857],{"id":2430,"depth":233,"text":2431},{"id":2504,"depth":233,"text":2505},{"id":2586,"depth":32,"text":2587,"children":6859},[6860,6861,6863,6864],{"id":2683,"depth":233,"text":2684},{"id":2724,"depth":233,"text":6862},"The referrer set is published_config only",{"id":2845,"depth":233,"text":2846},{"id":3061,"depth":233,"text":3062},{"id":3221,"depth":32,"text":3222},{"id":3319,"depth":32,"text":3320},{"id":3368,"depth":32,"text":3369,"children":6868},[6869,6870,6877,6881],{"id":4272,"depth":233,"text":4273},{"id":4498,"depth":233,"text":4499,"children":6871},[6872,6873,6874,6875,6876],{"id":4559,"depth":244,"text":4560},{"id":4704,"depth":244,"text":4705},{"id":4827,"depth":244,"text":4828},{"id":4862,"depth":244,"text":4863},{"id":4962,"depth":244,"text":4963},{"id":5000,"depth":233,"text":5001,"children":6878},[6879,6880],{"id":5256,"depth":244,"text":5257},{"id":5310,"depth":244,"text":5311},{"id":5456,"depth":233,"text":5457},{"id":329,"depth":32,"text":5633},{"id":5795,"depth":32,"text":5796,"children":6884},[6885,6886,6887],{"id":5814,"depth":233,"text":5815},{"id":5924,"depth":233,"text":5925},{"id":5971,"depth":233,"text":5972},{"id":6049,"depth":32,"text":6050},{"id":6409,"depth":32,"text":6410},"md",{},[6893,6894,6895],"engineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime","engineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime\u002Fexecution","engineering\u002Fsystem-design\u002Fagentic-platform\u002Fproducts\u002Fbuilder-chat",{"title":366,"description":367},"engineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime\u002Fdefinitions",[149,311,329,370],"xf4NlKA4lRmpHJbbQFITiwyXipLjCfxSSGgudiCKfEs",1788650174419]