[{"data":1,"prerenderedAt":4820},["ShallowReactive",2],{"docs-nav":3,"docs-article-engineering\u002Fsystem-design\u002Fagentic-platform\u002Fplanes\u002Fpolicy-and-governance":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":288,"body":799,"customComponent":480,"description":289,"extension":4808,"group":139,"lastUpdated":295,"meta":4809,"navigation":1313,"order":290,"path":287,"related":4810,"section":181,"seo":4816,"stem":4817,"tags":4818,"__hash__":4819},"docs\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fplanes\u002Fpolicy-and-governance.md",{"type":800,"value":801,"toc":4778},"minimark",[802,806,823,826,831,836,928,931,935,945,948,954,957,982,986,989,995,1006,1009,1016,1033,1037,1040,1081,1090,1097,1114,1120,1126,1129,1134,1137,1143,1146,1161,1165,1168,1222,1232,1248,1255,1261,1267,1364,1390,1393,1399,1404,1419,1422,1429,1452,1458,1464,1477,1490,1493,1497,1500,1503,1506,1512,1533,1551,1554,1558,1564,1584,1601,1604,1611,1617,1620,1630,1675,1678,1682,1685,1691,1698,1735,1763,1858,1868,1893,1914,1917,1985,1988,1994,2014,2043,2046,2064,2070,2080,2097,2115,2128,2134,2137,2163,2189,2213,2222,2228,2231,2240,2250,2258,2267,2283,2286,2291,2297,2304,2310,2324,2332,2335,2341,2360,2381,2385,2391,2414,2420,2429,2432,2436,2447,2453,2459,2462,2465,2469,2533,2536,2550,2578,2631,2640,2644,2647,2653,2663,2666,2672,2675,2679,2688,2712,2715,2721,2735,2752,2764,2796,2812,2822,2838,2844,2857,2861,2864,2930,2936,2942,2948,2958,2962,2986,3001,3007,3014,3017,3027,3033,3036,3043,3081,3090,3093,3102,3111,3120,3135,3143,3146,3152,3161,3167,3173,3183,3210,3222,3239,3246,3262,3266,3269,3276,3279,3295,3301,3321,3325,3328,3397,3400,3406,3430,3436,3460,3474,3483,3503,3509,3515,3529,3533,3539,3685,3899,3929,3932,3951,3959,4038,4045,4054,4057,4067,4070,4121,4127,4131,4371,4375,4584,4588,4774],[803,804,288],"h1",{"id":805},"policy-and-governance",[807,808,809,810,814,815,818,819,822],"p",{},"Permissions answer ",[811,812,813],"strong",{},"can it",". Policies answer ",[811,816,817],{},"under what conditions",". Limits answer ",[811,820,821],{},"how much",".",[807,824,825],{},"Policy is a plane, not an execution layer. It answers one question and writes one record. It never executes a tool, waits for a person, queries arbitrary domain data, or calls a model.",[827,828],"doc-diagram",{"caption":829,"name":830},"The three planes that cross every layer. Policy decides before the work, at three checkpoints, in two stages: the grant is read live so a stripped right stops working, then the rules restrict but never grant. Observability records beside the work into one run row, one span tree and one meter, always durable before live. Idempotency protects the effect with one table and two clocks, because a worker lease and a replay journal cannot share one field. The last band shows all three firing against a single run.","4-planes",[832,833,835],"h2",{"id":834},"three-checkpoints","Three checkpoints",[837,838,839,858],"table",{},[840,841,842],"thead",{},[843,844,845,849,852,855],"tr",{},[846,847,848],"th",{},"Checkpoint",[846,850,851],{},"Caller",[846,853,854],{},"How often",[846,856,857],{},"Question",[859,860,861,879,895],"tbody",{},[843,862,863,867,873,876],{},[864,865,866],"td",{},"Admission",[864,868,869],{},[870,871,872],"code",{},"RunManager.start()",[864,874,875],{},"once per run",[864,877,878],{},"May this actor run this definition now?",[843,880,881,884,889,892],{},[864,882,883],{},"Action",[864,885,886],{},[870,887,888],{},"ToolInvoker.invoke()",[864,890,891],{},"once per tool call",[864,893,894],{},"May this action happen with these arguments?",[843,896,897,900,922,925],{},[864,898,899],{},"Accrual",[864,901,902,905,906,909,910,913,914,917,918,921],{},[870,903,904],{},"RunManager"," at admission, ",[870,907,908],{},"AgentExecutor"," and ",[870,911,912],{},"WorkflowStepExecutor",", ",[870,915,916],{},"ToolInvoker"," for a metered call, and ",[870,919,920],{},"FrontDoorService"," in Phase 5",[864,923,924],{},"once before dispatch, then before each agent segment, each workflow node, and each paid call, plus once before a front door turn",[864,926,927],{},"May this turn or run start, and may it continue, inside the budget?",[807,929,930],{},"No other component calls the engine. A component that wants a decision goes through one of these three checkpoints.",[832,932,934],{"id":933},"closed-decision-set","Closed decision set",[936,937,943],"pre",{"className":938,"code":940,"language":941,"meta":942},[939],"language-text","allow\nrequire_approval\ndeny\n","text","",[870,944,940],{"__ignoreMap":942},[807,946,947],{},"Precedence:",[936,949,952],{"className":950,"code":951,"language":941,"meta":942},[939],"deny > require_approval > allow\n",[870,953,951],{"__ignoreMap":942},[807,955,956],{},"There are no priority numbers, no rule ordering and no fourth outcome in V1.",[807,958,959,960,963,964,967,968,909,971,974,975,978,979,981],{},"Overlapping rules still produce one row, so the tie is broken by id. ",[870,961,962],{},"matched_policy_ids"," holds ",[811,965,966],{},"every"," matching rule ordered by id, and ",[870,969,970],{},"reason",[870,972,973],{},"approval_ttl"," come from the first rule, by id, whose own decision is the winning outcome. Two ",[870,976,977],{},"require_approval"," rules therefore never race for the clock, and a ",[870,980,970],{}," stays inside the 2000 character column.",[832,983,985],{"id":984},"how-one-decision-is-made","How one decision is made",[807,987,988],{},"Every checkpoint runs the same two stages. The first stage asks whether the principal holds the right at all. The second stage asks whether the current rules restrict it.",[936,990,993],{"className":991,"code":992,"language":941,"meta":942},[939],"PolicyEngine.decide(principal, request)\n\n  1  grant     checkpoint == 'accrual' ?\n                 yes -> skip; a budget is not a permission\n                 no  -> effective = principal.scopes ∩ the actor's rights, read live\n                        action in effective ?\n                          no -> deny(not_granted)        \u003C- fail closed\n\n  2  rules     rules = cache.for_organization(org, action)\n               matched = rules where conditions hold for the request facts\n               no match  -> allow\n               match     -> the highest precedence decision wins\n\n  3  record    a refusal, a gate or an admission -> a policy_decisions row\n               an allowed action -> attributes on the tool span the invoker opened\n",[870,994,992],{"__ignoreMap":942},[807,996,997,998,1001,1002,1005],{},"Stage 1 reads the ",[811,999,1000],{},"live"," intersection, not the frozen set on the principal. Written against ",[870,1003,1004],{},"principal.scopes"," alone it would answer from the grant minted at start, and a right stripped an hour ago would still work — which is exactly what \"the grant narrows live\" below exists to stop. The frozen grant stays the ceiling; the live read can only shrink it.",[807,1007,1008],{},"Stage 1 is the permission model. Stage 2 is the condition model. Keeping them apart removes the contradiction between \"anything not granted is denied\" and \"a rule set with no matching rule allows\".",[807,1010,1011,1012,1015],{},"A grant is never implied by a rule. An ",[870,1013,1014],{},"allow"," rule cannot give a scope the principal does not hold.",[807,1017,1018,1019,1024,1025,1028,1029,1032],{},"⚠️ ",[811,1020,1021,1023],{},[870,1022,916],{}," holds no copy of this check."," A frozen read there saves a call and answers the wrong question: it passes a tool the live read stripped, and it refuses before ",[870,1026,1027],{},"decide()"," runs, so a scope refusal reaches no ",[870,1030,1031],{},"agent.policy_decisions"," row. One rule takes one implementation, and stage 1 is it.",[832,1034,1036],{"id":1035},"actions-and-scopes","Actions and scopes",[807,1038,1039],{},"One flat namespace names every action.",[837,1041,1042,1051],{},[840,1043,1044],{},[843,1045,1046,1048],{},[846,1047,848],{},[846,1049,1050],{},"Action string",[859,1052,1053,1062,1072],{},[843,1054,1055,1057],{},[864,1056,866],{},[864,1058,1059],{},[870,1060,1061],{},"run.start",[843,1063,1064,1066],{},[864,1065,883],{},[864,1067,1068,1069],{},"the tool name, for example ",[870,1070,1071],{},"email.send",[843,1073,1074,1076],{},[864,1075,899],{},[864,1077,1078],{},[870,1079,1080],{},"run.accrue",[807,1082,1083,1084,1086,1087,1089],{},"Accrual runs stage 2 only. A budget is not a permission, so ",[870,1085,1080],{}," is never a grant, and stage 1 skips it explicitly. Without that branch a literal reading of the algorithm denies every accrual check, because ",[870,1088,1080],{}," is in nobody's scopes.",[807,1091,1092,1093,1096],{},"A ",[811,1094,1095],{},"scope is one exact action name",". There is one scope vocabulary, and it is the tool name. A role that covers a whole domain expands to the tool names it covers when the principal is minted. A grant carries no wildcard, so nobody has to work out what a grant covers.",[807,1098,1099,1102,1103,1106,1107,1110,1111,1113],{},[811,1100,1101],{},"V1 lists the tool names and expands nothing."," ",[870,1104,1105],{},"ROLE_RIGHTS"," in ",[870,1108,1109],{},"governance\u002Fpolicy\u002Fprincipals.py"," names each tool. Expansion would need the tool registry inside ",[870,1112,294],{},", and the deploy runs three tools. One test refuses a declared tool that no role holds.",[807,1115,1092,1116,1119],{},[811,1117,1118],{},"rule"," may use a wildcard, because a rule restricts and never grants.",[936,1121,1124],{"className":1122,"code":1123,"language":941,"meta":942},[939],"email.send      one action\ncrm.*           every action in the domain\n*               every action\n",[870,1125,1123],{"__ignoreMap":942},[807,1127,1128],{},"Every matching rule is evaluated, and precedence resolves the outcome. There is no most-specific-wins search.",[1130,1131,1133],"h3",{"id":1132},"what-admission-checks","What admission checks",[807,1135,1136],{},"Admission is not only a scope check. It also refuses work that cannot succeed.",[936,1138,1141],{"className":1139,"code":1140,"language":941,"meta":942},[939],"the definition is published, enabled, and owned by the organization\nthe actor holds run.start\nthe organization is inside its day cost cap\na workflow run: the principal holds every scope its TOOL nodes need\nan agent run:   no scope requirement; the intersection removes tools instead\n",[870,1142,1140],{"__ignoreMap":942},[807,1144,1145],{},"A tool node cannot ask the model for another way, so a missing scope is fatal at admission. An agent can adapt, so it simply receives fewer tools.",[807,1147,1148,1151,1152,1155,1156,1160],{},[811,1149,1150],{},"An agent node inside a workflow adapts exactly like a standalone agent."," So the fatal set is ",[870,1153,1154],{},"required_scopes",", which ",[1157,1158,1159],"a",{"href":365},"runtime definitions"," computes from tool nodes and referenced subworkflows only. Fold an agent's scopes into the fatal set and a principal who can run agent A alone is refused the workflow that contains it, which is a denial with no failure behind it.",[832,1162,1164],{"id":1163},"the-principal","The principal",[807,1166,1167],{},"Every action carries one immutable run-scoped principal.",[936,1169,1173],{"className":1170,"code":1171,"language":1172,"meta":942,"style":942},"language-python shiki shiki-themes github-dark","@dataclass(frozen=True)\nclass Principal:\n    organization_id: UUID\n    run_id: UUID\n    definition_id: UUID\n    user_id: UUID | None        # set for an interactive run\n    trigger_id: UUID | None     # set for a machine run\n    scopes: frozenset[str]\n    authored_by: UUID | None    # the trigger author, for a machine run\n","python",[870,1174,1175,1182,1187,1192,1197,1202,1207,1212,1217],{"__ignoreMap":942},[1176,1177,1179],"span",{"class":1178,"line":22},"line",[1176,1180,1181],{},"@dataclass(frozen=True)\n",[1176,1183,1184],{"class":1178,"line":32},[1176,1185,1186],{},"class Principal:\n",[1176,1188,1189],{"class":1178,"line":233},[1176,1190,1191],{},"    organization_id: UUID\n",[1176,1193,1194],{"class":1178,"line":244},[1176,1195,1196],{},"    run_id: UUID\n",[1176,1198,1199],{"class":1178,"line":264},[1176,1200,1201],{},"    definition_id: UUID\n",[1176,1203,1204],{"class":1178,"line":222},[1176,1205,1206],{},"    user_id: UUID | None        # set for an interactive run\n",[1176,1208,1209],{"class":1178,"line":360},[1176,1210,1211],{},"    trigger_id: UUID | None     # set for a machine run\n",[1176,1213,1214],{"class":1178,"line":368},[1176,1215,1216],{},"    scopes: frozenset[str]\n",[1176,1218,1219],{"class":1178,"line":375},[1176,1220,1221],{},"    authored_by: UUID | None    # the trigger author, for a machine run\n",[807,1223,1224,1225,909,1228,1231],{},"Exactly one of ",[870,1226,1227],{},"user_id",[870,1229,1230],{},"trigger_id"," is set. There is no third actor kind and no service-principal table in V1.",[807,1233,1234,1102,1237,1239,1240,1243,1244,1247],{},[811,1235,1236],{},"The actor carries the organization, and the command does not.",[870,1238,872],{}," needs the organization three steps before it mints a Principal: for the day cap gate, for the start key, and for the Run row. ",[870,1241,1242],{},"StartRunCommand"," therefore has no ",[870,1245,1246],{},"organization_id"," of its own, because one fact with two writers is one fact that eventually disagrees with itself. Every caller already knows its actor: the channel gateway resolved a verified identity, and a trigger row belongs to an organization.",[807,1249,1250,1251,1254],{},"The grant is an ",[811,1252,1253],{},"intersection",", never a union.",[936,1256,1259],{"className":1257,"code":1258,"language":941,"meta":942},[939],"scopes the definition declares  ∩  rights the actor holds  =  effective scopes\n",[870,1260,1258],{"__ignoreMap":942},[807,1262,1263,1264,1266],{},"The caller supplies an actor. ",[870,1265,904],{}," mints the principal, because the intersection needs the definition and the run ID.",[936,1268,1270],{"className":1170,"code":1269,"language":1172,"meta":942,"style":942},"@dataclass(frozen=True)\nclass ActorIdentity:\n    kind: Literal['user', 'trigger']\n    organization_id: UUID\n    user_id: UUID | None\n    trigger_id: UUID | None\n    scopes: frozenset[str]\n    authored_by: UUID | None\n\nclass PrincipalFactory:\n    async def for_run(\n        self,\n        *,\n        definition_id: UUID,\n        declared_scopes: Iterable[str],   # tool_ids, or a workflow's declared_scopes\n        actor: ActorIdentity,\n        run_id: UUID,\n    ) -> Principal: ...\n",[870,1271,1272,1276,1281,1286,1290,1295,1300,1304,1309,1315,1320,1325,1330,1335,1340,1346,1352,1358],{"__ignoreMap":942},[1176,1273,1274],{"class":1178,"line":22},[1176,1275,1181],{},[1176,1277,1278],{"class":1178,"line":32},[1176,1279,1280],{},"class ActorIdentity:\n",[1176,1282,1283],{"class":1178,"line":233},[1176,1284,1285],{},"    kind: Literal['user', 'trigger']\n",[1176,1287,1288],{"class":1178,"line":244},[1176,1289,1191],{},[1176,1291,1292],{"class":1178,"line":264},[1176,1293,1294],{},"    user_id: UUID | None\n",[1176,1296,1297],{"class":1178,"line":222},[1176,1298,1299],{},"    trigger_id: UUID | None\n",[1176,1301,1302],{"class":1178,"line":360},[1176,1303,1216],{},[1176,1305,1306],{"class":1178,"line":368},[1176,1307,1308],{},"    authored_by: UUID | None\n",[1176,1310,1311],{"class":1178,"line":375},[1176,1312,1314],{"emptyLinePlaceholder":1313},true,"\n",[1176,1316,1317],{"class":1178,"line":157},[1176,1318,1319],{},"class PrincipalFactory:\n",[1176,1321,1322],{"class":1178,"line":182},[1176,1323,1324],{},"    async def for_run(\n",[1176,1326,1327],{"class":1178,"line":290},[1176,1328,1329],{},"        self,\n",[1176,1331,1332],{"class":1178,"line":280},[1176,1333,1334],{},"        *,\n",[1176,1336,1337],{"class":1178,"line":272},[1176,1338,1339],{},"        definition_id: UUID,\n",[1176,1341,1343],{"class":1178,"line":1342},15,[1176,1344,1345],{},"        declared_scopes: Iterable[str],   # tool_ids, or a workflow's declared_scopes\n",[1176,1347,1349],{"class":1178,"line":1348},16,[1176,1350,1351],{},"        actor: ActorIdentity,\n",[1176,1353,1355],{"class":1178,"line":1354},17,[1176,1356,1357],{},"        run_id: UUID,\n",[1176,1359,1361],{"class":1178,"line":1360},18,[1176,1362,1363],{},"    ) -> Principal: ...\n",[807,1365,1366,1102,1369,1372,1373,1376,1377,1380,1381,1383,1384,1386,1387,1389],{},[811,1367,1368],{},"It takes the declared names and not the definition.",[870,1370,1371],{},"ResolvedDefinition"," lives in ",[870,1374,1375],{},"runtime\u002Fruns\u002Fmanager.py",", and the import contract ",[870,1378,1379],{},"src.agentic.runtime is the top layer of the platform"," refuses ",[870,1382,294],{}," importing ",[870,1385,149],{},". ",[870,1388,904],{}," reads the names off the definition it already holds.",[807,1391,1392],{},"User rights come from the existing user roles. A mapping turns a role into scopes, and it grants two kinds.",[936,1394,1397],{"className":1395,"code":1396,"language":941,"meta":942},[939],"run.start           to every role that may run agentic work at all\ndefinition.publish  to the roles that may author and publish definitions\ntool names          the actions that role may take\n",[870,1398,1396],{"__ignoreMap":942},[807,1400,1401,1403],{},[870,1402,1061],{}," is an action and not a tool, so nothing else would grant it, and stage 1 fails closed. Every run would be denied.",[807,1405,1406,1409,1410,1413,1414,1386,1416,1418],{},[870,1407,1408],{},"definition.publish"," is the second such action. The ",[1157,1411,1412],{"href":306},"builder chat"," reaches publishing through a tool of that name, and the direct API route checks the same scope, so the two authoring paths cannot diverge. See ",[1157,1415,1159],{"href":365},[870,1417,1080],{}," is granted to nobody, because a budget is not a permission.",[807,1420,1421],{},"No new permission table exists only for agents.",[807,1423,1424,1425,1428],{},"A trigger row carries its own scopes. An admin sets them when authoring the trigger, and ",[870,1426,1427],{},"PrincipalFactory"," meets them with that admin's rights at every mint. A machine grant is normally narrower than a user grant, and it is never wider than its author.",[936,1430,1432],{"className":1170,"code":1431,"language":1172,"meta":942,"style":942},"@dataclass(frozen=True)\nclass ActorIdentity:\n    ...\n    authored_by: UUID | None   # the admin who authored the trigger\n",[870,1433,1434,1438,1442,1447],{"__ignoreMap":942},[1176,1435,1436],{"class":1178,"line":22},[1176,1437,1181],{},[1176,1439,1440],{"class":1178,"line":32},[1176,1441,1280],{},[1176,1443,1444],{"class":1178,"line":233},[1176,1445,1446],{},"    ...\n",[1176,1448,1449],{"class":1178,"line":244},[1176,1450,1451],{},"    authored_by: UUID | None   # the admin who authored the trigger\n",[807,1453,1454,1457],{},[870,1455,1456],{},"ActorIdentity"," refuses a trigger actor that names no author, and a user actor that names one. A person narrows against their own rights, so a second name there is a second answer.",[936,1459,1462],{"className":1460,"code":1461,"language":941,"meta":942},[939],"machine rights = (authored scopes  ∪  RUN_ACTIONS)  ∩  the author's rights, read live\n",[870,1463,1461],{"__ignoreMap":942},[807,1465,1466,1467,1469,1470,1473,1474,1476],{},"An admin authors a trigger by naming tools, exactly as a definition declares tools. Neither names ",[870,1468,1061],{},", so ",[870,1471,1472],{},"RUN_ACTIONS"," joins the authored set before the meet. The author still decides: a role that may not run agentic work holds no ",[870,1475,1061],{},", and the meet drops it again.",[807,1478,1479,1480,1483,1484,1486,1487,822],{},"A trigger row that authored ",[811,1481,1482],{},"nothing"," holds nothing, and ",[870,1485,1472],{}," does not rescue it. An empty scope list is a row an admin did not fill in. The refusal says so: ",[870,1488,1489],{},"this trigger holds no authored scope, so run.start is denied",[807,1491,1492],{},"A child run inherits the parent principal exactly. A step cannot widen authority.",[1130,1494,1496],{"id":1495},"the-grant-narrows-live-and-never-widens","The grant narrows live, and never widens",[807,1498,1499],{},"The snapshot freezes the principal so that work stays stable. Taken literally that has one consequence nobody wants: a person is offboarded, their role is stripped, and their three day email sequence keeps sending under the authority they held on Monday.",[807,1501,1502],{},"Tool revocation and the organization kill switch both reach an in flight run at its next checkpoint. The person's own rights would not, and that is the one authority in the system that a compliance answer is usually about.",[807,1504,1505],{},"So the principal is re-intersected at every checkpoint, and the result can only shrink.",[936,1507,1510],{"className":1508,"code":1509,"language":941,"meta":942},[939],"effective scopes = frozen grant  ∩  the actor's rights, read live\n",[870,1511,1509],{"__ignoreMap":942},[1513,1514,1515,1523,1530],"ul",{},[1516,1517,1518,1519,1522],"li",{},"A right removed since the run started ",[811,1520,1521],{},"is gone"," at the next checkpoint.",[1516,1524,1525,1526,1529],{},"A right added since the run started ",[811,1527,1528],{},"changes nothing",". The frozen grant is still the ceiling, so no run silently gains a capability an admin granted for something else.",[1516,1531,1532],{},"A trigger principal narrows the same way against its authoring admin's current rights.",[807,1534,1018,1535,1538,1539,1542,1543,1546,1547,1550],{},[811,1536,1537],{},"A machine grant narrows against its author, and not against the trigger."," A trigger id keys no membership row. ",[870,1540,1541],{},"LiveGrant.effective()"," reads ",[870,1544,1545],{},"authored_by",", so an offboarded admin strips every trigger they wrote at its next checkpoint. ",[870,1548,1549],{},"agent.runs.principal"," stores that id, because no column on the run row holds it. The trigger interface supplies it from the trigger row; nothing else in V1 builds a machine actor.",[807,1552,1553],{},"The live read is one lookup per checkpoint, cached per worker for 30 seconds beside the rule set it already caches. Nothing new is stored, and the frozen grant stays exactly what it was for: an upper bound that an edit to a definition cannot move.",[832,1555,1557],{"id":1556},"policy-rules","Policy rules",[936,1559,1562],{"className":1560,"code":1561,"language":941,"meta":942},[939],"agent.policies\n  id\n  organization_id\n  name\n  action           exact name, domain wildcard, or *\n  definition_id    optional narrowing to one agent or workflow\n  conditions       condition tree, optional\n  decision         allow | deny | require_approval\n  approval_ttl_seconds  optional and positive, read only by require_approval\n  enabled\n  created_at\n  updated_at       the write token; PATCH \u002Fpolicies\u002F{id} checks it\n",[870,1563,1561],{"__ignoreMap":942},[807,1565,1566,1569,1570,1573,1574,1577,1578,1581,1582,822],{},[870,1567,1568],{},"updated_at"," is the write token. ",[870,1571,1572],{},"PATCH \u002Fapi\u002Fv1\u002Fagentic\u002Fpolicies\u002F{id}"," carries ",[870,1575,1576],{},"expected_updated_at",". A stale writer reloads, and it never overwrites the other writer. The list cursor is ",[870,1579,1580],{},"(created_at, id)",", because a bare timestamp cursor drops a row on a page boundary. See ",[1157,1583,253],{"href":249},[807,1585,1586,1589,1590,1592,1593,1596,1597,1600],{},[870,1587,1588],{},"definition_id"," pairs with ",[870,1591,1246],{}," in the foreign key, so one organization cannot narrow a rule to another organization's definition. The key takes ",[870,1594,1595],{},"ON DELETE CASCADE",", so deleting the definition deletes the rule. ",[870,1598,1599],{},"ON DELETE SET NULL"," would turn a rule narrowed to one definition into an action-wide rule.",[807,1602,1603],{},"A rule name is unique inside one organization.",[807,1605,1606,1607,1610],{},"Bind a rule to an ",[811,1608,1609],{},"action"," first. Narrowing to one definition is optional, and it is the exception.",[936,1612,1615],{"className":1613,"code":1614,"language":941,"meta":942},[939],"email.send          AND recipient_is_new           -> require_approval\ncrm.update          AND target.lifecycle = customer -> require_approval\nrun.start           AND run.definition_id = \u003Csignals_search> AND arguments.limit > 1000 -> require_approval\n*                                                   -> deny        # the organization kill switch\n",[870,1616,1614],{"__ignoreMap":942},[807,1618,1619],{},"The last line is the emergency stop. One row disables every agentic action for an organization. Per-tool revocation stays in the tool registry, where the runtime already reads live tool state.",[807,1621,1622,1625,1626,1629],{},[811,1623,1624],{},"Be precise about what the kill switch does not do."," It refuses the next ",[1627,1628,1609],"em",{},", and it ends nothing on its own.",[837,1631,1632,1642],{},[840,1633,1634],{},[843,1635,1636,1639],{},[846,1637,1638],{},"To do this",[846,1640,1641],{},"Use this",[859,1643,1644,1656,1667],{},[843,1645,1646,1649],{},[864,1647,1648],{},"Stop every new action, everywhere, in under 30 seconds",[864,1650,1651,1652,1655],{},"the ",[870,1653,1654],{},"*"," deny rule",[843,1657,1658,1661],{},[864,1659,1660],{},"End the runs that are already executing",[864,1662,1663,1666],{},[870,1664,1665],{},"POST \u002Fruns\u002F{id}\u002Fcancel"," on each, or a bulk cancel",[843,1668,1669,1672],{},[864,1670,1671],{},"Stop a run that is waiting three days on an approval",[864,1673,1674],{},"cancel it; a waiting run reaches no checkpoint until it resumes",[807,1676,1677],{},"The third row is the one that surprises people during an incident. A waiting run holds no worker and evaluates no rule, so it sits under the kill switch untouched and meets it only when a person answers, days later. The rule refuses the action then, which is correct and very late. Cancel is what ends it now.",[832,1679,1681],{"id":1680},"conditions-and-facts","Conditions and facts",[807,1683,1684],{},"The condition language stays small.",[936,1686,1689],{"className":1687,"code":1688,"language":941,"meta":942},[939],"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",[870,1690,1688],{"__ignoreMap":942},[807,1692,1693,1694,1697],{},"There is no Python, SQL, CEL or Rego, and no model-evaluated expression. The triggers layer reuses the same ",[870,1695,1696],{},"ConditionEvaluator"," over an event-shaped fact map.",[807,1699,1700,1703,1704,1707,1708,1711,1712,1715,1716,1719,1720,1723,1724,1727,1728,1730,1731,1734],{},[811,1701,1702],{},"A condition is stored as data, and never as text."," A rule holds a small tree, and the evaluator walks it. Text would need a lexer and a parser, and a parser is the expression language this page refuses; it also grows a function on the first request the grammar cannot serve. A ",[870,1705,1706],{},"CHECK"," on ",[870,1709,1710],{},"agent.policies"," validates the tree at save time. It calls ",[870,1713,1714],{},"agent.condition_tree_is_valid()",", which recurses through ",[870,1717,1718],{},"of"," alone: ",[870,1721,1722],{},"value"," is a fact to compare and never a node. A jsonpath cannot do this work. ",[870,1725,1726],{},"$.**"," descends into ",[870,1729,1722],{},", so it reads an object argument as a malformed node. It also cannot count children, so ",[870,1732,1733],{},"not"," with two children saves.",[807,1736,1737,1743,1744,1747,1748,1751,1752,1755,1756,909,1759,1762],{},[811,1738,1739,1740,1742],{},"The walker and ",[870,1741,1696],{}," refuse the same trees."," They are two implementations of one grammar, so any shape that saves and then raises is a rule an admin cannot see is dead: ",[870,1745,1746],{},"deny"," at the checkpoint is correct and silent. Both cap the tree at ",[811,1749,1750],{},"20 levels",", both compare the key set of a node in each direction, and both refuse ",[870,1753,1754],{},"eq null",", an empty membership list and a null member. Move the two together, or the pgTAP test ",[870,1757,1758],{},"agent_policy_tables_test.sql",[870,1760,1761],{},"test_conditions.py"," disagree.",[936,1764,1768],{"className":1765,"code":1766,"language":1767,"meta":942,"style":942},"language-json shiki shiki-themes github-dark","{\"op\": \"and\", \"of\": [\n  {\"op\": \"eq\", \"path\": \"arguments.to\", \"value\": \"sales@example.com\"},\n  {\"op\": \"exists\", \"path\": \"target.lifecycle\"}\n]}\n","json",[870,1769,1770,1795,1830,1853],{"__ignoreMap":942},[1176,1771,1772,1776,1780,1783,1787,1789,1792],{"class":1178,"line":22},[1176,1773,1775],{"class":1774},"s95oV","{",[1176,1777,1779],{"class":1778},"sDLfK","\"op\"",[1176,1781,1782],{"class":1774},": ",[1176,1784,1786],{"class":1785},"sU2Wk","\"and\"",[1176,1788,913],{"class":1774},[1176,1790,1791],{"class":1778},"\"of\"",[1176,1793,1794],{"class":1774},": [\n",[1176,1796,1797,1800,1802,1804,1807,1809,1812,1814,1817,1819,1822,1824,1827],{"class":1178,"line":32},[1176,1798,1799],{"class":1774},"  {",[1176,1801,1779],{"class":1778},[1176,1803,1782],{"class":1774},[1176,1805,1806],{"class":1785},"\"eq\"",[1176,1808,913],{"class":1774},[1176,1810,1811],{"class":1778},"\"path\"",[1176,1813,1782],{"class":1774},[1176,1815,1816],{"class":1785},"\"arguments.to\"",[1176,1818,913],{"class":1774},[1176,1820,1821],{"class":1778},"\"value\"",[1176,1823,1782],{"class":1774},[1176,1825,1826],{"class":1785},"\"sales@example.com\"",[1176,1828,1829],{"class":1774},"},\n",[1176,1831,1832,1834,1836,1838,1841,1843,1845,1847,1850],{"class":1178,"line":233},[1176,1833,1799],{"class":1774},[1176,1835,1779],{"class":1778},[1176,1837,1782],{"class":1774},[1176,1839,1840],{"class":1785},"\"exists\"",[1176,1842,913],{"class":1774},[1176,1844,1811],{"class":1778},[1176,1846,1782],{"class":1774},[1176,1848,1849],{"class":1785},"\"target.lifecycle\"",[1176,1851,1852],{"class":1774},"}\n",[1176,1854,1855],{"class":1178,"line":244},[1176,1856,1857],{"class":1774},"]}\n",[807,1859,1860,1863,1864,1867],{},[870,1861,1862],{},"path"," reads one fact through a dotted lookup, so one implementation serves all three planes. Only the map behind it differs. See the ",[1157,1865,1866],{"href":365},"branch node"," for the workflow map.",[807,1869,1870,1873,1874,1877,1878,1881,1882,1885,1886,1889,1890,1892],{},[811,1871,1872],{},"The evaluator answers True or False, and never \"unknown\"."," A third answer would have to be folded back into a ",[870,1875,1876],{},"branch"," that has two children, so an absent fact answers False for every leaf, negated ones included. That is right for a branch and wrong for a gate, so ",[870,1879,1880],{},"deny(missing_fact)"," is the ",[811,1883,1884],{},"engine's"," rule and not the evaluator's: ",[870,1887,1888],{},"condition_paths()",", beside the evaluator, answers every path a rule reads, and the engine compares that set against the facts it resolved before it evaluates. The same function answers the publish check that every path of a ",[870,1891,1876],{}," sits inside the allowed namespaces.",[807,1894,1895,1902,1903,1905,1906,1909,1910,1913],{},[811,1896,1897,1898,1901],{},"It lives in ",[870,1899,1900],{},"src\u002Fagentic\u002Fshared\u002F",", not in the policy package."," Three planes call it over three different fact maps: policy rules, a workflow ",[870,1904,1876],{},", and a trigger condition. It is the same case as ",[870,1907,1908],{},"bound()",", and the same answer. The workflow executor is built in Phase 1 and the policy plane is not, so putting the evaluator under ",[870,1911,1912],{},"policy\u002F"," would make the first thing that needs it reach into a package that does not exist yet.",[807,1915,1916],{},"A condition reads a flat fact map. Three groups of facts exist.",[837,1918,1919,1932],{},[840,1920,1921],{},[843,1922,1923,1926,1929],{},[846,1924,1925],{},"Group",[846,1927,1928],{},"Example",[846,1930,1931],{},"Source",[859,1933,1934,1953,1969],{},[843,1935,1936,1939,1950],{},[864,1937,1938],{},"Principal and run",[864,1940,1941,913,1944,913,1947],{},[870,1942,1943],{},"principal.user_id",[870,1945,1946],{},"run.source",[870,1948,1949],{},"run.definition_id",[864,1951,1952],{},"the principal and the run row",[843,1954,1955,1958,1966],{},[864,1956,1957],{},"Arguments",[864,1959,1960,913,1963],{},[870,1961,1962],{},"arguments.to",[870,1964,1965],{},"arguments.limit",[864,1967,1968],{},"the proposed call",[843,1970,1971,1974,1982],{},[864,1972,1973],{},"Target",[864,1975,1976,913,1979],{},[870,1977,1978],{},"recipient_is_new",[870,1980,1981],{},"target.lifecycle",[864,1983,1984],{},"a declared fact resolver",[807,1986,1987],{},"Target facts are the part that needs care. A useful rule often asks a question the arguments cannot answer. The engine must not answer it by querying arbitrary domain data, and the tool handler cannot answer it either, because policy runs before the handler.",[807,1989,1990,1991,822],{},"So a fact is ",[811,1992,1993],{},"declared and resolved before the checkpoint",[936,1995,1997],{"className":1170,"code":1996,"language":1172,"meta":942,"style":942},"class FactResolver(Protocol):\n    name: str\n    async def resolve(self, principal: Principal, request: PolicyRequest) -> Any: ...\n",[870,1998,1999,2004,2009],{"__ignoreMap":942},[1176,2000,2001],{"class":1178,"line":22},[1176,2002,2003],{},"class FactResolver(Protocol):\n",[1176,2005,2006],{"class":1178,"line":32},[1176,2007,2008],{},"    name: str\n",[1176,2010,2011],{"class":1178,"line":233},[1176,2012,2013],{},"    async def resolve(self, principal: Principal, request: PolicyRequest) -> Any: ...\n",[1513,2015,2016,2025,2030,2033],{},[1516,2017,1092,2018,2021,2022,822],{},[870,2019,2020],{},"ToolSpec"," declares ",[870,2023,2024],{},"policy_facts: list[str]",[1516,2026,2027,2029],{},[870,2028,916],{}," resolves those facts and puts them in the request before it calls the engine.",[1516,2031,2032],{},"Saving a rule validates that every target fact it names is declared by the action it binds to.",[1516,2034,2035,2036,2039,2040,2042],{},"At run time a rule that names a missing ",[811,2037,2038],{},"target"," fact returns ",[870,2041,1880],{},". A gate that cannot be evaluated must never silently pass.",[807,2044,2045],{},"This keeps the engine free of domain queries, and it keeps the fact set visible in the tool catalogue.",[807,2047,2048,1102,2054,913,2057,909,2060,2063],{},[811,2049,2050,2053],{},[870,2051,2052],{},"missing_fact"," reads target facts alone, and three roots are reserved.",[870,2055,2056],{},"principal",[870,2058,2059],{},"run",[870,2061,2062],{},"arguments"," are supplied by the platform at every checkpoint, and an absent path under one of them answers False like any other leaf.",[936,2065,2068],{"className":2066,"code":2067,"language":941,"meta":942},[939],"principal.*    the grant, written by the engine\narguments.*    the proposed call, written by the engine from the request\nrun.*          the run row, written by the caller\nanything else  a declared target fact; absent means deny(missing_fact)\n",[870,2069,2067],{"__ignoreMap":942},[807,2071,2072,2073,2076,2077,2079],{},"Read literally, without that split, the ",[870,2074,2075],{},"arguments.limit > 1000"," rule above denies every call that passes no limit. The rule was written to gate a large one. ",[870,2078,1943],{}," is the same trap: it is null on every machine run.",[807,2081,2082,2085,2086,2089,2090,2093,2094,2096],{},[811,2083,2084],{},"The check reads presence, and never the value."," A resolver that answers ",[870,2087,2088],{},"false"," has answered, and ",[870,2091,2092],{},"recipient_is_new: false"," is the case an ",[870,2095,1071],{}," rule exists to allow. An engine that read the value as absence would deny every established recipient, which inverts the rule.",[807,2098,2099,2107,2108,2111,2112,2114],{},[811,2100,2101,2102,909,2104,2106],{},"The engine writes ",[870,2103,2056],{},[870,2105,2062],{}," last."," A ",[870,2109,2110],{},"FactResolver"," is domain code, and a fact map that let one return a ",[870,2113,2056],{}," key would let it rewrite the grant the same decision is reading.",[807,2116,2117,2120,2121,2124,2125,2127],{},[811,2118,2119],{},"Resolve only what a live rule asks for."," A declared fact is what a rule ",[1627,2122,2123],{},"may"," read, not what every call must pay for. ",[870,2126,1978],{}," costs a CRM lookup, and an organization with no rule naming it should never pay that on a send.",[936,2129,2132],{"className":2130,"code":2131,"language":941,"meta":942},[939],"declared by the tool  ∩  named by a rule matching this action  =  facts to resolve\n",[870,2133,2131],{"__ignoreMap":942},[807,2135,2136],{},"The rule set is already in the worker cache, so the intersection is free. The common case is an empty set and no extra query at all. This is an optimization only: it changes no decision, because a fact no rule reads cannot change an outcome.",[807,2138,2139,2142,2143,2145,2146,2148,2149,2152,2153,2155,2156,2159,2160,2162],{},[811,2140,2141],{},"The intersection matches by prefix, and never by equality."," A tool declares ",[870,2144,2038],{}," and a rule reads ",[870,2147,1981],{},". A declared fact ",[870,2150,2151],{},"F"," is named when a path equals ",[870,2154,2151],{}," or starts with ",[870,2157,2158],{},"F.",". Equality alone resolves nothing here, and the engine then answers ",[870,2161,1880],{}," on every call of that tool. The save-time check reads the same rule, so a tree that saves is a tree the run-time intersection covers.",[807,2164,2165,2177,2178,909,2180,2182,2183,2185,2186,2188],{},[811,2166,2167,913,2169,909,2171,2173,2174,822],{},[870,2168,2056],{},[870,2170,2059],{},[870,2172,2062],{}," are refused in ",[870,2175,2176],{},"policy_facts"," They are the three reserved roots, and a resolver may not write one. The engine writes ",[870,2179,2056],{},[870,2181,2062],{}," last, so neither is reachable. It does not write ",[870,2184,2059],{},", so a fact named ",[870,2187,2059],{}," overwrites the run facts the caller supplied. Registration refuses all three.",[807,2190,2191,2194,2195,2197,2198,2200,2201,1542,2203,2205,2206,2209,2210,2212],{},[811,2192,2193],{},"A declared fact is one name, and every dotted name is refused."," A path lookup splits on the dot, so a tool that declared ",[870,2196,1981],{}," writes the map key ",[870,2199,1981],{},", and the rule path ",[870,2202,1981],{},[870,2204,2038],{}," and then ",[870,2207,2208],{},"lifecycle",". No lookup reaches that key. The tool builds, the rule saves, and the engine answers ",[870,2211,1880],{}," on every call of that tool for ever. One regex refuses the whole class, and it refuses a dotted name rooted at a reserved root as one case of it.",[807,2214,2215,2218,2219,2221],{},[811,2216,2217],{},"Inside a fan out the remaining cost is real."," A rule naming ",[870,2220,1978],{}," over a 200-wide send costs 200 CRM lookups, one per call, before any of them reaches a handler. The resolution therefore caches each answer for the run.",[936,2223,2226],{"className":2224,"code":2225,"language":941,"meta":942},[939],"cache scope   one run\nkey           \u003Crun id>:\u003Cfact name>:\u003Carguments hash>\n",[870,2227,2225],{"__ignoreMap":942},[807,2229,2230],{},"One run asking the same question about the same recipient twice pays once. Asking about 200 different recipients still pays 200 times, which is correct: those are 200 different questions, and answering each one is what the rule is for.",[807,2232,2233,1102,2236,2239],{},[811,2234,2235],{},"The key reads the whole validated arguments, and never the subset one resolver read.",[870,2237,2238],{},"PolicyRequest.arguments_hash"," already carries that digest, and the caller computed it for the approval comparison. A resolver that declared its own reads would answer the cached value for a second recipient the moment it under-declared one, which is a wrong decision rather than a slow one. The whole-argument key is never wrong. It is only ever too precise: a repeat call that changed one unread field pays twice.",[807,2241,2242,2245,2246,2249],{},[811,2243,2244],{},"The cache is a cost, and never a guarantee."," It holds one answer for one run, so an agent that creates the recipient and then sends still reads ",[870,2247,2248],{},"recipient_is_new: true"," at the send. The map is process local and bounded, by a lifetime and by an entry count, because a worker outlives every run it serves. An evicted answer is resolved again, and the second answer is the live one. Read the cache as the thing that makes a repeat free, and never as the thing that makes two calls agree.",[807,2251,2252,1102,2255,2257],{},[811,2253,2254],{},"The retry guarantee is the idempotency journal, and not this cache.",[870,2256,916],{}," reads the journal before the checkpoint, so a segment that retries a call it already completed returns the stored result and reaches no decision at all. A cache that promised the same thing would promise it twice, and weakly. The run id is still part of the key, so no run reads another run's answer.",[807,2259,2260,2263,2264,2266],{},[811,2261,2262],{},"A resolver that raises resolves nothing, and the engine denies."," The rule names the fact, the fact map does not hold it, and ",[870,2265,1880],{}," follows from the rule already stated above. A second refusal built beside the resolver would answer without a decision row. A failure is not cached: a transient outage would otherwise deny for the rest of the run after it cleared.",[807,2268,2269,2272,2273,2275,2276,2279,2280,2282],{},[811,2270,2271],{},"The resolution raises nothing at all, and it resolves less instead."," The two reads it makes fail the same way a resolver does. A rule set that does not read leaves the intersection unknown, and a condition tree the walker refuses hides the paths of one rule. Either one resolves nothing and lets ",[870,2274,1027],{}," answer, because the engine holds a refusal for both: an unreadable rule set is ",[870,2277,2278],{},"deny(policy_unavailable)",", and a tree that saved and does not evaluate is a ",[870,2281,1746],{}," that names the rule. A resolution that raised would replace each of those answers with an internal error, and the run would end with no decision row.",[832,2284,2285],{"id":255},"Approvals",[807,2287,2288,2290],{},[870,2289,977],{}," creates one durable row, and the run waits on it.",[936,2292,2295],{"className":2293,"code":2294,"language":941,"meta":942},[939],"PolicyEngine -> require_approval\n  -> ApprovalService.create()      exact proposal, hash, TTL, idempotency key\n  -> ToolInvoker raises ApprovalRequired, so the segment ends\n  -> RunManager.mark_waiting()\n  -> the Inngest wait, with a timeout read from approval.expires_at\n  -> a person resolves the approval on any surface\n  -> exactly one atomic resolution\n  -> the wait receives the signal, and the run resumes\n",[870,2296,2294],{"__ignoreMap":942},[807,2298,2299,2300,2303],{},"An ",[811,2301,2302],{},"admission"," approval reverses the first two steps, because no segment stands between the decision and the row.",[936,2305,2308],{"className":2306,"code":2307,"language":941,"meta":942},[939],"PolicyEngine -> require_approval\n  -> RunManager.mark_waiting(run, 'approval', None)\n  -> ApprovalService.create()      run_deadline is None; the TTL stands alone\n  -> RunManager.mark_waiting(run, 'approval', approval.id)\n  -> the Inngest dispatch event, whose first step is the wait\n  -> a person resolves the approval on any surface\n  -> exactly one atomic resolution\n  -> the wait receives the signal, and the run resumes\n",[870,2309,2307],{"__ignoreMap":942},[807,2311,2312,2315,2316,2319,2320,2323],{},[811,2313,2314],{},"The hold comes before the row."," Create first and a crash between the two leaves a ",[870,2317,2318],{},"queued"," run holding an orphan approval, which the reaper then executes with nobody having approved it. ",[1157,2321,373],{"href":2322},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime\u002Fexecution#an-admission-approval-is-held-before-its-row-is-written"," owns that order and the ending it protects.",[807,2325,1092,2326,2328,2329,2331],{},[870,2327,977],{}," stops the segment rather than returning a pending value, because a value is just another tool result to the model. ",[1157,2330,191],{"href":190}," owns that boundary. Policy only produces the decision.",[807,2333,2334],{},"An approval stores enough context to decide without opening the run.",[936,2336,2339],{"className":2337,"code":2338,"language":941,"meta":942},[939],"run_id, root_run_id, raised_by, action\ntarget summary, exact proposed arguments and content\nthe handler preview line, when the tool declares `approval_preview`\narguments_hash\nreason and matched policy\nidempotency key\nexpires_at\ncontinuation identity when one is needed\n",[870,2340,2338],{"__ignoreMap":942},[807,2342,1018,2343,1102,2346,2349,2350,909,2352,2355,2356,2359],{},[811,2344,2345],{},"The row stores no definition.",[870,2347,2348],{},"agent.runs"," pairs its definition key with\n",[870,2351,1246],{},[870,2353,2354],{},"kind",", and this table holds no kind. A single column key\nwould let an approval name another tenant's definition. The run answers the\ndefinition, and ",[1157,2357,2358],{"href":344},"human review inbox"," reads it for the selected row.",[807,2361,2362,2363,2368,2369,909,2371,2373,2374,2377,2378,2380],{},"An approval row records ",[811,2364,2365],{},[870,2366,2367],{},"raised_by",", not a policy checkpoint: ",[870,2370,2302],{},[870,2372,1609],{}," come from this plane, and ",[870,2375,2376],{},"node"," comes from a workflow author, which is not a policy decision at all. All three produce the same row and appear in the same inbox. See ",[1157,2379,2358],{"href":344}," for the product view.",[1130,2382,2384],{"id":2383},"one-clock-one-waiter","One clock, one waiter",[807,2386,2387,2390],{},[870,2388,2389],{},"approval.expires_at"," is the only expiry clock. The Inngest wait timeout is computed from it. Two independent timers drift, and the run then hangs after the approval died, or resumes on an approval that is still pending.",[807,2392,2393,1102,2396,2399,2400,2402,2403,1102,2406,2409,2410,2413],{},[811,2394,2395],{},"The Run's own wall clock caps that expiry, or a person's decision is thrown\naway.",[870,2397,2398],{},"max_run_duration"," counts the waits, and ",[870,2401,973],{}," comes from a rule\nthat has never read the Run. A definition with a one hour duration and a 24 hour\napproval TTL resumes at hour 25, the executor subtracts a duration remainder of\nzero, and the Run ends ",[870,2404,2405],{},"succeeded",[811,2407,2408],{},"without running the call the person\napproved",". That is the exact failure the deterministic resume exists to stop:\nthe approval reads ",[870,2411,2412],{},"approved",", and no email was sent.",[936,2415,2418],{"className":2416,"code":2417,"language":941,"meta":942},[939],"expires_at = min(now + approval_ttl, run.started_at + run.max_run_duration)\n",[870,2419,2417],{"__ignoreMap":942},[807,2421,2422,2425,2426,2428],{},[870,2423,2424],{},"ApprovalService.create()"," is the one writer of the column, so it is the one\nplace the cap belongs. The rule holds for every ",[870,2427,2367],{}," value. An admission\napproval takes the same form, and its Run has not started, so the second term is\nabsent and the TTL stands alone.",[807,2430,2431],{},"That rule has one consequence for admission. An admission approval must also dispatch the Inngest function, and the first step of that function is the wait. A waiting run holds no worker, so this costs nothing, and it gives the admission approval the same timeout owner as an action approval. A run that waits for approval with nobody waiting on the clock never ends.",[1130,2433,2435],{"id":2434},"somebody-must-learn-about-it","Somebody must learn about it",[807,2437,2438,2439,2442,2443,2446],{},"An approval nobody sees expires, and the work dies quietly. That is easy to miss for an interactive run, because the person is already in the conversation. It is the normal case for a ",[811,2440,2441],{},"machine run",": a trigger run has no conversation, so ",[1157,2444,2445],{"href":230},"Channel Gateway"," has no session and drops the outbound intent.",[807,2448,2449,2452],{},[870,2450,2451],{},"ApprovalNotifier"," closes that hole with one rule, and it is not a notification system.",[936,2454,2457],{"className":2455,"code":2456,"language":941,"meta":942},[939],"approval created\n  -> the run has a conversation with a channel session\n       yes -> one OutboundIntent on that session\n       no  -> one OutboundIntent to the person who authored the trigger,\n              over their linked channel\n  -> Human Review always lists it, whatever happened above\n",[870,2458,2456],{"__ignoreMap":942},[807,2460,2461],{},"The trigger author is the right fallback, because that person chose to start work without a person watching it. Nothing else is added: the notifier writes no row, keeps no queue, and reuses the intent the Gateway already delivers and dedupes.",[807,2463,2464],{},"Human Review remains the guaranteed floor. A notification is a prompt to look, never the decision surface.",[1130,2466,2468],{"id":2467},"states-and-writers","States and writers",[837,2470,2471,2481],{},[840,2472,2473],{},[843,2474,2475,2478],{},[846,2476,2477],{},"State",[846,2479,2480],{},"Written by",[859,2482,2483,2494,2507,2517],{},[843,2484,2485,2490],{},[864,2486,2487],{},[870,2488,2489],{},"pending",[864,2491,2492],{},[870,2493,2424],{},[843,2495,2496,2504],{},[864,2497,2498,2500,2501],{},[870,2499,2412],{}," \u002F ",[870,2502,2503],{},"rejected",[864,2505,2506],{},"a person, through any surface",[843,2508,2509,2514],{},[864,2510,2511],{},[870,2512,2513],{},"expired",[864,2515,2516],{},"the wait timeout, when it fires",[843,2518,2519,2524],{},[864,2520,2521],{},[870,2522,2523],{},"cancelled",[864,2525,2526,913,2529,2532],{},[870,2527,2528],{},"RunManager.cancel()",[870,2530,2531],{},"RunManager.fail()",", and the segment supersede",[807,2534,2535],{},"There is no sweeper job. Every terminal state has a real writer.",[807,2537,2538,2543,2544,2546,2547,2549],{},[811,2539,2540,2542],{},[870,2541,2523],{}," has three writers, and only the first is obvious."," A model may\npropose several calls at once, and the first one that needs a person ends the\nsegment. ",[870,2545,908],{}," then cancels every ",[870,2548,2489],{}," approval of the run other\nthan the id the segment stopped on, because a row nobody waits on must never sit\nin a person's inbox. A segment that stopped on no approval cancels all of them.",[807,2551,2552,2553,2555,2556,2559,2560,2563,2564,2567,2568,2571,2572,2574,2575,2577],{},"The third is the terminal write. ",[870,2554,2531],{}," cancels the pending\napprovals of the run it ended, because a run that ended holds no waiter: the\nsegment that filed a row failed before it superseded it, or a resume left the\nrun ",[870,2557,2558],{},"waiting"," and the loop ended it on ",[870,2561,2562],{},"orphan_approval",". Without that write\n",[870,2565,2566],{},"resolve"," still accepts the row and the approve route answers ",[870,2569,2570],{},"200",", so a\nperson answers a question on a run that ended minutes ago.\n",[1157,2573,373],{"href":372},"\nowns both calls, and each writes ",[870,2576,2523],{}," through the same conditional update.",[936,2579,2581],{"className":1170,"code":2580,"language":1172,"meta":942,"style":942},"class ApprovalRepository(Protocol):\n    async def create(self, approval: Approval) -> Approval: ...\n    async def get(self, approval_id: UUID) -> Approval | None: ...\n    async def resolve(\n        self,\n        approval_id: UUID,\n        *,\n        resolution: Literal['approved', 'rejected', 'expired', 'cancelled'],\n        actor_id: UUID | None,\n    ) -> Approval | None: ...\n",[870,2582,2583,2588,2593,2598,2603,2607,2612,2616,2621,2626],{"__ignoreMap":942},[1176,2584,2585],{"class":1178,"line":22},[1176,2586,2587],{},"class ApprovalRepository(Protocol):\n",[1176,2589,2590],{"class":1178,"line":32},[1176,2591,2592],{},"    async def create(self, approval: Approval) -> Approval: ...\n",[1176,2594,2595],{"class":1178,"line":233},[1176,2596,2597],{},"    async def get(self, approval_id: UUID) -> Approval | None: ...\n",[1176,2599,2600],{"class":1178,"line":244},[1176,2601,2602],{},"    async def resolve(\n",[1176,2604,2605],{"class":1178,"line":264},[1176,2606,1329],{},[1176,2608,2609],{"class":1178,"line":222},[1176,2610,2611],{},"        approval_id: UUID,\n",[1176,2613,2614],{"class":1178,"line":360},[1176,2615,1334],{},[1176,2617,2618],{"class":1178,"line":368},[1176,2619,2620],{},"        resolution: Literal['approved', 'rejected', 'expired', 'cancelled'],\n",[1176,2622,2623],{"class":1178,"line":375},[1176,2624,2625],{},"        actor_id: UUID | None,\n",[1176,2627,2628],{"class":1178,"line":157},[1176,2629,2630],{},"    ) -> Approval | None: ...\n",[807,2632,2633,2635,2636,2639],{},[870,2634,2566],{}," is a conditional update with ",[870,2637,2638],{},"WHERE status = 'pending'",". One writer wins, and a later writer receives the resolved state. No distributed lock is used.",[1130,2641,2643],{"id":2642},"the-timeout-and-the-person-can-race-and-the-person-must-win","The timeout and the person can race, and the person must win",[807,2645,2646],{},"An approval sitting at 71 hours 59 minutes is resolved by a person in the same second the wait timeout fires. Both write, and one loses.",[936,2648,2651],{"className":2649,"code":2650,"language":941,"meta":942},[939],"the person wins    the timeout's UPDATE touches no row\nthe timeout wins   the person reads `expired`, which is the honest answer\n",[870,2652,2650],{"__ignoreMap":942},[807,2654,2655,2656,2659,2660,2662],{},"The second case needs nothing. ",[811,2657,2658],{},"The first case is the bug",", and it is silent: the Inngest wait has already returned on its timeout, so the run would finalize without executing a call somebody really approved. The approval row would read ",[870,2661,2412],{}," and no email would exist.",[807,2664,2665],{},"So the timeout path never assumes it won.",[936,2667,2670],{"className":2668,"code":2669,"language":941,"meta":942},[939],"the wait returns on its timeout\n  -> resolve the approval to expired\n       one row  -> nobody answered; finalize without executing\n       no row   -> re-read the approval\n                     approved -> execute it, exactly as the resolved path does\n                     rejected -> write the rejected result, and continue\n",[870,2671,2669],{"__ignoreMap":942},[807,2673,2674],{},"The re-read costs one indexed query on a path that runs once per expiry. A person who pressed Approve inside the last second gets what they asked for.",[1130,2676,2678],{"id":2677},"before-an-approved-action-runs","Before an approved action runs",[807,2680,2681,2684,2685,2687],{},[870,2682,2683],{},"ApprovalService"," re-checks five things, immediately before the effect, and ",[870,2686,916],{}," adds a sixth.",[2689,2690,2691,2694,2697,2700,2703,2706],"ol",{},[1516,2692,2693],{},"The row names this run and this organization.",[1516,2695,2696],{},"The decision was made before the row expired.",[1516,2698,2699],{},"The approver holds the scope the action needs. An approval cannot grant authority the approver lacks.",[1516,2701,2702],{},"The arguments hash still matches the stored proposal.",[1516,2704,2705],{},"The required target state is unchanged, where the action declares one.",[1516,2707,2708,2709,2711],{},"The rule that asked still holds. The row stores ",[870,2710,962],{},", and the rule deciding the fresh decision must be one of them.",[807,2713,2714],{},"A stale approval fails closed and needs a fresh decision.",[807,2716,2717,2720],{},[811,2718,2719],{},"Check 6 belongs to the invoker, and not to the service."," Checks 1 to 5 read the row alone. Check 6 compares the row against the decision the engine made for this call now, and the service is never handed one. Without it, an admin who disables the rule a person answered and saves a second rule leaves the stored answer covering a question nobody asked: none of checks 1 to 5 moves when the rule set is edited.",[807,2722,1018,2723,1102,2726,2728,2729,2731,2732,2734],{},[811,2724,2725],{},"The test reads the deciding rule, and never the whole matched set.",[870,2727,962],{}," names every rule that matched, including one whose ",[870,2730,1014],{}," lost on precedence. Compared as a set, an admin who saves an unrelated ",[870,2733,1014],{}," rule for the same action widens the fresh side and throws the person's answer away, though nothing about their call changed. Precedence picks one rule, and that rule is the one that asked. The row still stores the whole set, because an auditor asks which rules governed the call.",[807,2736,2737,2738,2741,2742,2745,2746,2748,2749,2751],{},"A decision that names no rule is covered by every row, which is what a workflow ",[870,2739,2740],{},"approval"," node needs. An admission row never reaches this check at all: ",[870,2743,2744],{},"authorizes()"," compares the action, and ",[870,2747,1061],{}," never equals a tool name, so its ",[870,2750,962],{}," is the audit alone.",[807,2753,2754,2760,2761,2763],{},[811,2755,2756,2757,2759],{},"Two ",[870,2758,977],{}," rules that alternate re-ask each time, and that is the answer."," A rule the person never saw is asking, so the platform asks. They end it by rejecting, which ",[870,2762,916],{}," makes final.",[807,2765,2766,2769,2770,2773,2774,2777,2778,2780,2781,2784,2785,2787,2788,2791,2792,2795],{},[811,2767,2768],{},"A fresh decision means a fresh row, and the idempotency key is what used to\nstop one."," The refused call is decided again and files a new proposal on\n",[870,2771,2772],{},"\u003Crun_id>:\u003Cstep_path>:\u003Cargs_hash>",". Checks 1 and 4 change that key, so both\nalready asked a person again. Check 2, check 3 and the status test leave it\nunchanged, and ",[870,2775,2776],{},"uq_approvals_run_id_idempotency_key"," held every row of one key\nuntil ENG-2156. The service read the terminal row back, ",[870,2779,916],{}," raised\n",[870,2782,2783],{},"ApprovalRequired"," naming a row a person had already answered, and the wait could\nonly time out: the run ended ",[870,2786,2405],{}," with ",[870,2789,2790],{},"partial_reason=approval_expired","\nand nobody was asked. The index is now partial on ",[870,2793,2794],{},"status = 'pending'",", so a live\nproposal still dedupes a replayed segment and a terminal one releases its key.",[807,2797,1018,2798,2801,2802,2804,2805,2807,2808,2811],{},[811,2799,2800],{},"A row a person rejected must not produce a fresh question."," They answered,\nand a second card on the same call is a loop that ends only on a ceiling. The\nindex cannot hold that rule, because it reads a key and never an answer. So\n",[870,2803,916],{}," reads the rejection of the claim before it files, and it returns a\n",[870,2806,2503],{}," result to the segment instead. Change the index without that guard and\nthe fix trades a silent stall for a silent loop. ",[1157,2809,2810],{"href":190},"Tools and\nintegrations","\nowns that path.",[807,2813,1018,2814,2817,2818,2821],{},[811,2815,2816],{},"Check 1 is not implied by the approval id."," One ",[870,2819,2820],{},"parallel"," workflow node\nstarts several child runs of one organization, and each child proposes the same\ntool with the same arguments. The rows are then equal in every field a check\nreads except the run. Compare the tool and the hash alone, and one child's\napproval authorizes a sibling's call.",[807,2823,1018,2824,2834,2835,2837],{},[811,2825,2826,2827,2830,2831,822],{},"Check 2 reads ",[870,2828,2829],{},"resolved_at",", and never ",[870,2832,2833],{},"now()"," A person who presses\nApprove in the last second produces a row that reads ",[870,2836,2412],{}," with an expiry\nalready in the past. A check written against the clock refuses exactly the call\nthe race rule above says must run, and the person's decision is thrown away a\nsecond time. The row is honest about when the decision was made, so the check\nreads that.",[807,2839,2840,2841,2843],{},"Check 5 has no input in V1. No action declares a target state, because a\ndeclared fact reaches a checkpoint through ",[870,2842,2110],{}," and no tool declares\none yet. The check is a rule this page holds, not code the service ships.",[807,2845,2846,2847,2850,2851,2854,2855,822],{},"Approval and idempotency answer different questions. Approval answers ",[811,2848,2849],{},"may this happen",". Idempotency answers ",[811,2852,2853],{},"has this already happened",". See ",[1157,2856,200],{"href":269},[832,2858,2860],{"id":2859},"limits-and-accrual","Limits and accrual",[807,2862,2863],{},"Policy owns the ceiling values. It owns no counter and no limiter.",[837,2865,2866,2879],{},[840,2867,2868],{},[843,2869,2870,2873,2876],{},[846,2871,2872],{},"Shape",[846,2874,2875],{},"Where it lives in V1",[846,2877,2878],{},"Enforced by",[859,2880,2881,2894,2905,2919],{},[843,2882,2883,2886,2889],{},[864,2884,2885],{},"Per-run ceilings: turns, tool calls, duration",[864,2887,2888],{},"the definition budget, frozen into the run snapshot",[864,2890,2891,2893],{},[870,2892,908],{},", per segment",[843,2895,2896,2899,2902],{},[864,2897,2898],{},"Per-run cost",[864,2900,2901],{},"the definition budget",[864,2903,2904],{},"accrual, against the usage meter",[843,2906,2907,2910,2917],{},[864,2908,2909],{},"Per-organization daily cost",[864,2911,2912,2913,2916],{},"an ",[870,2914,2915],{},"agent.cost_ceilings"," row",[864,2918,2904],{},[843,2920,2921,2924,2927],{},[864,2922,2923],{},"Concurrency and rate",[864,2925,2926],{},"Inngest flow control",[864,2928,2929],{},"Inngest",[807,2931,2932,2933,2935],{},"Concurrency is deliberately not a policy row. Inngest reads its concurrency limit when the function is declared, so a per-organization row could not reach it without a deploy. V1 runs one platform-wide concurrency key on ",[870,2934,1246],{},". A per-organization rate limit waits until a customer needs one.",[807,2937,2938,2939,2941],{},"So ",[870,2940,2915],{}," holds cost only. The name refuses the rate limit and the concurrency cap on sight, and the table stays small.",[936,2943,2946],{"className":2944,"code":2945,"language":941,"meta":942},[939],"agent.cost_ceilings\n  organization_id\n  kind          daily_cost\n  value_cents\n  created_at\n  updated_at\n",[870,2947,2945],{"__ignoreMap":942},[807,2949,2950,2953,2954,2957],{},[870,2951,2952],{},"PUT \u002Fapi\u002Fv1\u002Fagentic\u002Flimits"," is an upsert, so ",[870,2955,2956],{},"(organization_id, kind)"," is the key.",[1130,2959,2961],{"id":2960},"how-accrual-behaves","How accrual behaves",[807,2963,2964,2965,1386,2968,2970,2971,2973,2974,2976,2977,2979,2980,1386,2983,2985],{},"Accrual has one definition and ",[811,2966,2967],{},"five call sites, which take four shapes",[870,2969,904],{}," calls it at admission. ",[870,2972,908],{}," calls it at the top of each agent segment and ",[870,2975,912],{}," at each node, and those two pass the same arguments, so the table below gives them one row. ",[870,2978,916],{}," calls it before a tool whose spec sets ",[870,2981,2982],{},"metered",[870,2984,920],{}," calls it before its model call, and that component lands in Phase 5. All of them read the canonical usage meter, and none owns a counter.",[807,2987,2988,2994,2995,2997,2998,3000],{},[811,2989,2990,2991,822],{},"Not ",[870,2992,2993],{},"RunExecutor"," It claims the Run and dispatches it by kind, once per function run, so it has already returned when segment 4 begins. The segment loop lives in the Inngest function, and the code inside each step is ",[870,2996,908],{}," or ",[870,2999,912],{},". Those two are the only components that run at every segment and every node.",[936,3002,3005],{"className":3003,"code":3004,"language":941,"meta":942},[939],"run cost so far           sum of ai_usage_log by root_run_id\norganization cost today   sum of ai_usage_log by organization_id, for the current UTC day\n",[870,3006,3004],{"__ignoreMap":942},[807,3008,3009,3010,3013],{},"The meter holds model spend ",[811,3011,3012],{},"and"," metered vendor spend. A discovery run that spends nothing on tokens and a lot at a search vendor must stop at the same ceiling. A vendor call that is metered nowhere is a hole in the budget.",[807,3015,3016],{},"Accrual cannot stop a unit that is already in flight, so a ceiling always overshoots by the unit it was checked before.",[807,3018,3019,3022,3023,3026],{},[811,3020,3021],{},"For a run ceiling that is the whole story. For an organization ceiling it is not."," The check is a ",[870,3024,3025],{},"SUM"," with no lock and no reservation, so twenty runs in flight can all read the same total and all decide to proceed.",[936,3028,3031],{"className":3029,"code":3030,"language":941,"meta":942},[939],"organization overshoot  \u003C=  the Inngest concurrency limit  x  the largest single unit cost\n",[870,3032,3030],{"__ignoreMap":942},[807,3034,3035],{},"Both terms are ours, so the bound is knowable and it is written down beside the limit. V1 does not add a reservation or a distributed counter to close it: the counter would put Redis on the correctness path, and it would fail in the one case it exists for, a worker dying while holding a reservation.",[807,3037,3038,3039,3042],{},"The day is ",[811,3040,3041],{},"UTC",". Two workers in two regions must agree which day a call belongs to, and a local day boundary makes that a coin toss.",[837,3044,3045,3055],{},[840,3046,3047],{},[843,3048,3049,3052],{},[846,3050,3051],{},"Checked before",[846,3053,3054],{},"Overshoot",[859,3056,3057,3065,3073],{},[843,3058,3059,3062],{},[864,3060,3061],{},"An agent segment",[864,3063,3064],{},"one segment, which is why a segment carries its own turn ceiling",[843,3066,3067,3070],{},[864,3068,3069],{},"A workflow node",[864,3071,3072],{},"one node, and one node per branch of a parallel set",[843,3074,3075,3078],{},[864,3076,3077],{},"A metered tool call",[864,3079,3080],{},"one call",[807,3082,3083,3086,3087,3089],{},[811,3084,3085],{},"The metered call check is what makes the node bound honest."," A node is not always small. Signals Search searches people across every company that survived pruning, inside one step, and that step can make hundreds of paid vendor calls. Check only at the node boundary and the overshoot is the whole fan out, not one node. The ",[870,3088,2982],{}," flag on the tool is what closes it.",[807,3091,3092],{},"An unmetered read costs nothing and needs no check. The extra read is one indexed lookup, and only a paid call pays for it.",[807,3094,3095,1102,3098,3101],{},[811,3096,3097],{},"The day sum counts every row of the organization, and it filters no source.",[870,3099,3100],{},"ai_usage_log"," is the cost truth of the whole product, so a Signals sweep and an agent segment spend the same day cap. The ceiling answers \"what this organization spent on AI today\", and an organization that spends it outside the agentic platform has still spent it.",[807,3103,3104,3110],{},[811,3105,3106,3107,3109],{},"An organization with no ",[870,3108,2915],{}," row has no day cap."," The check reads one row by its primary key, and an absent row allows. A cap nobody set must not stop work.",[807,3112,3113,3119],{},[811,3114,3115,3116,822],{},"The comparison is ",[870,3117,3118],{},">="," A run that spent exactly its ceiling has spent its whole budget, so the next unit is refused.",[807,3121,3122,1102,3131,3134],{},[811,3123,3124,3125,3127,3128,822],{},"A meter read that fails answers ",[870,3126,1746],{},", and it carries ",[870,3129,3130],{},"fault_code",[870,3132,3133],{},"AccrualChecker"," never raises, so a failed read still returns a decision, and it returns the same one the meter itself takes: a run whose spend cannot be counted cannot be governed. A transient fault is read again first, three attempts in all, because one pooler restart must not end a run.",[807,3136,3137,1102,3140,3142],{},[811,3138,3139],{},"One read reaches Sentry one time in each window, and the log receives every call.",[870,3141,916],{}," checks the meter before each metered call. Reported per call, a fan out node gives one outage hundreds of events, and Sentry drops other issues to its rate limit. The checker holds the time each read last reported, and it reports again once the window passes.",[807,3144,3145],{},"The key names the read, and never the subject. The run subject holds a run id, so a key on it grows by one entry for every run of that fan out. The reads are two: the run tree and the organization day. One outage that reaches both gives two events.",[807,3147,3148,3149,822],{},"An outage that continues gives one event for each read in each window. An operator reads that the outage is not over. Every run that meets it ends under ",[870,3150,3151],{},"metering_unavailable",[807,3153,3154,1102,3157,3160],{},[811,3155,3156],{},"The bound belongs to one checker.",[870,3158,3159],{},"build_platform"," caches one graph for each running event loop, so a second loop or a second worker process gives one more report. One checker gives two events each minute at most: one window, and two reads.",[807,3162,1018,3163,3166],{},[811,3164,3165],{},"The window is fixed, and it never slides."," The checker stamps the time it reported, and never the time of the last call. A stamp on every call moves the window forward by the gap between two calls, so a busy outage reports one time and then goes silent.",[807,3168,1018,3169,3172],{},[811,3170,3171],{},"The bound is a time window, and never a success."," Reporting the first failure after each success looks correct. A pooler at connection saturation answers some reads and fails others, so every failure follows a success. That design gives one event for every second call, and the fan out is the shape that matters.",[807,3174,1018,3175,3178,3179,3182],{},[811,3176,3177],{},"This bound is not the bound a static defect takes."," A wiring defect cannot change without a deploy, so an entry that never expires fits it, and ",[870,3180,3181],{},"ToolInvoker._contract_fault"," uses one. An outage starts and it ends, so that entry would report the first outage and hide every one after it.",[807,3184,3185,3186,963,3188,3190,3191,3194,3195,3198,3199,3202,3203,3206,3207,3209],{},"The fault is not a ceiling, and the decision says so in a field rather than in prose. ",[870,3187,3130],{},[870,3189,3151],{},", the code ",[870,3192,3193],{},"UsageMeter"," already uses for a write it could not count, and a rule refusal leaves it null. ",[811,3196,3197],{},"Every caller reads the field",": the run ends ",[811,3200,3201],{},"failed under that code",", never partial and never ",[870,3204,3205],{},"budget_exhausted",". An operator grouping failures on the money code would open ",[870,3208,3100],{}," and the run ceilings, and both show headroom.",[807,3211,3212,3213,3216,3217,909,3219,3221],{},"A second fault code sits beside it. ",[870,3214,3215],{},"runtime_contract"," answers a caller that passed an argument this plane cannot read, such as a scope that is not a scope. It is the code ",[870,3218,908],{},[870,3220,912],{}," already use for a broken contract, so a defect groups with the other defects.",[807,3223,3224,3231,3232,3234,3235,3238],{},[811,3225,3226,3227,3230],{},"A fault is a platform stop, and ",[870,3228,3229],{},"continue_on_error"," never tolerates one."," A node that declares it says the author expects that step to fail sometimes, and a database the meter could not read is not that. Tolerated, every later node runs and the run reports success in the middle of an outage. The ",[811,3233,870],{}," carries that rule across a step boundary, and never a flag: a child start crosses a durable step as JSON, so a boolean is dropped there and the same fault reaches the parent tolerated. ",[870,3236,3237],{},"run_tool"," is the one caller that passes a flag. It reads the mark the tool layer wrote, and it crosses no boundary carrying one.",[807,3240,3241,3242,3245],{},"The reason carries no vendor payload, and no argument the caller passed. ",[870,3243,3244],{},"RunResult.summary"," has no ceiling and the run detail route returns it to an authenticated user, and a PostgREST error stringifies to its message, its SQLSTATE, its hint and its details. The exception text stays in the log and in Sentry.",[807,3247,3248,3249,3256,3257,3261],{},"A run stopped by a ceiling ",[811,3250,3251,3252,3255],{},"succeeds with a ",[870,3253,3254],{},"partial_reason",", when the run produced output",". It fails when it produced none. One rule decides it, and ",[1157,3258,3260],{"href":3259},"\u002Fengineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime\u002Fexecution#result-and-error","runtime execution"," states it.",[832,3263,3265],{"id":3264},"live-rules-and-the-cache-delay","Live rules and the cache delay",[807,3267,3268],{},"An in-flight run reads the current rule set at its next checkpoint. A safety change must not wait for the next run.",[807,3270,3271,3272,3275],{},"Reading every rule from Postgres on every tool call is wasteful, so each worker caches an organization's rule set for ",[811,3273,3274],{},"30 seconds",". That is the whole delay. State it plainly: a new deny rule reaches every worker and every in-flight run within 30 seconds, at that run's next checkpoint.",[807,3277,3278],{},"The definition snapshot is frozen for run stability. Policy, tool state and credentials are not. That split is what allows emergency revocation.",[807,3280,3281,1102,3284,3287,3288,3291,3292,3294],{},[811,3282,3283],{},"One entry holds one organization, and never one action.",[870,3285,3286],{},"PolicyRepository.enabled_rules()"," reads every enabled rule of the organization, and ",[870,3289,3290],{},"RuleCache"," matches the exact name, the domain wildcard and the star in memory. Keying on the action instead would put a query on the wire for each action a run touches, and it would leave the ",[870,3293,1654],{}," kill switch out of every answer it did not ask for.",[807,3296,3297,3300],{},[811,3298,3299],{},"An empty answer is a cached answer."," Most organizations hold no rule, so a cache that skipped the empty set would read Postgres on every tool call of the common case.",[807,3302,3303,3306,3307,3310,3311,2787,3313,3316,3317,3320],{},[811,3304,3305],{},"A rule set that cannot be read denies."," The repository tries a transient fault three times, as ",[870,3308,3309],{},"MeterAccrualChecker"," does, and then raises. The engine answers ",[870,3312,1746],{},[870,3314,3315],{},"fault_code = policy_unavailable",". Failing open is defensible, because a rule restricts and never grants — and it is refused, because the kill switch would then not fire for as long as the fault lasts. The same rule covers a rule set the database answers short: ",[870,3318,3319],{},"PGRST_DB_MAX_ROWS"," truncates in silence, and the dropped row may be the deny.",[832,3322,3324],{"id":3323},"the-decision-log","The decision log",[807,3326,3327],{},"Every decision that changed an outcome is durable.",[837,3329,3330,3340],{},[840,3331,3332],{},[843,3333,3334,3337],{},[846,3335,3336],{},"Decision",[846,3338,3339],{},"Recorded as",[859,3341,3342,3352,3363,3373,3387],{},[843,3343,3344,3347],{},[864,3345,3346],{},"Any admission decision",[864,3348,3349,3350,2916],{},"a ",[870,3351,1031],{},[843,3353,3354,3359],{},[864,3355,3356,3357],{},"Any ",[870,3358,1746],{},[864,3360,3349,3361,2916],{},[870,3362,1031],{},[843,3364,3365,3369],{},[864,3366,3356,3367],{},[870,3368,977],{},[864,3370,3349,3371,2916],{},[870,3372,1031],{},[843,3374,3375,3378],{},[864,3376,3377],{},"Any accrual stop",[864,3379,3349,3380,3382,3383,3386],{},[870,3381,1031],{}," row; ",[870,3384,3385],{},"run_id"," is null when it refused a start",[843,3388,3389,3394],{},[864,3390,3391,3392],{},"An action ",[870,3393,1014],{},[864,3395,3396],{},"attributes on the tool span that already exists",[807,3398,3399],{},"An agent run makes hundreds of allowed tool calls, and each already opens one span. Writing a second row for each of them doubles the write cost of the hot path and adds nothing an auditor cannot read from the span tree. Everything that refused, gated or stopped work keeps its own row.",[936,3401,3404],{"className":3402,"code":3403,"language":941,"meta":942},[939],"agent.policy_decisions\n  id\n  organization_id\n  run_id\n  checkpoint         admission | action | accrual\n  action\n  decision           allow | deny | require_approval\n  reason\n  matched_policy_ids\n  arguments_hash\n  principal    jsonb\n  created_at\n",[870,3405,3403],{"__ignoreMap":942},[807,3407,3408,3410,3411,3413,3414,3416,3417,913,3419,909,3422,3424,3425,3427,3428,822],{},[870,3409,2056],{}," holds five identifiers of the run grant plus two scope sets. It is ",[811,3412,1733],{}," the shape ",[870,3415,1549],{}," holds: that column keeps three keys, because the run row carries ",[870,3418,1246],{},[870,3420,3421],{},"id",[870,3423,1588],{}," as columns of its own. An audit row is self-contained, so a decision stays readable after ",[870,3426,1599],{}," clears its ",[870,3429,3385],{},[936,3431,3434],{"className":3432,"code":3433,"language":941,"meta":942},[939],"principal\n  organization_id\n  run_id\n  definition_id\n  user_id\n  trigger_id\n  scopes            the frozen ceiling\n  effective_scopes  what stage 1 decided on, or null\n",[870,3435,3433],{"__ignoreMap":942},[807,3437,3438,3448,3449,3451,3452,3455,3456,3459],{},[811,3439,3440,3443,3444,3447],{},[870,3441,3442],{},"scopes"," is the ceiling, and ",[870,3445,3446],{},"effective_scopes"," is the answer."," Stage 1 decides on the live intersection, so an offboarded member produces a row whose ",[870,3450,3442],{}," still names the action its reason says they do not hold. Only the pair answers what the actor held at that instant: ",[870,3453,3454],{},"RoleRights"," caches for 30 seconds and ",[870,3457,3458],{},"organization_profiles"," keeps no history, so nothing else the platform stores can reconstruct it, and a row written without it is permanently unanswerable.",[807,3461,1018,3462,3467,3468,3470,3471,3473],{},[811,3463,3464,3466],{},[870,3465,3446],{}," is null when no live set was read, and never an empty list."," Null says nothing was intersected; the empty set says the actor holds nothing now. Four writes file null: accrual, which returns before the read because a budget is not a permission; a rights read that did not answer; the day cap gate, which refuses before the freeze, so no grant exists and ",[870,3469,3442],{}," already carries what the caller held; and the admission check for a workflow's ",[870,3472,1154],{},", which reads the frozen grant on purpose, microseconds after the freeze, so a second copy of it would read as a measurement nobody took.",[807,3475,3476,3477,3479,3480,3482],{},"An allowed ",[811,3478,1609],{}," writes no row, and an accrual check that passes writes no row. The only ",[870,3481,1014],{}," here is an admission one. Admission runs once per run, and the spec above records every admission decision.",[807,3484,3485,3486,3488,3489,3492,3493,909,3495,3498,3499,3502],{},"The table withholds ",[870,3487,2056],{}," from ",[870,3490,3491],{},"authenticated",", as ",[870,3494,2348],{},[870,3496,3497],{},"agent.spans"," withhold theirs. ",[870,3500,3501],{},"SELECT *"," therefore fails.",[807,3504,3505,3506,3508],{},"A deleted run keeps its decisions. The reference clears ",[870,3507,3385],{},", and the row stays.",[807,3510,3511,3514],{},[811,3512,3513],{},"A write that fails still returns the decision."," The row is the audit and never the answer, so a refusal the log could not hold is still a refusal. The engine reports the failed write and returns, because refusing to refuse is the worse of the two losses.",[807,3516,3517,3518,3521,3522,909,3525,3528],{},"Retention target: 12 months, then delete. ",[870,3519,3520],{},"retention.sweeper"," is the job, an Inngest cron beside ",[870,3523,3524],{},"run.reaper",[870,3526,3527],{},"idempotency.sweeper",". It reads the oldest rows past the window and deletes them by id, bounded, once an hour.",[832,3530,3532],{"id":3531},"core-code","Core code",[936,3534,3537],{"className":3535,"code":3536,"language":941,"meta":942},[939],"governance\u002Fpolicy\u002F\n  models.py        Principal, PolicyRule, PolicyRequest, PolicyDecision\n  # ActorIdentity is NOT here. RunManager takes one before a principal\n  # exists, so it lives at src\u002Fagentic\u002Fshared\u002Fidentity.py.\n  principals.py    PrincipalFactory, role to scope mapping\n  engine.py        PolicyEngine\n  rules.py         PolicyRepository, RuleCache\n  # ConditionEvaluator is NOT here. Three planes call it, so it lives at\n  # src\u002Fagentic\u002Fshared\u002Fconditions.py, beside bound(). See the note below.\n  facts.py         FactResolver registry\n  accrual.py       AccrualChecker              reads the usage meter\n  decisions.py     PolicyDecisionRepository\n  # AdmissionGate is NOT here. It takes a Run and a ResolvedDefinition, and\n  # both live in runtime, so it sits at\n  # src\u002Fagentic\u002Fruntime\u002Fruns\u002Fadmission.py. See the note below.\n  approvals\u002F\n    service.py     ApprovalService\n    repository.py  ApprovalRepository\n    notifier.py    ApprovalNotifier\n    models.py      Approval, ApprovalStatus\n",[870,3538,3536],{"__ignoreMap":942},[837,3540,3541,3551],{},[840,3542,3543],{},[843,3544,3545,3548],{},[846,3546,3547],{},"Component",[846,3549,3550],{},"Job",[859,3552,3553,3563,3572,3582,3592,3601,3610,3619,3631,3641,3657,3666,3676],{},[843,3554,3555,3560],{},[864,3556,3557],{},[870,3558,3559],{},"Principal",[864,3561,3562],{},"immutable effective authority for one run",[843,3564,3565,3569],{},[864,3566,3567],{},[870,3568,1427],{},[864,3570,3571],{},"compute the intersection and mint the principal",[843,3573,3574,3579],{},[864,3575,3576],{},[870,3577,3578],{},"PolicyRequest",[864,3580,3581],{},"one checkpoint, action, arguments and facts",[843,3583,3584,3589],{},[864,3585,3586],{},[870,3587,3588],{},"PolicyEngine",[864,3590,3591],{},"grant check, rule match, precedence, record",[843,3593,3594,3598],{},[864,3595,3596],{},[870,3597,3290],{},[864,3599,3600],{},"per-worker rule set with a 30 second TTL",[843,3602,3603,3607],{},[864,3604,3605],{},[870,3606,1696],{},[864,3608,3609],{},"the small deterministic condition language",[843,3611,3612,3616],{},[864,3613,3614],{},[870,3615,2110],{},[864,3617,3618],{},"resolve one declared target fact before a checkpoint",[843,3620,3621,3625],{},[864,3622,3623],{},[870,3624,3133],{},[864,3626,3627,3628],{},"compare meter sums against the ceilings, and return a ",[870,3629,3630],{},"PolicyDecision",[843,3632,3633,3638],{},[864,3634,3635],{},[870,3636,3637],{},"AdmissionGate",[864,3639,3640],{},"the two halves of admission, one either side of the Run insert",[843,3642,3643,3647],{},[864,3644,3645],{},[870,3646,3630],{},[864,3648,3649,3650,913,3652,2997,3654,3656],{},"stable ",[870,3651,1014],{},[870,3653,1746],{},[870,3655,977],{}," result",[843,3658,3659,3663],{},[864,3660,3661],{},[870,3662,2683],{},[864,3664,3665],{},"create, validate and resolve a human decision",[843,3667,3668,3673],{},[864,3669,3670],{},[870,3671,3672],{},"ApprovalRepository",[864,3674,3675],{},"atomic approval row transition",[843,3677,3678,3682],{},[864,3679,3680],{},[870,3681,2451],{},[864,3683,3684],{},"tell one person an approval is waiting",[936,3686,3688],{"className":1170,"code":3687,"language":1172,"meta":942,"style":942},"@dataclass(frozen=True)\nclass PolicyRequest:\n    checkpoint: Literal['admission', 'action', 'accrual']\n    action: str\n    arguments: dict = field(default_factory=dict)\n    facts: dict = field(default_factory=dict)\n    # The caller hashes the proposal, and the engine never hashes it again.\n    # `ToolInvoker` already holds `hash_arguments()` for the approval\n    # comparison, and two implementations of one hash disagree in silence.\n    arguments_hash: str | None = None\n\n@dataclass(frozen=True)\nclass PolicyDecision:\n    outcome: Literal['allow', 'deny', 'require_approval']\n    reason: str\n    matched_policy_ids: tuple[UUID, ...]\n    # The one rule precedence picked, or None when no rule produced this\n    # answer. It is NOT matched_policy_ids[0]: that tuple names every rule that\n    # matched, including one whose allow lost to a deny. Check 6 reads this.\n    deciding_policy_id: UUID | None = None\n    approval_ttl: timedelta | None = None\n    # The cause, when a refusal is a fault and not an answer. A denied rule and\n    # a spent ceiling both leave it None. Accrual sets metering_unavailable\n    # when the meter did not answer, and runtime_contract for a bad argument.\n    # The engine sets policy_unavailable when the rule set did not read.\n    fault_code: str | None = None\n\nclass PolicyEngine:\n    async def decide(self, principal: Principal, request: PolicyRequest) -> PolicyDecision: ...\n\n\nclass AccrualChecker:\n    async def check(\n        self,\n        organization_id: UUID,\n        *,\n        root_run_id: UUID | None,        # None before a run exists, such as a front door turn\n        ceilings: RunCeilings | None,    # None when there is no run budget to read\n        scope: Literal['day', 'run', 'run_and_day'],\n    ) -> PolicyDecision: ...\n",[870,3689,3690,3694,3699,3704,3709,3714,3719,3724,3729,3734,3739,3743,3747,3752,3757,3762,3767,3772,3777,3783,3788,3793,3798,3804,3810,3816,3822,3827,3833,3839,3843,3848,3854,3860,3865,3871,3876,3882,3888,3894],{"__ignoreMap":942},[1176,3691,3692],{"class":1178,"line":22},[1176,3693,1181],{},[1176,3695,3696],{"class":1178,"line":32},[1176,3697,3698],{},"class PolicyRequest:\n",[1176,3700,3701],{"class":1178,"line":233},[1176,3702,3703],{},"    checkpoint: Literal['admission', 'action', 'accrual']\n",[1176,3705,3706],{"class":1178,"line":244},[1176,3707,3708],{},"    action: str\n",[1176,3710,3711],{"class":1178,"line":264},[1176,3712,3713],{},"    arguments: dict = field(default_factory=dict)\n",[1176,3715,3716],{"class":1178,"line":222},[1176,3717,3718],{},"    facts: dict = field(default_factory=dict)\n",[1176,3720,3721],{"class":1178,"line":360},[1176,3722,3723],{},"    # The caller hashes the proposal, and the engine never hashes it again.\n",[1176,3725,3726],{"class":1178,"line":368},[1176,3727,3728],{},"    # `ToolInvoker` already holds `hash_arguments()` for the approval\n",[1176,3730,3731],{"class":1178,"line":375},[1176,3732,3733],{},"    # comparison, and two implementations of one hash disagree in silence.\n",[1176,3735,3736],{"class":1178,"line":157},[1176,3737,3738],{},"    arguments_hash: str | None = None\n",[1176,3740,3741],{"class":1178,"line":182},[1176,3742,1314],{"emptyLinePlaceholder":1313},[1176,3744,3745],{"class":1178,"line":290},[1176,3746,1181],{},[1176,3748,3749],{"class":1178,"line":280},[1176,3750,3751],{},"class PolicyDecision:\n",[1176,3753,3754],{"class":1178,"line":272},[1176,3755,3756],{},"    outcome: Literal['allow', 'deny', 'require_approval']\n",[1176,3758,3759],{"class":1178,"line":1342},[1176,3760,3761],{},"    reason: str\n",[1176,3763,3764],{"class":1178,"line":1348},[1176,3765,3766],{},"    matched_policy_ids: tuple[UUID, ...]\n",[1176,3768,3769],{"class":1178,"line":1354},[1176,3770,3771],{},"    # The one rule precedence picked, or None when no rule produced this\n",[1176,3773,3774],{"class":1178,"line":1360},[1176,3775,3776],{},"    # answer. It is NOT matched_policy_ids[0]: that tuple names every rule that\n",[1176,3778,3780],{"class":1178,"line":3779},19,[1176,3781,3782],{},"    # matched, including one whose allow lost to a deny. Check 6 reads this.\n",[1176,3784,3785],{"class":1178,"line":520},[1176,3786,3787],{},"    deciding_policy_id: UUID | None = None\n",[1176,3789,3790],{"class":1178,"line":318},[1176,3791,3792],{},"    approval_ttl: timedelta | None = None\n",[1176,3794,3795],{"class":1178,"line":327},[1176,3796,3797],{},"    # The cause, when a refusal is a fault and not an answer. A denied rule and\n",[1176,3799,3801],{"class":1178,"line":3800},23,[1176,3802,3803],{},"    # a spent ceiling both leave it None. Accrual sets metering_unavailable\n",[1176,3805,3807],{"class":1178,"line":3806},24,[1176,3808,3809],{},"    # when the meter did not answer, and runtime_contract for a bad argument.\n",[1176,3811,3813],{"class":1178,"line":3812},25,[1176,3814,3815],{},"    # The engine sets policy_unavailable when the rule set did not read.\n",[1176,3817,3819],{"class":1178,"line":3818},26,[1176,3820,3821],{},"    fault_code: str | None = None\n",[1176,3823,3825],{"class":1178,"line":3824},27,[1176,3826,1314],{"emptyLinePlaceholder":1313},[1176,3828,3830],{"class":1178,"line":3829},28,[1176,3831,3832],{},"class PolicyEngine:\n",[1176,3834,3836],{"class":1178,"line":3835},29,[1176,3837,3838],{},"    async def decide(self, principal: Principal, request: PolicyRequest) -> PolicyDecision: ...\n",[1176,3840,3841],{"class":1178,"line":481},[1176,3842,1314],{"emptyLinePlaceholder":1313},[1176,3844,3846],{"class":1178,"line":3845},31,[1176,3847,1314],{"emptyLinePlaceholder":1313},[1176,3849,3851],{"class":1178,"line":3850},32,[1176,3852,3853],{},"class AccrualChecker:\n",[1176,3855,3857],{"class":1178,"line":3856},33,[1176,3858,3859],{},"    async def check(\n",[1176,3861,3863],{"class":1178,"line":3862},34,[1176,3864,1329],{},[1176,3866,3868],{"class":1178,"line":3867},35,[1176,3869,3870],{},"        organization_id: UUID,\n",[1176,3872,3874],{"class":1178,"line":3873},36,[1176,3875,1334],{},[1176,3877,3879],{"class":1178,"line":3878},37,[1176,3880,3881],{},"        root_run_id: UUID | None,        # None before a run exists, such as a front door turn\n",[1176,3883,3885],{"class":1178,"line":3884},38,[1176,3886,3887],{},"        ceilings: RunCeilings | None,    # None when there is no run budget to read\n",[1176,3889,3891],{"class":1178,"line":3890},39,[1176,3892,3893],{},"        scope: Literal['day', 'run', 'run_and_day'],\n",[1176,3895,3896],{"class":1178,"line":503},[1176,3897,3898],{},"    ) -> PolicyDecision: ...\n",[807,3900,3901,1102,3908,3911,3912,3915,3916,3918,3919,3922,3923,1469,3926,3928],{},[811,3902,3903,3905,3906,822],{},[870,3904,3637],{}," is the only component of this plane that lives in ",[870,3907,149],{},[870,3909,3910],{},"PolicyGate.admit()"," takes a ",[870,3913,3914],{},"Run"," and a ",[870,3917,1371],{},", and ",[870,3920,3921],{},"src.agentic.governance"," may not import ",[870,3924,3925],{},"src.agentic.runtime",[870,3927,3588],{}," cannot satisfy that protocol. The gate holds the engine, the accrual checker and the decision log, and it decides nothing of its own except the fatal scope set.",[807,3930,3931],{},"Two rules of it are easy to get wrong.",[1513,3933,3934,3945],{},[1516,3935,3936,3941,3942,3944],{},[811,3937,3938,3939,822],{},"The fatal scope check runs before ",[870,3940,1027],{}," The engine records its own row and an admission ",[870,3943,1014],{}," is one of them, so a scope refusal placed after it writes a second row for one Run.",[1516,3946,3947,3950],{},[811,3948,3949],{},"The day cap writes no row when it allows."," Admission records one row for each Run and the engine writes it after the insert. A row here would double the count for every start.",[807,3952,3953,1102,3955,3958],{},[870,3954,3133],{},[811,3956,3957],{},"returns"," a decision. It never raises, and it has one shape for every call site.",[837,3960,3961,3975],{},[840,3962,3963],{},[843,3964,3965,3967,3972],{},[846,3966,851],{},[846,3968,3969],{},[870,3970,3971],{},"scope",[846,3973,3974],{},"Reads",[859,3976,3977,3992,4006,4024],{},[843,3978,3979,3984,3989],{},[864,3980,3981,3983],{},[870,3982,920],{},", before the model call (Phase 5)",[864,3985,3986],{},[870,3987,3988],{},"day",[864,3990,3991],{},"the organization day; there is no run yet",[843,3993,3994,3999,4003],{},[864,3995,3996,3998],{},[870,3997,3637],{},", before the freeze",[864,4000,4001],{},[870,4002,3988],{},[864,4004,4005],{},"the organization day; no Run exists yet",[843,4007,4008,4016,4021],{},[864,4009,4010,4012,4013,4015],{},[870,4011,908],{}," before a segment, ",[870,4014,912],{}," before a node",[864,4017,4018],{},[870,4019,4020],{},"run_and_day",[864,4022,4023],{},"the run tree total and the organization day",[843,4025,4026,4031,4035],{},[864,4027,4028,4030],{},[870,4029,916],{},", before a metered call",[864,4032,4033],{},[870,4034,2059],{},[864,4036,4037],{},"the run tree total only",[807,4039,4040,4041,4044],{},"The second row is what stops a trigger storm cheaply, and its position in the start flow is the whole point. It needs the organization ID and nothing else, so it runs ",[811,4042,4043],{},"before"," the definition resolve and the snapshot freeze. Five hundred runs firing at 09:00 against a spent day cap then cost one indexed query each, and none of them creates a Run row. Put it after the freeze and each refusal still pays for a resolve, a principal, a snapshot and an insert.",[807,4046,4047,4048,4050,4051,4053],{},"A denial there has no Run to attach to, so it writes a ",[870,4049,1031],{}," row with ",[870,4052,3385],{}," null. It is the only decision written with no Run behind it. A row can also lose its Run later, because deleting a Run clears the column.",[807,4055,4056],{},"The last row is the one that matters inside a run. A metered call in a wide fan out must not aggregate the organization's whole day, and it does not need to: a run cannot pass the organization ceiling faster than it passes its own.",[807,4058,4059,4060,4062,4063,4066],{},"Every read is on ",[870,4061,3100],{},", the canonical meter. ",[870,4064,4065],{},"ai_usage_daily"," is a reporting rollup, it lags, and a run's own in flight spend is not in it. Reading the rollup here would mean the ceiling never fires.",[832,4068,4069],{"id":612},"Data model",[837,4071,4072,4082],{},[840,4073,4074],{},[843,4075,4076,4079],{},[846,4077,4078],{},"Table",[846,4080,4081],{},"Holds",[859,4083,4084,4093,4102,4111],{},[843,4085,4086,4090],{},[864,4087,4088],{},[870,4089,1710],{},[864,4091,4092],{},"one rule",[843,4094,4095,4099],{},[864,4096,4097],{},[870,4098,2915],{},[864,4100,4101],{},"one cost ceiling",[843,4103,4104,4108],{},[864,4105,4106],{},[870,4107,1031],{},[864,4109,4110],{},"every admission decision, and every decision that refused, gated or stopped work",[843,4112,4113,4118],{},[864,4114,4115],{},[870,4116,4117],{},"agent.approvals",[864,4119,4120],{},"one human decision, shared with the runtime",[807,4122,4123,4124,4126],{},"Every table carries ",[870,4125,1246],{},". RLS remains the tenancy boundary. Policy is authority logic and defence in depth, not a replacement for it.",[832,4128,4130],{"id":4129},"scenarios-that-shaped-this-design","Scenarios that shaped this design",[837,4132,4133,4143],{},[840,4134,4135],{},[843,4136,4137,4140],{},[846,4138,4139],{},"Scenario",[846,4141,4142],{},"What answers it",[859,4144,4145,4156,4164,4174,4182,4193,4201,4212,4220,4228,4236,4247,4258,4266,4274,4287,4295,4303,4311,4319,4327,4335,4343,4353,4361],{},[843,4146,4147,4150],{},[864,4148,4149],{},"A rule references a fact only the CRM knows",[864,4151,4152,4153,4155],{},"The tool declares ",[870,4154,2176],{},"; the resolver runs before the checkpoint",[843,4157,4158,4161],{},[864,4159,4160],{},"Someone writes a rule naming a fact no tool resolves",[864,4162,4163],{},"Save-time validation refuses the rule",[843,4165,4166,4169],{},[864,4167,4168],{},"A fact resolver fails at run time",[864,4170,4171,4173],{},[870,4172,1880],{},"; a gate that cannot run never passes",[843,4175,4176,4179],{},[864,4177,4178],{},"An admin adds a deny rule while 40 runs are live",[864,4180,4181],{},"Each run sees it at its next checkpoint, within the 30 second cache TTL",[843,4183,4184,4187],{},[864,4185,4186],{},"An organization must be stopped now",[864,4188,4189,4190,4192],{},"One ",[870,4191,1654],{}," deny row for the organization",[843,4194,4195,4198],{},[864,4196,4197],{},"A single tool must be stopped now",[864,4199,4200],{},"The tool registry kill switch, which the invoker reads live",[843,4202,4203,4209],{},[864,4204,4205,4206,4208],{},"A user without ",[870,4207,1071],{}," owns an agent that declares it",[864,4210,4211],{},"The intersection removes the tool; the model never sees it",[843,4213,4214,4217],{},[864,4215,4216],{},"A workflow node needs a scope the actor lacks",[864,4218,4219],{},"Admission denies, because a workflow cannot adapt",[843,4221,4222,4225],{},[864,4223,4224],{},"An approver lacks the right for the action",[864,4226,4227],{},"The authority re-check fails closed",[843,4229,4230,4233],{},[864,4231,4232],{},"An admin edits the rule set while a person deliberates",[864,4234,4235],{},"Check 6 refuses the stored answer, and the call files a fresh proposal",[843,4237,4238,4241],{},[864,4239,4240],{},"Two surfaces approve the same row at once",[864,4242,4243,4244,4246],{},"The conditional ",[870,4245,2489],{}," update picks one winner",[843,4248,4249,4252],{},[864,4250,4251],{},"An approval sits unanswered for 25 hours",[864,4253,4254,4255,4257],{},"The wait timeout resolves it ",[870,4256,2513],{}," and releases the run",[843,4259,4260,4263],{},[864,4261,4262],{},"An admission approval is never answered",[864,4264,4265],{},"Admission also dispatches the function, so one wait owns the clock",[843,4267,4268,4271],{},[864,4269,4270],{},"A trigger run needs an approval at 02:00",[864,4272,4273],{},"The notifier reaches the trigger author, because the run has no conversation",[843,4275,4276,4279],{},[864,4277,4278],{},"A run is cancelled with an approval pending",[864,4280,4281,4283,4284,4286],{},[870,4282,2528],{}," writes ",[870,4285,2523],{}," for the subtree",[843,4288,4289,4292],{},[864,4290,4291],{},"The proposal changes while a person is deciding",[864,4293,4294],{},"The arguments hash no longer matches, so the effect fails closed",[843,4296,4297,4300],{},[864,4298,4299],{},"A trigger fires 500 runs and the day cap is reached",[864,4301,4302],{},"Admission accrual denies the rest before they dispatch; live runs end partial",[843,4304,4305,4308],{},[864,4306,4307],{},"A run passes its cost cap mid segment",[864,4309,4310],{},"Accrual runs at the next boundary; overshoot is one segment",[843,4312,4313,4316],{},[864,4314,4315],{},"A fan out inside one step passes the cap",[864,4317,4318],{},"The metered call checks accrual, so the stop is one call",[843,4320,4321,4324],{},[864,4322,4323],{},"A run spends vendor money and almost no tokens",[864,4325,4326],{},"Metered vendor rows reach the same meter, so accrual still stops it",[843,4328,4329,4332],{},[864,4330,4331],{},"An agent is denied an action mid loop",[864,4333,4334],{},"The denial returns as a structured tool result, and the agent adapts",[843,4336,4337,4340],{},[864,4338,4339],{},"An agent asks a second time after a denial",[864,4341,4342],{},"The same deterministic rule set returns the same answer",[843,4344,4345,4348],{},[864,4346,4347],{},"An auditor asks what was refused last quarter",[864,4349,4350,4352],{},[870,4351,1031],{}," holds every refusal, gate and stop",[843,4354,4355,4358],{},[864,4356,4357],{},"An auditor asks which calls were allowed last quarter",[864,4359,4360],{},"The run span tree holds every allowed call",[843,4362,4363,4366],{},[864,4364,4365],{},"An auditor asks which runs were admitted last quarter",[864,4367,4368,4370],{},[870,4369,1031],{}," holds every admission decision",[832,4372,4374],{"id":4373},"rules","Rules",[1513,4376,4377,4380,4383,4386,4396,4399,4410,4419,4422,4425,4428,4431,4434,4437,4440,4449,4455,4458,4461,4464,4470,4473,4479,4487,4490,4493,4501,4507,4514,4519,4527,4530,4533,4536,4539,4542,4550,4553,4556,4563,4566,4569,4572,4575,4578,4581],{},[1516,4378,4379],{},"The engine is deterministic and model free.",[1516,4381,4382],{},"Two stages: grant first, then rules. Anything not granted is denied.",[1516,4384,4385],{},"Bind a rule to an action. Narrowing to a definition is the exception.",[1516,4387,4388,4389,4391,4392,4391,4394,822],{},"Every matching rule is evaluated, and ",[870,4390,1746],{}," beats ",[870,4393,977],{},[870,4395,1014],{},[1516,4397,4398],{},"The tie is broken by id. The reason and the ttl come from the first winning rule.",[1516,4400,4401,913,4403,909,4405,4407,4408,822],{},[870,4402,2056],{},[870,4404,2059],{},[870,4406,2062],{}," are reserved roots, and only a target fact answers ",[870,4409,2052],{},[1516,4411,4412,4413,909,4415,4418],{},"A target fact is present or absent. ",[870,4414,2088],{},[870,4416,4417],{},"null"," are answers.",[1516,4420,4421],{},"A decision row that fails to write is reported, and the decision still stands.",[1516,4423,4424],{},"A target fact is declared by the action and resolved before the checkpoint.",[1516,4426,4427],{},"A capability an agent must never hold is absent from its tool subset, not merely denied.",[1516,4429,4430],{},"Policy is evaluated on behalf of the agent. The agent cannot ask for another answer.",[1516,4432,4433],{},"A denial becomes a structured tool result, so the agent can adapt safely.",[1516,4435,4436],{},"Rules are live at the next checkpoint, within the 30 second cache TTL.",[1516,4438,4439],{},"The rule cache holds one entry per organization, and an empty rule set is one of them.",[1516,4441,4442,4443,4445,4446,822],{},"A rule set that cannot be read, or that reads short, answers ",[870,4444,1746],{}," under ",[870,4447,4448],{},"policy_unavailable",[1516,4450,4451,4452,4454],{},"The save-time grammar and ",[870,4453,1696],{}," refuse the same trees, to 20 levels.",[1516,4456,4457],{},"One expiry clock per approval, and one waiter that owns it.",[1516,4459,4460],{},"Every approval reaches a person: the conversation when there is one, the trigger author when there is not.",[1516,4462,4463],{},"An approval cannot grant authority the approver lacks.",[1516,4465,4466,4467,4469],{},"Accrual reads ",[870,4468,3100],{},", the canonical meter, and owns no second counter. It never reads the rollup.",[1516,4471,4472],{},"The day sum filters on the organization and the UTC day, and on no source. It counts what the whole product spent.",[1516,4474,4475,4476,4478],{},"An absent ",[870,4477,2915],{}," row is no cap. The check allows.",[1516,4480,3124,4481,4483,4484,4486],{},[870,4482,1746],{}," and sets ",[870,4485,3130],{},". The callers end the run under that code, and never as a spent budget or a partial success.",[1516,4488,4489],{},"A transient meter fault is read again before it denies.",[1516,4491,4492],{},"A meter outage reaches Sentry one time for each read in each window. The bound is a time window, because a meter that alternates makes every failure follow a success.",[1516,4494,4495,4496,4445,4498,4500],{},"A scope the checker does not know answers ",[870,4497,1746],{},[870,4499,3215],{},", and the reason withholds the value.",[1516,4502,4503,4504,4506],{},"A fault is a platform stop. ",[870,4505,3229],{}," never tolerates one, and the code carries that rule across a step boundary rather than a flag.",[1516,4508,4509,4510,4513],{},"A metered call refused by a fault answers the fault code, so the node stops on it. ",[870,4511,4512],{},"internal_error"," answers a wiring defect alone.",[1516,4515,4516,4517,822],{},"A metered call that cannot be measured is a fault and not a ceiling. No run scope, or no checker wired, answers ",[870,4518,4512],{},[1516,4520,4521,4523,4524,4526],{},[870,4522,3133],{}," returns a ",[870,4525,3630],{},". It never raises.",[1516,4528,4529],{},"Stage 1 skips the grant check for the accrual checkpoint.",[1516,4531,4532],{},"The accrual day is UTC.",[1516,4534,4535],{},"Organization overshoot is bounded by the concurrency limit, not by one unit.",[1516,4537,4538],{},"Concurrency and rate belong to Inngest. Policy holds cost ceilings only.",[1516,4540,4541],{},"Accrual has five call sites, which take four shapes. The admission gate runs before the freeze, so a refused start costs one query.",[1516,4543,4544,4545,4547,4548,822],{},"The role mapping grants ",[870,4546,1061],{},". Nothing grants ",[870,4549,1080],{},[1516,4551,4552],{},"The role mapping lists tool names. A grant carries no wildcard.",[1516,4554,4555],{},"The principal narrows live at every checkpoint, and never widens.",[1516,4557,4558,4559,4445,4561,822],{},"A read of the live rights that fails answers ",[870,4560,1746],{},[870,4562,4448],{},[1516,4564,4565],{},"A machine grant narrows against the admin who authored its trigger.",[1516,4567,4568],{},"The kill switch refuses the next action. Cancel is what ends a run.",[1516,4570,4571],{},"A fact resolver caches its answer for the run, keyed on the arguments it read.",[1516,4573,4574],{},"A workflow's fatal scope set covers its tool nodes. An agent node adapts.",[1516,4576,4577],{},"The fatal scope check reads the frozen grant, and it runs before the rules, so one admission writes one row.",[1516,4579,4580],{},"The day cap gate writes a row only when it refuses. The engine writes the row of every admission.",[1516,4582,4583],{},"RLS remains the tenancy boundary.",[832,4585,4587],{"id":4586},"minimum-contract-tests","Minimum contract tests",[1513,4589,4590,4593,4598,4601,4604,4607,4610,4613,4616,4619,4627,4633,4639,4642,4645,4650,4653,4656,4661,4664,4667,4670,4673,4676,4683,4686,4689,4692,4695,4698,4701,4707,4710,4713,4716,4722,4725,4728,4731,4737,4740,4743,4746,4752,4758,4765,4771],{},[1516,4591,4592],{},"An action outside the principal scopes is denied, with no rule present.",[1516,4594,4595,4596,822],{},"With the grant held and no rule matching, the outcome is ",[870,4597,1014],{},[1516,4599,4600],{},"Deny beats approval and allow under overlapping matching rules.",[1516,4602,4603],{},"Approval beats allow when no deny matches.",[1516,4605,4606],{},"A wildcard rule and an exact rule both match, and precedence decides.",[1516,4608,4609],{},"The principal never exceeds the actor rights or the definition scopes.",[1516,4611,4612],{},"A trigger principal is narrower than an unrelated user principal.",[1516,4614,4615],{},"A machine actor holds no scope its author lost, at the mint and at every checkpoint.",[1516,4617,4618],{},"A rule naming an undeclared fact is refused when it is saved.",[1516,4620,4621,4622,4624,4625,822],{},"A rule whose target fact fails to resolve produces ",[870,4623,1746],{},", not ",[870,4626,1014],{},[1516,4628,4629,4630,4632],{},"A target fact resolved to ",[870,4631,2088],{}," is present, and the rule evaluates against it.",[1516,4634,4635,4636,4638],{},"A rule reading an absent ",[870,4637,2062],{}," path evaluates False, and never denies.",[1516,4640,4641],{},"A workflow whose tool node needs a missing scope is denied at admission.",[1516,4643,4644],{},"A workflow whose agent node names a missing scope still starts, and that agent runs with fewer tools.",[1516,4646,4647,4648,822],{},"A run refused for the organization day cap creates no Run row, and writes a decision with a null ",[870,4649,3385],{},[1516,4651,4652],{},"A meter fault at the day cap answers a fault and never a spent budget.",[1516,4654,4655],{},"An admission denied by a rule writes exactly one decision row, and so does one denied for a missing tool node scope.",[1516,4657,4658,4659,822],{},"A role that may run agentic work holds ",[870,4660,1061],{},[1516,4662,4663],{},"An agent whose declared tool is not granted still starts, without that tool.",[1516,4665,4666],{},"Two concurrent approval resolutions produce one winner.",[1516,4668,4669],{},"An expired, stale-hash or unauthorized approval cannot execute.",[1516,4671,4672],{},"An admission approval that is never answered ends the run at its timeout.",[1516,4674,4675],{},"Cancelling a run cancels its pending approvals.",[1516,4677,4678,4679,4682],{},"A workflow approval node carries its own ",[870,4680,4681],{},"ttl_s",", because no rule sets one for it. It is clamped by the Run deadline exactly as a rule TTL is.",[1516,4684,4685],{},"A person approving in the same instant as the timeout still gets the action executed.",[1516,4687,4688],{},"An approval on a run with no conversation still notifies the trigger author.",[1516,4690,4691],{},"An in-flight run observes a new deny rule at its next action checkpoint.",[1516,4693,4694],{},"A run whose actor loses a right cannot use it at the next checkpoint.",[1516,4696,4697],{},"A run whose actor gains a right does not gain it mid run.",[1516,4699,4700],{},"The kill switch does not end a run already waiting on an approval.",[1516,4702,4703,4704,4706],{},"An organization ",[870,4705,1654],{}," deny rule stops every checkpoint at once.",[1516,4708,4709],{},"Accrual stops a run at its cost ceiling, and the run succeeds with a partial reason.",[1516,4711,4712],{},"A metered call is refused once the run is over its ceiling, without waiting for the next node.",[1516,4714,4715],{},"A metered call does not aggregate the organization day.",[1516,4717,4718,4719,4721],{},"An accrual check passes stage 1 without holding a ",[870,4720,1080],{}," scope.",[1516,4723,4724],{},"A front door turn with no run is checked against the organization day alone.",[1516,4726,4727],{},"An organization with no ceiling row is allowed.",[1516,4729,4730],{},"A run whose spend equals its ceiling exactly is refused.",[1516,4732,4733,4734,4736],{},"A meter read that raises produces ",[870,4735,1746],{},", and the checker still returns.",[1516,4738,4739],{},"Many failed reads of one outage produce one Sentry event for each read, and one log line for each call.",[1516,4741,4742],{},"A meter that alternates success and failure produces one event for each read in each window, and never one for each pair.",[1516,4744,4745],{},"An outage that spans many windows produces one event in each of them, because the window does not slide with the calls.",[1516,4747,4748,4749,4751],{},"A meter fault ends the run under ",[870,4750,3151],{},", and never as a partial success.",[1516,4753,4754,4755,4757],{},"A node that declares ",[870,4756,3229],{}," still stops on a meter fault, at its own gate, at a metered tool call inside it, at a child start, and on a child that died of one.",[1516,4759,4760,4761,2830,4763,822],{},"A metered call outside a run scope answers ",[870,4762,4512],{},[870,4764,3205],{},[1516,4766,4767,4768,4770],{},"A denied action writes a ",[870,4769,1031],{}," row; an allowed action does not.",[1516,4772,4773],{},"Stage 1 reads the live intersection, so a right stripped mid run is refused at the next checkpoint.",[4775,4776,4777],"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":942,"searchDepth":32,"depth":233,"links":4779},[4780,4781,4782,4783,4786,4789,4790,4791,4798,4801,4802,4803,4804,4805,4806,4807],{"id":834,"depth":32,"text":835},{"id":933,"depth":32,"text":934},{"id":984,"depth":32,"text":985},{"id":1035,"depth":32,"text":1036,"children":4784},[4785],{"id":1132,"depth":233,"text":1133},{"id":1163,"depth":32,"text":1164,"children":4787},[4788],{"id":1495,"depth":233,"text":1496},{"id":1556,"depth":32,"text":1557},{"id":1680,"depth":32,"text":1681},{"id":255,"depth":32,"text":2285,"children":4792},[4793,4794,4795,4796,4797],{"id":2383,"depth":233,"text":2384},{"id":2434,"depth":233,"text":2435},{"id":2467,"depth":233,"text":2468},{"id":2642,"depth":233,"text":2643},{"id":2677,"depth":233,"text":2678},{"id":2859,"depth":32,"text":2860,"children":4799},[4800],{"id":2960,"depth":233,"text":2961},{"id":3264,"depth":32,"text":3265},{"id":3323,"depth":32,"text":3324},{"id":3531,"depth":32,"text":3532},{"id":612,"depth":32,"text":4069},{"id":4129,"depth":32,"text":4130},{"id":4373,"depth":32,"text":4374},{"id":4586,"depth":32,"text":4587},"md",{},[4811,4812,4813,4814,4815],"engineering\u002Fsystem-design\u002Fagentic-platform\u002Fcontract","engineering\u002Fsystem-design\u002Fagentic-platform\u002Fruntime\u002Fexecution","engineering\u002Fsystem-design\u002Fagentic-platform\u002Fcapabilities\u002Ftools-and-integrations","engineering\u002Fsystem-design\u002Fagentic-platform\u002Fproducts\u002Fhuman-review","engineering\u002Fsystem-design\u002Fagentic-platform\u002Fplanes\u002Fobservability-and-operations",{"title":288,"description":289},"engineering\u002Fsystem-design\u002Fagentic-platform\u002Fplanes\u002Fpolicy-and-governance",[198,292,255,293,294],"KAYVgCTZpcaX04Yav2_s8nK67_t8FnDaAEEUyWg-DlI",1788650193909]