[{"data":1,"prerenderedAt":3272},["ShallowReactive",2],{"navigation_docs":3,"-reference-configuration-repos":270,"-reference-configuration-repos-surround":3267},[4,28,51,110,243],{"title":5,"path":6,"stem":7,"children":8},"Get started","\u002Fget-started","1.get-started",[9,12,16,20,24],{"title":10,"path":6,"stem":11},"Overview","1.get-started\u002Findex",{"title":13,"path":14,"stem":15},"Why Kraft","\u002Fget-started\u002Fwhy-kraft","1.get-started\u002F0.why-kraft",{"title":17,"path":18,"stem":19},"Install","\u002Fget-started\u002Finstall","1.get-started\u002F1.install",{"title":21,"path":22,"stem":23},"Your first work item","\u002Fget-started\u002Ffirst-work-item","1.get-started\u002F2.first-work-item",{"title":25,"path":26,"stem":27},"Troubleshooting and FAQ","\u002Fget-started\u002Ftroubleshooting","1.get-started\u002F3.troubleshooting",{"title":29,"path":30,"stem":31,"children":32},"Concepts","\u002Fconcepts","2.concepts",[33,35,39,43,47],{"title":10,"path":30,"stem":34},"2.concepts\u002Findex",{"title":36,"path":37,"stem":38},"Vocabulary","\u002Fconcepts\u002Fvocabulary","2.concepts\u002F1.vocabulary",{"title":40,"path":41,"stem":42},"How a work item runs","\u002Fconcepts\u002Fhow-a-work-item-runs","2.concepts\u002F2.how-a-work-item-runs",{"title":44,"path":45,"stem":46},"Caps and budgets","\u002Fconcepts\u002Fcaps-and-budgets","2.concepts\u002F3.caps-and-budgets",{"title":48,"path":49,"stem":50},"Why a permission gate","\u002Fconcepts\u002Fpermission-gate","2.concepts\u002F4.permission-gate",{"title":52,"path":53,"stem":54,"children":55},"Guides","\u002Fguides","3.guides",[56,58,62,66,70,74,78,82,86,90,94,98,102,106],{"title":10,"path":53,"stem":57},"3.guides\u002Findex",{"title":59,"path":60,"stem":61},"Use Kraft from your agent","\u002Fguides\u002Fagent-integration","3.guides\u002F01.agent-integration",{"title":63,"path":64,"stem":65},"Kraft Lite","\u002Fguides\u002Fkraft-lite","3.guides\u002F02.kraft-lite",{"title":67,"path":68,"stem":69},"Remote access","\u002Fguides\u002Fremote-access","3.guides\u002F03.remote-access",{"title":71,"path":72,"stem":73},"Add or override a harness","\u002Fguides\u002Fadding-a-harness","3.guides\u002F04.adding-a-harness",{"title":75,"path":76,"stem":77},"Schedule or webhook work","\u002Fguides\u002Fschedule-and-webhook-work","3.guides\u002F05.schedule-and-webhook-work",{"title":79,"path":80,"stem":81},"Add a security review or a gate reviewer","\u002Fguides\u002Fadd-review-agents","3.guides\u002F06.add-review-agents",{"title":83,"path":84,"stem":85},"Kraft for VS Code","\u002Fguides\u002Fvscode","3.guides\u002F07.vscode",{"title":87,"path":88,"stem":89},"Reviewing a change","\u002Fguides\u002Freview-a-change","3.guides\u002F08.review-a-change",{"title":91,"path":92,"stem":93},"Write your own chain","\u002Fguides\u002Fwrite-your-own-chain","3.guides\u002F09.write-your-own-chain",{"title":95,"path":96,"stem":97},"Switch a harness","\u002Fguides\u002Fswitch-harness","3.guides\u002F10.switch-harness",{"title":99,"path":100,"stem":101},"Upgrade your templates","\u002Fguides\u002Fupgrading-templates","3.guides\u002F11.upgrading-templates",{"title":103,"path":104,"stem":105},"Operations","\u002Fguides\u002Foperations","3.guides\u002F12.operations",{"title":107,"path":108,"stem":109},"Worker Kit","\u002Fguides\u002Fworker-kit","3.guides\u002F13.worker-kit",{"title":111,"path":112,"stem":113,"children":114},"Reference","\u002Freference","4.reference",[115,117,139,183,201,205,227,231,235,239],{"title":10,"path":112,"stem":116},"4.reference\u002Findex",{"title":118,"path":119,"stem":120,"children":121},"CLI","\u002Freference\u002Fcli","4.reference\u002F1.cli\u002Findex",[122,123,127,131,135],{"title":10,"path":119,"stem":120},{"title":124,"path":125,"stem":126},"Item verbs","\u002Freference\u002Fcli\u002Fitem","4.reference\u002F1.cli\u002F2.item",{"title":128,"path":129,"stem":130},"View verbs","\u002Freference\u002Fcli\u002Fview","4.reference\u002F1.cli\u002F3.view",{"title":132,"path":133,"stem":134},"Repo verbs","\u002Freference\u002Fcli\u002Frepo","4.reference\u002F1.cli\u002F4.repo",{"title":136,"path":137,"stem":138},"Admin verbs","\u002Freference\u002Fcli\u002Fadmin","4.reference\u002F1.cli\u002F5.admin",{"title":140,"path":141,"stem":142,"children":143},"Configuration","\u002Freference\u002Fconfiguration","4.reference\u002F2.configuration\u002Findex",[144,145,155,159,163,167,171,175,179],{"title":10,"path":141,"stem":142},{"title":146,"path":147,"stem":148,"children":149},"Repos","\u002Freference\u002Fconfiguration\u002Frepos","4.reference\u002F2.configuration\u002F2.repos\u002Findex",[150,151],{"title":10,"path":147,"stem":148},{"title":152,"path":153,"stem":154},"Workspaces","\u002Freference\u002Fconfiguration\u002Frepos\u002Fworkspaces","4.reference\u002F2.configuration\u002F2.repos\u002F3.workspaces",{"title":156,"path":157,"stem":158},"Policy","\u002Freference\u002Fconfiguration\u002Fpolicy","4.reference\u002F2.configuration\u002F3.policy",{"title":160,"path":161,"stem":162},"Library and chains","\u002Freference\u002Fconfiguration\u002Flibrary-and-chains","4.reference\u002F2.configuration\u002F4.library-and-chains",{"title":164,"path":165,"stem":166},"Harnesses file","\u002Freference\u002Fconfiguration\u002Fharnesses-file","4.reference\u002F2.configuration\u002F5.harnesses-file",{"title":168,"path":169,"stem":170},"Access","\u002Freference\u002Fconfiguration\u002Faccess","4.reference\u002F2.configuration\u002F6.access",{"title":172,"path":173,"stem":174},"Intake","\u002Freference\u002Fconfiguration\u002Fintake","4.reference\u002F2.configuration\u002F7.intake",{"title":176,"path":177,"stem":178},"Sandbox host","\u002Freference\u002Fconfiguration\u002Fsandbox","4.reference\u002F2.configuration\u002F8.sandbox",{"title":180,"path":181,"stem":182},"Environment variables","\u002Freference\u002Fconfiguration\u002Fenvironment-variables","4.reference\u002F2.configuration\u002F9.environment-variables",{"title":184,"path":185,"stem":186,"children":187},"Chain nodes","\u002Freference\u002Fchain-nodes","4.reference\u002F3.chain-nodes\u002Findex",[188,189,193,197],{"title":10,"path":185,"stem":186},{"title":190,"path":191,"stem":192},"Subprocess tasks","\u002Freference\u002Fchain-nodes\u002Fsubprocess-tasks","4.reference\u002F3.chain-nodes\u002F2.subprocess-tasks",{"title":194,"path":195,"stem":196},"Fix loop and judge","\u002Freference\u002Fchain-nodes\u002Ffix-loop","4.reference\u002F3.chain-nodes\u002F3.fix-loop",{"title":198,"path":199,"stem":200},"Result file","\u002Freference\u002Fchain-nodes\u002Fresult-file","4.reference\u002F3.chain-nodes\u002F4.result-file",{"title":202,"path":203,"stem":204},"Permission gate","\u002Freference\u002Fpermissions","4.reference\u002F4.permissions",{"title":206,"path":207,"stem":208,"children":209},"Agent harnesses","\u002Freference\u002Fharnesses","4.reference\u002F5.harnesses\u002Findex",[210,211,215,219,223],{"title":10,"path":207,"stem":208},{"title":212,"path":213,"stem":214},"Unattended runs","\u002Freference\u002Fharnesses\u002Funattended-runs","4.reference\u002F5.harnesses\u002F2.unattended-runs",{"title":216,"path":217,"stem":218},"Agent profiles","\u002Freference\u002Fharnesses\u002Fagent-profiles","4.reference\u002F5.harnesses\u002F3.agent-profiles",{"title":220,"path":221,"stem":222},"Harness files","\u002Freference\u002Fharnesses\u002Fharness-files","4.reference\u002F5.harnesses\u002F4.harness-files",{"title":224,"path":225,"stem":226},"Fallback and escalation","\u002Freference\u002Fharnesses\u002Ffallback-and-escalation","4.reference\u002F5.harnesses\u002F5.fallback-and-escalation",{"title":228,"path":229,"stem":230},"Inbound triggers","\u002Freference\u002Ftriggers","4.reference\u002F6.triggers",{"title":232,"path":233,"stem":234},"HTTP API","\u002Freference\u002Fhttp-api","4.reference\u002F7.http-api",{"title":236,"path":237,"stem":238},"MCP tools","\u002Freference\u002Fmcp-tools","4.reference\u002F8.mcp-tools",{"title":240,"path":241,"stem":242},"Events","\u002Freference\u002Fevents","4.reference\u002F9.events",{"title":244,"path":245,"stem":246,"children":247},"Project","\u002Fproject","5.project",[248,250,254,258,262,266],{"title":10,"path":245,"stem":249},"5.project\u002Findex",{"title":251,"path":252,"stem":253},"Architecture","\u002Fproject\u002Farchitecture","5.project\u002F1.architecture",{"title":255,"path":256,"stem":257},"Contributing","\u002Fproject\u002Fcontributing","5.project\u002F2.contributing",{"title":259,"path":260,"stem":261},"Security","\u002Fproject\u002Fsecurity","5.project\u002F3.security",{"title":263,"path":264,"stem":265},"Data and privacy","\u002Fproject\u002Fdata-and-privacy","5.project\u002F4.data-and-privacy",{"title":267,"path":268,"stem":269},"Status and support","\u002Fproject\u002Fstatus-and-support","5.project\u002F5.status-and-support",{"id":271,"title":146,"body":272,"description":3260,"extension":3261,"links":3262,"meta":3263,"navigation":3264,"path":147,"seo":3265,"stem":148,"__hash__":3266},"docs\u002F4.reference\u002F2.configuration\u002F2.repos\u002Findex.md",{"type":273,"value":274,"toc":3247},"minimark",[275,283,523,1200,1211,1215,1233,1510,1544,1556,1559,1563,1617,1628,1633,1671,1694,1698,1771,1863,1874,1898,1938,1942,2038,2050,2090,2126,2161,2170,2206,2220,2247,2253,2274,2290,2313,2327,2344,2349,2371,2373,2518,2524,2691,2713,2749,2753,2801,2805,2822,2861,2901,2908,3032,3052,3075,3099,3115,3118,3126,3138,3145,3148,3152,3173,3177,3204,3209,3221,3232,3236,3243],[276,277,278,282],"p",{},[279,280,281],"code",{},"repos.yaml"," lists the repositories you connected and how Kraft works in each.",[284,285,290],"pre",{"className":286,"code":287,"language":288,"meta":289,"style":289},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","repos:\n  - path: \u002Fhome\u002Fyou\u002Fcode\u002Fmy-service\n    name: my-service\n    default_chain_template: default\n    forge: github\n    test_command: null\n    intent_dir: null\n    setup_command: \"uv sync\"\n    env: {}\n    env_passthrough: []\n    local_files: []\n    models: {}\n    deny_tools: []\n    steering: []\n    sandbox: null\n    policy:\n      allowed_tools: [Read, Edit, Bash]\n    automated_review:\n      bot: coderabbitai     # or `check: \u003Cname>` -- exactly one of the two\n","yaml","",[279,291,292,305,321,332,343,354,365,375,392,403,414,424,434,444,454,464,472,500,508],{"__ignoreMap":289},[293,294,297,301],"span",{"class":295,"line":296},"line",1,[293,298,300],{"class":299},"swJcz","repos",[293,302,304],{"class":303},"sMK4o",":\n",[293,306,308,311,314,317],{"class":295,"line":307},2,[293,309,310],{"class":303},"  -",[293,312,313],{"class":299}," path",[293,315,316],{"class":303},":",[293,318,320],{"class":319},"sfazB"," \u002Fhome\u002Fyou\u002Fcode\u002Fmy-service\n",[293,322,324,327,329],{"class":295,"line":323},3,[293,325,326],{"class":299},"    name",[293,328,316],{"class":303},[293,330,331],{"class":319}," my-service\n",[293,333,335,338,340],{"class":295,"line":334},4,[293,336,337],{"class":299},"    default_chain_template",[293,339,316],{"class":303},[293,341,342],{"class":319}," default\n",[293,344,346,349,351],{"class":295,"line":345},5,[293,347,348],{"class":299},"    forge",[293,350,316],{"class":303},[293,352,353],{"class":319}," github\n",[293,355,357,360,362],{"class":295,"line":356},6,[293,358,359],{"class":299},"    test_command",[293,361,316],{"class":303},[293,363,364],{"class":303}," null\n",[293,366,368,371,373],{"class":295,"line":367},7,[293,369,370],{"class":299},"    intent_dir",[293,372,316],{"class":303},[293,374,364],{"class":303},[293,376,378,381,383,386,389],{"class":295,"line":377},8,[293,379,380],{"class":299},"    setup_command",[293,382,316],{"class":303},[293,384,385],{"class":303}," \"",[293,387,388],{"class":319},"uv sync",[293,390,391],{"class":303},"\"\n",[293,393,395,398,400],{"class":295,"line":394},9,[293,396,397],{"class":299},"    env",[293,399,316],{"class":303},[293,401,402],{"class":303}," {}\n",[293,404,406,409,411],{"class":295,"line":405},10,[293,407,408],{"class":299},"    env_passthrough",[293,410,316],{"class":303},[293,412,413],{"class":303}," []\n",[293,415,417,420,422],{"class":295,"line":416},11,[293,418,419],{"class":299},"    local_files",[293,421,316],{"class":303},[293,423,413],{"class":303},[293,425,427,430,432],{"class":295,"line":426},12,[293,428,429],{"class":299},"    models",[293,431,316],{"class":303},[293,433,402],{"class":303},[293,435,437,440,442],{"class":295,"line":436},13,[293,438,439],{"class":299},"    deny_tools",[293,441,316],{"class":303},[293,443,413],{"class":303},[293,445,447,450,452],{"class":295,"line":446},14,[293,448,449],{"class":299},"    steering",[293,451,316],{"class":303},[293,453,413],{"class":303},[293,455,457,460,462],{"class":295,"line":456},15,[293,458,459],{"class":299},"    sandbox",[293,461,316],{"class":303},[293,463,364],{"class":303},[293,465,467,470],{"class":295,"line":466},16,[293,468,469],{"class":299},"    policy",[293,471,304],{"class":303},[293,473,475,478,480,483,486,489,492,494,497],{"class":295,"line":474},17,[293,476,477],{"class":299},"      allowed_tools",[293,479,316],{"class":303},[293,481,482],{"class":303}," [",[293,484,485],{"class":319},"Read",[293,487,488],{"class":303},",",[293,490,491],{"class":319}," Edit",[293,493,488],{"class":303},[293,495,496],{"class":319}," Bash",[293,498,499],{"class":303},"]\n",[293,501,503,506],{"class":295,"line":502},18,[293,504,505],{"class":299},"    automated_review",[293,507,304],{"class":303},[293,509,511,514,516,519],{"class":295,"line":510},19,[293,512,513],{"class":299},"      bot",[293,515,316],{"class":303},[293,517,518],{"class":319}," coderabbitai",[293,520,522],{"class":521},"sHwdD","     # or `check: \u003Cname>` -- exactly one of the two\n",[524,525,526,542],"table",{},[527,528,529],"thead",{},[530,531,532,536,539],"tr",{},[533,534,535],"th",{},"Field",[533,537,538],{},"Default",[533,540,541],{},"Means",[543,544,545,562,575,591,618,635,658,688,710,745,761,783,811,827,850,941,963,997,1015,1033,1069,1138,1167],"tbody",{},[530,546,547,553,559],{},[548,549,550],"td",{},[279,551,552],{},"path",[548,554,555],{},[556,557,558],"em",{},"(required)",[548,560,561],{},"Absolute path to the repo.",[530,563,564,569,572],{},[548,565,566],{},[279,567,568],{},"name",[548,570,571],{},"—",[548,573,574],{},"Display name; set at connect time, not otherwise validated.",[530,576,577,582,584],{},[548,578,579],{},[279,580,581],{},"id",[548,583,571],{},[548,585,586,587,590],{},"The repository id a workspace names this entry by (",[279,588,589],{},"[a-z][a-z0-9_-]*",", unique). Only a workspace's root and members need one; connecting a repo with submodules writes it for them.",[530,592,593,598,603],{},[548,594,595],{},[279,596,597],{},"enabled",[548,599,600],{},[279,601,602],{},"true",[548,604,605,606,609,610,613,614,617],{},"Set ",[279,607,608],{},"false"," to take this repo out of service without disconnecting it: new items can't target it and auto-intake skips it, while running items keep going. An absent key counts as enabled. Kraft refuses an edit that would leave an enabled repo with neither a ",[279,611,612],{},"test_command"," nor ",[279,615,616],{},"test_scopes",".",[530,619,620,625,629],{},[548,621,622],{},[279,623,624],{},"managed",[548,626,627],{},[279,628,602],{},[548,630,631,632,617],{},"Keeps a human-connected repo out of Settings' \"Detected\" section; auto-connected submodules are written with ",[279,633,634],{},"managed: false",[530,636,637,642,644],{},[548,638,639],{},[279,640,641],{},"default_chain_template",[548,643,571],{},[548,645,646,647,650,651,654,655,617],{},"Which chain template a work item on this repo uses when none is named explicitly, however it is filed: ",[279,648,649],{},"kraft item create",", the MCP tool, the board, the API, ",[279,652,653],{},"POST \u002Fapi\u002Ftriggers"," or auto-intake. Unset, it is ",[279,656,657],{},"default",[530,659,660,665,670],{},[548,661,662],{},[279,663,664],{},"forge",[548,666,667],{},[279,668,669],{},"null",[548,671,672,675,676,679,680,683,684,687],{},[279,673,674],{},"github"," or ",[279,677,678],{},"gitlab",", which forge adapter ",[279,681,682],{},"backend: auto"," resolves to for this repo. ",[279,685,686],{},"kraft repo connect"," sets it from the repo's remote.",[530,689,690,695,699],{},[548,691,692],{},[279,693,694],{},"project",[548,696,697],{},[279,698,669],{},[548,700,701,702,705,706,709],{},"The GitLab project path, when ",[279,703,704],{},"forge: gitlab",". A legacy ",[279,707,708],{},"gitlab_project"," key still reads.",[530,711,712,717,722],{},[548,713,714],{},[279,715,716],{},"models",[548,718,719],{},[279,720,721],{},"{}",[548,723,724,725,728,729,732,733,736,737,740,741,744],{},"The model an agent task runs with on this repo, per harness profile id (",[279,726,727],{},"claude: opus","): above the profile's own ",[279,730,731],{},"defaults:",", below a task's ",[279,734,735],{},"model:"," or agent ",[279,738,739],{},"profile:"," and the work item's override. Keyed by profile because one model name means nothing to another provider. The retired ",[279,742,743],{},"default_model"," key is dropped with a warning.",[530,746,747,751,755],{},[548,748,749],{},[279,750,612],{},[548,752,753],{},[279,754,669],{},[548,756,757,758,760],{},"The command CI actually runs for this repo — what the changed-test-scope verification runs, as one scope over every path. A repo with neither this nor ",[279,759,616],{}," stops that verification for a human rather than inventing a command.",[530,762,763,768,772],{},[548,764,765],{},[279,766,767],{},"areas",[548,769,770],{},[279,771,721],{},[548,773,774,775,778,779,782],{},"Path-scoped contexts inside this repo, keyed by id: ",[279,776,777],{},"{paths: [...], setup: \"...\", verification: {test_scopes: [...]}}",". An area's test scopes join the repo's and are selected by changed paths the same way; its ",[279,780,781],{},"setup"," runs once before the first of its scopes runs. Areas are never forge targets.",[530,784,785,789,793],{},[548,786,787],{},[279,788,616],{},[548,790,791],{},[279,792,669],{},[548,794,795,796,799,800,803,804,807,808,810],{},"A monorepo's per-directory test commands: a list of ",[279,797,798],{},"{paths: [...], command: \"...\"}"," mappings, each ",[279,801,802],{},"paths"," non-empty and each ",[279,805,806],{},"command"," a non-empty string. Not synthesized from ",[279,809,612],{}," — the two stay independently editable.",[530,812,813,818,822],{},[548,814,815],{},[279,816,817],{},"intent_dir",[548,819,820],{},[279,821,669],{},[548,823,824,825,617],{},"Where the repo's intent tree lives, relative to its root. When set, every agent in the repo is told to follow it. Its check runs as one of the repo's ",[279,826,616],{},[530,828,829,834,839],{},[548,830,831],{},[279,832,833],{},"setup_command",[548,835,836],{},[556,837,838],{},"(required — no fallback)",[548,840,841,842,845,846,849],{},"Run in every new worktree before any node starts. ",[279,843,844],{},"\"\""," means \"deliberately nothing\"; an absent value stops the repo's next work item rather than guessing. On a sandboxed item it runs as ",[279,847,848],{},"sh -c"," inside the sandbox, never on the host; without docker the item stops.",[530,851,852,857,861],{},[548,853,854],{},[279,855,856],{},"env",[548,858,859],{},[279,860,721],{},[548,862,863,864,867,868,867,871,867,874,867,877,867,880,867,883,867,886,867,889,867,892,867,895,898,899,867,902,867,905,867,908,911,912,867,915,867,918,867,921,867,924,867,927,930,931,934,935,940],{},"Literal environment variables every worker for this repo gets. A worker's environment is an allowlist, not the daemon's: ",[279,865,866],{},"PATH",", ",[279,869,870],{},"HOME",[279,872,873],{},"USER",[279,875,876],{},"LOGNAME",[279,878,879],{},"SHELL",[279,881,882],{},"LANG",[279,884,885],{},"LC_ALL",[279,887,888],{},"TERM",[279,890,891],{},"TZ",[279,893,894],{},"TMPDIR",[279,896,897],{},"SSH_AUTH_SOCK",", the proxy variables (",[279,900,901],{},"HTTP_PROXY",[279,903,904],{},"HTTPS_PROXY",[279,906,907],{},"ALL_PROXY",[279,909,910],{},"NO_PROXY",", any case), the CA variables (",[279,913,914],{},"SSL_CERT_FILE",[279,916,917],{},"SSL_CERT_DIR",[279,919,920],{},"REQUESTS_CA_BUNDLE",[279,922,923],{},"CURL_CA_BUNDLE",[279,925,926],{},"GIT_SSL_CAINFO",[279,928,929],{},"NODE_EXTRA_CA_CERTS","), Kraft's own ",[279,932,933],{},"KRAFT_*"," variables and the agent's credential variable. These values are layered on top of it. A sandboxed worker gets none of that allowlist, only what ",[936,937,939],"a",{"href":938},"#sandboxed-workers","Sandboxed workers"," lists, which covers the daemon's proxy and an extra CA.",[530,942,943,948,953],{},[548,944,945],{},[279,946,947],{},"env_passthrough",[548,949,950],{},[279,951,952],{},"[]",[548,954,955,956,958,959,962],{},"Names of variables to carry over from the daemon's own environment, for what the worker allowlist under ",[279,957,856],{}," doesn't cover. A sandbox gets each by name, so its value never appears on the ",[279,960,961],{},"docker"," command line.",[530,964,965,970,974],{},[548,966,967],{},[279,968,969],{},"local_files",[548,971,972],{},[279,973,952],{},[548,975,976,977,980,981,984,985,988,989,992,993,996],{},"Relative paths (no globs, no directories) to copy into every new worktree — for files ",[279,978,979],{},"git worktree add"," can't carry, like an untracked ",[279,982,983],{},".python-version",". Only a file the worktree's ",[279,986,987],{},".gitignore"," covers is copied; an entry that is not, or a directory, is refused and named in its own section of the item's ",[279,990,991],{},"worktree_prepared"," event (",[279,994,995],{},"kraft view events",").",[530,998,999,1004,1008],{},[548,1000,1001],{},[279,1002,1003],{},"deny_tools",[548,1005,1006],{},[279,1007,952],{},[548,1009,1010,1011,1014],{},"Tool names withheld from every agent task on this repo. Part of the repository policy layer (see the ",[279,1012,1013],{},"policy"," row): frozen into each work item when it is filed, and a later addition still applies to running items.",[530,1016,1017,1022,1026],{},[548,1018,1019],{},[279,1020,1021],{},"steering",[548,1023,1024],{},[279,1025,952],{},[548,1027,1028,1029,1032],{},"Names of ",[279,1030,1031],{},"library.yaml"," steering profiles given to every agent launch on this repo, before the task's own steering. Frozen into each work item when it is filed.",[530,1034,1035,1040,1044],{},[548,1036,1037],{},[279,1038,1039],{},"sandbox",[548,1041,1042],{},[279,1043,669],{},[548,1045,1046,1049,1050,1054,1055,1059,1060,1062,1063,1066,1067,617],{},[279,1047,1048],{},"{kind: docker, image: ..., resources: {...}, network: {...}}"," — run this repo's task processes in that container, within the optional ",[936,1051,1053],{"href":1052},"#resource-limits","resource limits"," and ",[936,1056,1058],{"href":1057},"#network-policy","network policy",". Part of the repository policy layer: once set, no chain, node or task can turn it off or change it, limits and network policy included, and ",[279,1061,608],{}," here cannot turn off one a layer set. Set it here or in ",[279,1064,1065],{},"policy.sandbox",", not both. See ",[936,1068,939],{"href":938},[530,1070,1071,1075,1079],{},[548,1072,1073],{},[279,1074,1013],{},[548,1076,1077],{},[279,1078,669],{},[548,1080,1081,1082,867,1085,867,1087,867,1090,867,1092,867,1095,867,1098,1101,1102,867,1105,867,1108,867,1111,1114,1115,1118,1119,1122,1123,1125,1126,1128,1129,1133,1134,617],{},"The repository policy layer: any of ",[279,1083,1084],{},"allowed_tools",[279,1086,1003],{},[279,1088,1089],{},"grants",[279,1091,1039],{},[279,1093,1094],{},"allowed_harnesses",[279,1096,1097],{},"timeout_minutes",[279,1099,1100],{},"max_attempts"," and the four caps (",[279,1103,1104],{},"time_cap_minutes",[279,1106,1107],{},"total_time_cap_minutes",[279,1109,1110],{},"token_budget",[279,1112,1113],{},"budget_usd",", each the work item's own, within ",[279,1116,1117],{},"maxima.work_item","), applied after ",[279,1120,1121],{},"policy.yaml"," and before the chain, and only ever tightening what ",[279,1124,1121],{}," allows. It binds every work item filed in this repo, whatever its chain; a value ",[279,1127,1121],{}," refuses makes ",[936,1130,1132],{"href":1131},"\u002Fconcepts\u002Fvocabulary#intake","intake"," refuse the item. See ",[936,1135,1137],{"href":1136},"\u002Freference\u002Fconfiguration\u002Fpolicy#policy-fields","Policy fields",[530,1139,1140,1145,1149],{},[548,1141,1142],{},[279,1143,1144],{},"automated_review",[548,1146,1147],{},[279,1148,669],{},[548,1150,1151,1152,1155,1156,675,1159,1162,1163,617],{},"The one automated reviewer a chain's ",[279,1153,1154],{},"mr.automated_review"," task waits for: ",[279,1157,1158],{},"bot: \u003Clogin>",[279,1160,1161],{},"check: \u003Cname>",". See ",[936,1164,1166],{"href":1165},"#automated-review","Automated review",[530,1168,1169,1174,1178],{},[548,1170,1171],{},[279,1172,1173],{},"ci_checks",[548,1175,1176],{},[279,1177,602],{},[548,1179,605,1180,1182,1183,1186,1187,1190,1191,1194,1195,675,1197,617],{},[279,1181,608],{}," on a repo with no CI: both CI waits, a chain's ",[279,1184,1185],{},"mr.ci"," task before the merge and its ",[279,1188,1189],{},"mr.post_merge_ci"," task after it, then pass at once, each recording ",[279,1192,1193],{},"ci_not_configured",", instead of waiting on checks that never come. It does not touch ",[279,1196,1154],{},[279,1198,1199],{},"mr.external_approval",[276,1201,1202,1203,1206,1207,1210],{},"No key on an entry passes silently. A key within two edits of a field above (",[279,1204,1205],{},"automated_reviews:",") is refused when the file loads, naming the field it meant. Any other key the table doesn't list is kept, logged as a warning, and fails ",[279,1208,1209],{},"kraft admin doctor"," until it is removed.",[1212,1213,939],"h2",{"id":1214},"sandboxed-workers",[276,1216,1217,1218,1221,1222,1225,1226,1228,1229,1232],{},"A sandboxed task runs ",[279,1219,1220],{},"docker run --init"," (or ",[279,1223,1224],{},"podman run",") with every capability dropped, within its ",[936,1227,1053],{"href":1052},", as a user that leaves what it writes yours, on rootless Docker and Podman too. Which CLI runs it, and what happens on an SELinux-enforcing host, is ",[936,1230,1231],{"href":177},"sandbox.yaml","'s. Only what is listed here reaches the container.",[524,1234,1235,1245],{},[527,1236,1237],{},[530,1238,1239,1242],{},[533,1240,1241],{},"What",[533,1243,1244],{},"How it reaches the container",[543,1246,1247,1255,1279,1298,1329,1341,1381,1449,1470],{},[530,1248,1249,1252],{},[548,1250,1251],{},"The worktree",[548,1253,1254],{},"Mounted read-write at its real path.",[530,1256,1257,1260],{},[548,1258,1259],{},"The repository's refs",[548,1261,1262,1263,1266,1267,1270,1271,1274,1275,996],{},"A private copy per worktree, under ",[279,1264,1265],{},"$KRAFT_HOME\u002Frun\u002Fsandbox-git\u002F",". The worker sees every branch and tag as of its session's start, and may create, move or delete refs, but only there. When a session ends, Kraft moves the item's branch in your repository to where the worker left it, and only if nothing else moved it since; any other ref the worker changed is dropped. What Kraft relies on to decide that lives outside the copy, where the worker cannot write. A branch Kraft could not move is recorded as a ",[279,1268,1269],{},"sandbox_branch_not_synced"," event, and its commit is kept at ",[279,1272,1273],{},"refs\u002Fkraft\u002Funsynced\u002F\u003Cbranch>",". A setup command sees the copy but never moves a branch. A HEAD the worker points at another branch, or a rebase, merge or other git operation it leaves in progress, is never acted on: Kraft stops the item for you instead (see ",[936,1276,1278],{"href":1277},"\u002Fget-started\u002Ftroubleshooting#why-did-my-item-stop","Troubleshooting",[530,1280,1281,1284],{},[548,1282,1283],{},"Objects and LFS content",[548,1285,1286,1287,1054,1290,1293,1294,1297],{},"Your repository's ",[279,1288,1289],{},"objects\u002F",[279,1291,1292],{},"lfs\u002F",", read-write: they are data a commit writes. ",[279,1295,1296],{},"objects\u002Finfo"," (where alternates are named) and alternates themselves are read-only.",[530,1299,1300,1303],{},[548,1301,1302],{},"Workspace members",[548,1304,1305,1306,1309,1310,867,1313,1054,1316,1319,1320,1322,1323,1325,1326,617],{},"Each member's own repository, the same way as the root's: a private copy of its refs, from which Kraft moves the item's branch in that repository, and its objects. The member's ",[279,1307,1308],{},".git"," file, and the ",[279,1311,1312],{},"commondir",[279,1314,1315],{},"gitdir",[279,1317,1318],{},"config.worktree"," of its git directory, are read-only, as the root's are, and every directory from the worktree down to the member is a mount point, which the worker cannot rename or remove. Kraft takes every path it mounts from the connected repository, never from the member's ",[279,1321,1308],{},". A member that is not the checkout Kraft made, or that is reached through a symlink, is not mounted, and the launch does not start. A task fanned out to one member still mounts the whole worktree, and starts in the member. A member branch Kraft could not move is its own ",[279,1324,1269],{}," event, naming the repository, and abandoning or archiving the item removes the members' copies too. See ",[936,1327,152],{"href":1328},"\u002Freference\u002Fconfiguration\u002Frepos\u002Fworkspaces#sandboxing",[530,1330,1331,1335],{},[548,1332,1333],{},[279,1334,870],{},[548,1336,1337,1340],{},[279,1338,1339],{},"$KRAFT_HOME\u002Frun\u002Fsandbox-home\u002F\u003Cwork item>",", read-write and kept across sessions, so an agent CLI keeps its state and can resume a paused session. A CLI config directory Kraft owns (Cursor's) lives there too, one per item.",[530,1342,1343,1346],{},[548,1344,1345],{},"Credentials",[548,1347,1348,867,1351,1354,1355,1054,1357,1359,1360,1362,1363,1366,1367,1370,1371,1377,1378,1380],{},[279,1349,1350],{},"CLAUDE_CODE_OAUTH_TOKEN",[279,1352,1353],{},"ANTHROPIC_API_KEY",", and every name in ",[279,1356,947],{},[279,1358,856],{},", forwarded by name, so their values never appear on the ",[279,1361,961],{}," command line or in ",[279,1364,1365],{},"ps",". The container's own environment still holds them, so ",[279,1368,1369],{},"docker inspect"," shows them to anyone who can reach the container runtime. A name listed under ",[936,1372,1374],{"href":1373},"#credentials",[279,1375,1376],{},"credentials"," is the exception: the container holds only its sentinel, and ",[279,1379,1369],{}," shows that. Logins kept in your home directory or the OS keychain do not reach a sandbox.",[530,1382,1383,1386],{},[548,1384,1385],{},"Proxy and CA",[548,1387,1388,1389,867,1391,867,1393,1054,1395,1397,1398,867,1401,867,1404,1407,1408,1410,1411,1413,1414,1417,1418,1421,1422,1424,1425,1428,1429,867,1431,867,1433,867,1435,867,1437,867,1439,867,1442,1054,1445,1448],{},"The daemon's ",[279,1390,901],{},[279,1392,904],{},[279,1394,907],{},[279,1396,910],{}," (any case), forwarded by name. A proxy on the daemon's loopback (",[279,1399,1400],{},"127.0.0.0\u002F8",[279,1402,1403],{},"localhost",[279,1405,1406],{},"::1",") is left out, since the container's loopback is its own and nothing listens there; ",[279,1409,1209],{}," warns about it. Under a ",[936,1412,1058],{"href":1057}," none of these is forwarded: the container's proxy is Kraft's own. When there is an extra CA, ",[936,1415,1231],{"href":1416},"\u002Freference\u002Fconfiguration\u002Fsandbox#ca-certificates","'s ",[279,1419,1420],{},"ca_bundle"," or else a usable ",[279,1423,914],{}," of the daemon's, Kraft combines it with the image's own roots into one bundle, mounted read-only at ",[279,1426,1427],{},"\u002Fetc\u002Fkraft\u002Fca-bundle.pem",", and points ",[279,1430,914],{},[279,1432,920],{},[279,1434,923],{},[279,1436,926],{},[279,1438,929],{},[279,1440,1441],{},"CODEX_CA_CERTIFICATE",[279,1443,1444],{},"PIP_CERT",[279,1446,1447],{},"npm_config_cafile"," at it. No host path is forwarded. With no extra CA, nothing is mounted or set.",[530,1450,1451,1454],{},[548,1452,1453],{},"Git identity",[548,1455,1456,1054,1459,1462,1463,1054,1466,1469],{},[279,1457,1458],{},"GIT_AUTHOR_*",[279,1460,1461],{},"GIT_COMMITTER_*"," from the daemon's environment, else your ",[279,1464,1465],{},"user.name",[279,1467,1468],{},"user.email"," for the repository. Repository hooks never run in the container, as they never run on a worker's commits outside one.",[530,1471,1472,1475],{},[548,1473,1474],{},"Tool policy",[548,1476,1477,1478,1480,1481,1485,1486,1489,1490,867,1492,1494,1495,1498,1499,1501,1502,1505,1506,1509],{},"Enforced inside the container or refused. Amp's and OpenCode's rules travel with the launch, and Amp's rules file is mounted read-only. Codex's policy is held by Kraft's permission hook, which reaches Kraft only through a ",[936,1479,1058],{"href":1057},"'s channel (see ",[936,1482,1484],{"href":1483},"#callbacks-from-a-sandbox","Callbacks from a sandbox","); without ",[279,1487,1488],{},"network:"," a Codex or Cursor task with ",[279,1491,1084],{},[279,1493,1003],{}," or a grant other than ",[279,1496,1497],{},"git-commit"," is refused. Claude asks Kraft through an MCP tool on every launch, so a sandboxed Claude task without ",[279,1500,1488],{}," is refused whatever its policy. Codex runs with ",[279,1503,1504],{},"sandbox_mode=danger-full-access"," unless a task or harness profile sets a mode other than ",[279,1507,1508],{},"workspace-write",", because its own sandbox cannot start inside Docker.",[276,1511,1512,1516,1517,1519,1520,867,1523,1526,1527,1530,1531,1534,1535,1537,1538,1540,1541,1543],{},[1513,1514,1515],"strong",{},"The image"," must hold the agent CLI (and, for a fallback, every harness it may fall back to) on its ",[279,1518,866],{},", plus ",[279,1521,1522],{},"git",[279,1524,1525],{},"sh"," and CA certificates, and ",[279,1528,1529],{},"curl"," under a network policy (the ",[279,1532,1533],{},"kraft"," shim uses it). Before a task's first launch in an image, Kraft asks the image, through its own entrypoint and with the repository's ",[279,1536,856],{},", whether the command is there; an image that answers no stops the item as a configuration error before anything runs. ",[279,1539,1209],{}," checks that the runtime answers, that SELinux has an answer in ",[279,1542,1231],{}," where it enforces, that an extra CA can be read, and that the image is pulled; pull it before filing work, or the first launch pulls it inside the task's time cap.",[276,1545,1546,1549,1550,1552,1553,1555],{},[1513,1547,1548],{},"Not yet covered."," Without a ",[936,1551,1058],{"href":1057},", the container has the default bridge network: open egress, and on a cloud VM the metadata address is reachable. A worker can still delete objects from your repository, which breaks it loudly but cannot put content on another branch. Without a network policy a worker cannot reach Kraft at all: see ",[936,1554,1484],{"href":1483},". Only repositories keeping refs in git's default files storage are supported; a reftable repository stops the item.",[276,1557,1558],{},"Abandoning or archiving an item removes its ref store and sandbox home.",[1560,1561,1484],"h3",{"id":1562},"callbacks-from-a-sandbox",[276,1564,1565,1566,1568,1569,1571,1572,1054,1574,1576,1577,1580,1581,1583,1584,1586,1587,867,1590,867,1593,867,1596,1054,1599,1602,1603,675,1606,1609,1610,1612,1613,1616],{},"Under a ",[936,1567,1058],{"href":1057}," a sandboxed worker reaches Kraft through its session's own channel, the one its proxy runs on. Every such container has Kraft's ",[279,1570,1533],{}," shim, a ",[279,1573,1525],{},[279,1575,1529],{}," script, mounted read-only at ",[279,1578,1579],{},"\u002Fopt\u002Fkraft\u002Fbin"," and first on its ",[279,1582,866],{}," (the image's own ",[279,1585,866],{}," after it). The shim has only the verbs a worker needs: ",[279,1588,1589],{},"kraft item progress K",[279,1591,1592],{},"kraft item reply THREAD --body ...",[279,1594,1595],{},"kraft item retry",[279,1597,1598],{},"kraft view show|threads",[279,1600,1601],{},"kraft admin permission-hook HARNESS",". There is no ",[279,1604,1605],{},"kraft view diff",[279,1607,1608],{},"compare"," inside: Kraft reads no git in a worktree while a sandboxed session runs in it, and the worker has ",[279,1611,1522],{}," itself. It sends each to ",[279,1614,1615],{},"http:\u002F\u002Fkraft"," through the proxy, which answers it in the Kraft server itself, never forwarded anywhere.",[276,1618,1619,1620,1623,1624,1627],{},"A call acts as the session whose channel it came in on, whatever headers or token the worker sends, and only on that session's own work item: its progress and retry, replies on its own item's threads, its own permission asks, and reads of its own item. Anything else is answered ",[279,1621,1622],{},"403"," and recorded as a ",[279,1625,1626],{},"sandbox_egress_refused"," event, like a refused host. No allow list entry is needed for any of this.",[276,1629,1630,1631,316],{},"Permission checks under ",[279,1632,1488],{},[1634,1635,1636,1651,1665],"ul",{},[1637,1638,1639,1642,1643,1646,1647,1650],"li",{},[1513,1640,1641],{},"Codex."," Its hook is the shim's ",[279,1644,1645],{},"admin permission-hook codex",". The hash Codex needs to trust the hook is asked of the image's own ",[279,1648,1649],{},"codex",", in a short container with no network, since Codex silently skips a hook it does not trust and runs the call. When that cannot be done (the image's Codex does not start, or does not trust the hook), the task is refused. When the shim gets no answer from Kraft, it prints Codex's own deny.",[1637,1652,1653,1656,1657,1660,1661,1664],{},[1513,1654,1655],{},"Claude."," Launched with Kraft's MCP server at ",[279,1658,1659],{},"http:\u002F\u002Fkraft\u002Fmcp"," and no other (",[279,1662,1663],{},"--strict-mcp-config","), which answers its permission tool for that session alone.",[1637,1666,1667,1670],{},[1513,1668,1669],{},"Cursor"," ignores a proxy, so it is refused under a network policy.",[276,1672,1673,1678,1679,1681,1682,1685,1686,1689,1690,1693],{},[1513,1674,1675,1676],{},"Without ",[279,1677,1488],{}," the container has no route to Kraft. A sandboxed Claude task is refused before it starts (its permission tool would be missing and the CLI would exit), as is a Codex or Cursor task with a tool policy. Prompts do not tell the worker to run ",[279,1680,1533],{},": it tags its commits with the task number instead of reporting progress, and says what it did about each review thread in its result. No reply agent is launched for review threads: a ",[279,1683,1684],{},"comment"," review answers ",[279,1687,1688],{},"reply_agent: false",", and the item records a ",[279,1691,1692],{},"reply_agent_skipped"," event saying why. An escalation turn cannot resume the chain itself; a person retries it.",[1560,1695,1697],{"id":1696},"resource-limits","Resource limits",[284,1699,1701],{"className":286,"code":1700,"language":288,"meta":289,"style":289},"sandbox:\n  kind: docker\n  image: ghcr.io\u002Facme\u002Fagent:1\n  resources: {cpu: 2, memory: 4g, pids: 512}\n",[279,1702,1703,1709,1719,1729],{"__ignoreMap":289},[293,1704,1705,1707],{"class":295,"line":296},[293,1706,1039],{"class":299},[293,1708,304],{"class":303},[293,1710,1711,1714,1716],{"class":295,"line":307},[293,1712,1713],{"class":299},"  kind",[293,1715,316],{"class":303},[293,1717,1718],{"class":319}," docker\n",[293,1720,1721,1724,1726],{"class":295,"line":323},[293,1722,1723],{"class":299},"  image",[293,1725,316],{"class":303},[293,1727,1728],{"class":319}," ghcr.io\u002Facme\u002Fagent:1\n",[293,1730,1731,1734,1736,1739,1742,1744,1748,1750,1753,1755,1758,1760,1763,1765,1768],{"class":295,"line":334},[293,1732,1733],{"class":299},"  resources",[293,1735,316],{"class":303},[293,1737,1738],{"class":303}," {",[293,1740,1741],{"class":299},"cpu",[293,1743,316],{"class":303},[293,1745,1747],{"class":1746},"sbssI"," 2",[293,1749,488],{"class":303},[293,1751,1752],{"class":299}," memory",[293,1754,316],{"class":303},[293,1756,1757],{"class":319}," 4g",[293,1759,488],{"class":303},[293,1761,1762],{"class":299}," pids",[293,1764,316],{"class":303},[293,1766,1767],{"class":1746}," 512",[293,1769,1770],{"class":303},"}\n",[524,1772,1773,1783],{},[527,1774,1775],{},[530,1776,1777,1779,1781],{},[533,1778,535],{},[533,1780,538],{},[533,1782,541],{},[543,1784,1785,1808,1844],{},[530,1786,1787,1791,1794],{},[548,1788,1789],{},[279,1790,1741],{},[548,1792,1793],{},"unset",[548,1795,1796,1797,1800,1801,1804,1805,617],{},"CPUs the container may use (",[279,1798,1799],{},"--cpus","), fractions allowed: ",[279,1802,1803],{},"0.5",", at least ",[279,1806,1807],{},"0.01",[530,1809,1810,1815,1817],{},[548,1811,1812],{},[279,1813,1814],{},"memory",[548,1816,1793],{},[548,1818,1819,1820,867,1823,867,1826,675,1829,1832,1833,1836,1837,1840,1841,617],{},"A hard memory limit: a whole number with an optional unit ",[279,1821,1822],{},"b",[279,1824,1825],{},"k",[279,1827,1828],{},"m",[279,1830,1831],{},"g"," (binary, as Docker reads them), at least ",[279,1834,1835],{},"6m",". Swap is held to the same limit (",[279,1838,1839],{},"--memory-swap",") wherever the runtime can limit swap; where it cannot, the container may swap as much again, and doctor says ",[279,1842,1843],{},"memory (swap unbounded)",[530,1845,1846,1851,1856],{},[548,1847,1848],{},[279,1849,1850],{},"pids",[548,1852,1853],{},[279,1854,1855],{},"4096",[548,1857,1858,1859,1862],{},"Processes and threads at once (",[279,1860,1861],{},"--pids-limit","), which bounds a fork bomb.",[276,1864,1865,1866,1869,1870,1873],{},"The limits bind every sandboxed launch of the item, its setup command included, and they are part of the sandbox: a chain, node or task cannot loosen, tighten or drop them. ",[279,1867,1868],{},"resources: {}"," sets none, and is the same sandbox as no ",[279,1871,1872],{},"resources"," at all.",[276,1875,1876,1877,1880,1881,1884,1885,1888,1889,1891,1892,1894,1895,1897],{},"A limit is applied or the task does not start. Docker runs a container without a limit whose cgroup controller is missing, with only a warning, and rootless Podman on cgroup v1 ignores every limit, so Kraft asks the runtime what it can enforce (Docker's ",[279,1878,1879],{},"info",", Podman's cgroup controllers) and stops a task that sets a limit it cannot as a configuration error naming it. The fix is cgroup v2 and, for a rootless runtime, delegating the controllers to your user (systemd ",[279,1882,1883],{},"Delegate=cpu memory pids"," for ",[279,1886,1887],{},"user@.service","). A runtime whose ",[279,1890,1879],{}," gives no answer Kraft can read (a daemon still starting) enforces nothing as far as Kraft knows: a task that sets a limit stops, and the next launch asks again. The default ",[279,1893,1850],{}," is left out, never refused, where the runtime cannot limit processes. ",[279,1896,1209],{}," names the limits the runtime enforces on each sandboxed repository's row, and fails a row whose limits it cannot.",[276,1899,1900,1903,1904,1907,1908,1911,1912,1915,1916,992,1919,1922,1923,1926,1927,1933,1934,1937],{},[1513,1901,1902],{},"Out of memory."," A session container is kept after it exits, without a log of its own (",[279,1905,1906],{},"--log-driver=none","; Kraft reads the output as it runs), so Kraft can ask the runtime whether the memory limit killed it, then removes it by name. The ",[279,1909,1910],{},"docker run"," client runs in a directory of Kraft's under ",[279,1913,1914],{},"$KRAFT_HOME\u002Frun\u002Fsandbox-client\u002F",", never the worktree. When a process in the sandbox was killed by its memory limit, Kraft records a ",[279,1917,1918],{},"sandbox_oom_killed",[279,1920,1921],{},"{session_id, memory, confirmed}",") and the item stops for you as a configuration error naming the limit: the same limit would kill a retry the same way, so no fix loop runs. A session that still reported a result of its own (something it ran was killed and it got past it) keeps that result, with the event recorded. The runtime's own word makes a confirmed kill: Docker's out-of-memory flag, and on Podman also the ",[279,1924,1925],{},"oom"," file its conmon writes where the client runs, since Podman on cgroup v1 never sets the flag. The runtime can set its flag a moment after the container exits, so for a container that exited 137 under a memory limit Kraft asks again for up to two seconds. Docker on cgroup v2 can also drop the flag altogether (",[936,1928,1932],{"href":1929,"rel":1930},"https:\u002F\u002Fgithub.com\u002Fmoby\u002Fmoby\u002Fissues\u002F41929",[1931],"nofollow","moby#41929","), so a container that exited 137 under a memory limit, that Kraft did not stop itself (a pause, a time cap, a cancel), and that is still unflagged after those two seconds counts as an unconfirmed kill: ",[279,1935,1936],{},"confirmed: false",", and the stop says the runtime did not confirm it was the limit. Exit 137 from a container with no memory limit is never counted. A setup command the limit killed stops the item the same way.",[1560,1939,1941],{"id":1940},"network-policy","Network policy",[284,1943,1945],{"className":286,"code":1944,"language":288,"meta":289,"style":289},"sandbox:\n  kind: docker\n  image: ghcr.io\u002Facme\u002Fagent@sha256:...\n  network:\n    install: {allow: [registry.npmjs.org, pypi.org, files.pythonhosted.org]}\n    runtime: {allow: [api.github.com], deny: []}\n",[279,1946,1947,1953,1961,1970,1977,2009],{"__ignoreMap":289},[293,1948,1949,1951],{"class":295,"line":296},[293,1950,1039],{"class":299},[293,1952,304],{"class":303},[293,1954,1955,1957,1959],{"class":295,"line":307},[293,1956,1713],{"class":299},[293,1958,316],{"class":303},[293,1960,1718],{"class":319},[293,1962,1963,1965,1967],{"class":295,"line":323},[293,1964,1723],{"class":299},[293,1966,316],{"class":303},[293,1968,1969],{"class":319}," ghcr.io\u002Facme\u002Fagent@sha256:...\n",[293,1971,1972,1975],{"class":295,"line":334},[293,1973,1974],{"class":299},"  network",[293,1976,304],{"class":303},[293,1978,1979,1982,1984,1986,1989,1991,1993,1996,1998,2001,2003,2006],{"class":295,"line":345},[293,1980,1981],{"class":299},"    install",[293,1983,316],{"class":303},[293,1985,1738],{"class":303},[293,1987,1988],{"class":299},"allow",[293,1990,316],{"class":303},[293,1992,482],{"class":303},[293,1994,1995],{"class":319},"registry.npmjs.org",[293,1997,488],{"class":303},[293,1999,2000],{"class":319}," pypi.org",[293,2002,488],{"class":303},[293,2004,2005],{"class":319}," files.pythonhosted.org",[293,2007,2008],{"class":303},"]}\n",[293,2010,2011,2014,2016,2018,2020,2022,2024,2027,2030,2033,2035],{"class":295,"line":356},[293,2012,2013],{"class":299},"    runtime",[293,2015,316],{"class":303},[293,2017,1738],{"class":303},[293,2019,1988],{"class":299},[293,2021,316],{"class":303},[293,2023,482],{"class":303},[293,2025,2026],{"class":319},"api.github.com",[293,2028,2029],{"class":303},"],",[293,2031,2032],{"class":299}," deny",[293,2034,316],{"class":303},[293,2036,2037],{"class":303}," []}\n",[276,2039,1675,2040,2043,2044,2046,2047,2049],{},[279,2041,2042],{},"network",", a sandboxed task has the runtime's default network and can reach anything, the cloud metadata address included. ",[279,2045,1209],{}," warns about each sandboxed repository with open egress. With ",[279,2048,2042],{}," set, a sandboxed task reaches only what its lists allow.",[276,2051,2052,2055,2056,2059,2060,2062,2063,2066,2067,1054,2069,2071,2072,2075,2076,2078,2079,2082,2083,867,2085,1054,2087,2089],{},[1513,2053,2054],{},"How it works."," Each session gets a relay container with no network of its own, from ",[279,2057,2058],{},"relay_image"," in ",[936,2061,1231],{"href":177},". The worker joins the relay's network namespace, so loopback is its only interface. The relay forwards ",[279,2064,2065],{},"127.0.0.1:3128"," there to a unix socket that belongs to that session alone, and Kraft's daemon serves the proxy on it. The worker gets ",[279,2068,901],{},[279,2070,904],{},", in both cases, set to ",[279,2073,2074],{},"http:\u002F\u002F127.0.0.1:3128",", an empty ",[279,2077,910],{},", and ",[279,2080,2081],{},"NODE_USE_ENV_PROXY=1",". The daemon makes each allowed connection itself, through its own ",[279,2084,904],{},[279,2086,901],{},[279,2088,910],{}," when it has them, so a proxy on the host's loopback works here. After a daemon restart, a running session's channel is reopened with the lists it launched under, not the current configuration.",[276,2091,2092,2095,2096,2099,2100,2103,2104,2107,2108,2111,2112,2115,2116,2118,2119,2122,2123,2125],{},[1513,2093,2094],{},"Docker Desktop and Podman machine."," These run containers in a VM, where a container cannot connect to a host unix socket. Kraft checks once per runtime whether a container can connect to a host socket. When it cannot, and the runtime reports that it runs in a VM (Docker Desktop's ",[279,2097,2098],{},"OperatingSystem",", Podman's ",[279,2101,2102],{},"ServiceIsRemote","), Kraft carries the channel in two hops instead. The relay the worker joins, still with no network, forwards to a socket in a volume made for that session. A second relay, on the runtime's default network, forwards that socket to one mutual-TLS listener the daemon keeps on ",[279,2105,2106],{},"127.0.0.1",", which it reaches as ",[279,2109,2110],{},"host.docker.internal"," (Docker) or ",[279,2113,2114],{},"host.containers.internal"," (Podman). Both relays run ",[279,2117,2058],{},". The listener trusts only a client certificate from Kraft's own CA, kept under ",[279,2120,2121],{},"run\u002Fca\u002F",", and takes the session from that certificate, one per session, which only the second relay mounts. The worker shares no network, volume or mount with it. ",[279,2124,1209],{}," names the transport in the repository's sandbox row and checks that the listener answers.",[524,2127,2128,2137],{},[527,2129,2130],{},[530,2131,2132,2134],{},[533,2133,535],{},[533,2135,2136],{},"Applies to",[543,2138,2139,2151],{},[530,2140,2141,2146],{},[548,2142,2143],{},[279,2144,2145],{},"install",[548,2147,2148,2149,617],{},"The repository's ",[279,2150,833],{},[530,2152,2153,2158],{},[548,2154,2155],{},[279,2156,2157],{},"runtime",[548,2159,2160],{},"Every task session: agents, commands, test scopes, reviews and escalations.",[276,2162,2163,2164,1054,2166,2169],{},"Each phase takes ",[279,2165,1988],{},[279,2167,2168],{},"deny",", lists of hosts:",[1634,2171,2172,2181,2194,2203],{},[1637,2173,2174,2176,2177,2180],{},[279,2175,2026],{}," names that host exactly, and ",[279,2178,2179],{},"api.github.com:443"," names it on one port.",[1637,2182,2183,2186,2187,2190,2191,2193],{},[279,2184,2185],{},"*.example.com"," covers exactly one label in front of ",[279,2188,2189],{},"example.com",", not ",[279,2192,2189],{}," itself.",[1637,2195,2196,1054,2199,2202],{},[279,2197,2198],{},"*",[279,2200,2201],{},"**"," cover everything.",[1637,2204,2205],{},"CIDRs are not accepted.",[276,2207,2208,2209,2212,2213,2216,2217,2219],{},"Deny wins over allow. A phase that is missing or has empty lists allows nothing, so ",[279,2210,2211],{},"network: {runtime: {}}"," denies everything. Only ",[279,2214,2215],{},"network: {}"," with no phase at all is the same as no ",[279,2218,2042],{},": open. The lists are part of the sandbox, so a chain, node or task cannot change them.",[276,2221,2222,2225,2226,2228,2229,2232,2233,2235,2236,2239,2240,2243,2244,2246],{},[1513,2223,2224],{},"Harness hosts."," An agent session's ",[279,2227,2157],{}," allow list also gets the hosts its harness file declares under ",[279,2230,2231],{},"network.requires",", and a ",[279,2234,2168],{}," still wins over them. Only ",[279,2237,2238],{},"api.anthropic.com"," (Claude) and ",[279,2241,2242],{},"api.openai.com"," (Codex) have been checked. The rest are unverified: if a CLI needs a host its harness does not list, allow it in ",[279,2245,2157],{},". OpenCode declares none, because its hosts depend on its provider.",[276,2248,2249,2252],{},[1513,2250,2251],{},"Always denied",", whatever the lists say:",[1634,2254,2255,2258,2268,2271],{},[1637,2256,2257],{},"loopback;",[1637,2259,2260,2261,867,2264,2267],{},"link-local (",[279,2262,2263],{},"169.254.0.0\u002F16",[279,2265,2266],{},"fe80::\u002F10",");",[1637,2269,2270],{},"cloud metadata names and addresses;",[1637,2272,2273],{},"the host's own addresses, which are what its hostname resolves to.",[276,2275,2276,2277,675,2279,2281,2282,2285,2286,2289],{},"Kraft checks the address a name resolves to, resolving it once on the host, and then connects to that checked address. Through an upstream proxy (the daemon's own ",[279,2278,901],{},[279,2280,904],{},"), Kraft still checks the address, but the upstream resolves the name again and connects to whatever it gets. If any address a name resolves to is denied, the whole name is refused. An IPv4 address carried inside an IPv6 one (IPv4-mapped, IPv4-compatible, 6to4, NAT64 ",[279,2283,2284],{},"64:ff9b::\u002F96",") is checked as that IPv4 address as well. Any other non-public address (RFC 1918, IPv6 ULA, the shared ",[279,2287,2288],{},"100.64.0.0\u002F10"," range, reserved ranges) is reachable only through an allow entry naming its host exactly, never through a wildcard.",[276,2291,2292,2295,2296,2298,2299,992,2301,2304,2305,2308,2309,2312],{},[1513,2293,2294],{},"Refusals."," A refused request is answered ",[279,2297,1622],{}," with a one-line reason. Kraft records one ",[279,2300,1626],{},[279,2302,2303],{},"{session_id, host, port, phase, reason}",") per session and host, so a retry loop does not flood the timeline: ",[279,2306,2307],{},"kraft view events --type sandbox_egress_refused",". After 100 hosts, one last event with ",[279,2310,2311],{},"suppressed: true"," says later refusals in that session are not recorded; they are still refused.",[276,2314,2315,2318,2319,2322,2323,2326],{},[1513,2316,2317],{},"Clients that ignore the proxy."," Anything that does not use ",[279,2320,2321],{},"HTTP(S)_PROXY",", such as git over SSH or a raw socket, has no route at all. A harness whose file declares ",[279,2324,2325],{},"proxy_aware: false"," (Cursor) is refused under a network policy as a configuration error before it starts.",[276,2328,2329,2332,2333,2336,2337,2339,2340,2343],{},[1513,2330,2331],{},"It fails closed."," A session that cannot get its route stops as a configuration error and runs nothing. This happens when the socket check does not run, when a container cannot connect to the socket on a runtime that does not report a VM (an SELinux denial, or the permissions on ",[279,2334,2335],{},"run\u002Fsn\u002F","), when the relay image is not pulled, or when a relay does not start. ",[279,2338,1209],{}," fails the repository's sandbox row for the first three, naming ",[279,2341,2342],{},"docker pull \u003Crelay_image>"," for a missing image, so pull it before filing work.",[276,2345,2346],{},[1513,2347,2348],{},"Limits.",[1634,2350,2351,2357],{},[1637,2352,2353,2356],{},[1513,2354,2355],{},"Docker Desktop and Podman machine: seven days per session."," A session's client certificate expires after seven days; a session that runs longer loses its route and fails closed. The listener keeps its port across a daemon restart. If another program has taken that port by then, the listener moves to a new one, and sessions started before the restart have no route until they are retried.",[1637,2358,2359,2362,2363,2366,2367,2370],{},[1513,2360,2361],{},"Domain fronting."," The allow list is enforced on the name the client asks for and on the address Kraft dials. It does not see what travels inside the TLS connection. An allowed name on a shared CDN address can reach other names that address serves, by TLS SNI or the HTTP ",[279,2364,2365],{},"Host"," header. Kraft terminates TLS only for a managed ",[936,2368,2369],{"href":1373},"credential","'s host.",[1560,2372,1345],{"id":1376},[284,2374,2376],{"className":286,"code":2375,"language":288,"meta":289,"style":289},"sandbox:\n  kind: docker\n  image: ghcr.io\u002Facme\u002Fagent@sha256:...\n  network:\n    runtime: {allow: [registry.example.com]}\n  credentials:\n    - env: ANTHROPIC_API_KEY          # its harness says how\n    - env: REGISTRY_TOKEN             # the repository's own, in full\n      service: registry\n      inject: [{domain: registry.example.com, header: authorization, format: \"Bearer %s\"}]\n",[279,2377,2378,2384,2392,2400,2406,2425,2432,2448,2462,2472],{"__ignoreMap":289},[293,2379,2380,2382],{"class":295,"line":296},[293,2381,1039],{"class":299},[293,2383,304],{"class":303},[293,2385,2386,2388,2390],{"class":295,"line":307},[293,2387,1713],{"class":299},[293,2389,316],{"class":303},[293,2391,1718],{"class":319},[293,2393,2394,2396,2398],{"class":295,"line":323},[293,2395,1723],{"class":299},[293,2397,316],{"class":303},[293,2399,1969],{"class":319},[293,2401,2402,2404],{"class":295,"line":334},[293,2403,1974],{"class":299},[293,2405,304],{"class":303},[293,2407,2408,2410,2412,2414,2416,2418,2420,2423],{"class":295,"line":345},[293,2409,2013],{"class":299},[293,2411,316],{"class":303},[293,2413,1738],{"class":303},[293,2415,1988],{"class":299},[293,2417,316],{"class":303},[293,2419,482],{"class":303},[293,2421,2422],{"class":319},"registry.example.com",[293,2424,2008],{"class":303},[293,2426,2427,2430],{"class":295,"line":356},[293,2428,2429],{"class":299},"  credentials",[293,2431,304],{"class":303},[293,2433,2434,2437,2440,2442,2445],{"class":295,"line":367},[293,2435,2436],{"class":303},"    -",[293,2438,2439],{"class":299}," env",[293,2441,316],{"class":303},[293,2443,2444],{"class":319}," ANTHROPIC_API_KEY",[293,2446,2447],{"class":521},"          # its harness says how\n",[293,2449,2450,2452,2454,2456,2459],{"class":295,"line":377},[293,2451,2436],{"class":303},[293,2453,2439],{"class":299},[293,2455,316],{"class":303},[293,2457,2458],{"class":319}," REGISTRY_TOKEN",[293,2460,2461],{"class":521},"             # the repository's own, in full\n",[293,2463,2464,2467,2469],{"class":295,"line":394},[293,2465,2466],{"class":299},"      service",[293,2468,316],{"class":303},[293,2470,2471],{"class":319}," registry\n",[293,2473,2474,2477,2479,2482,2485,2487,2490,2492,2495,2497,2500,2502,2505,2507,2509,2512,2515],{"class":295,"line":405},[293,2475,2476],{"class":299},"      inject",[293,2478,316],{"class":303},[293,2480,2481],{"class":303}," [{",[293,2483,2484],{"class":299},"domain",[293,2486,316],{"class":303},[293,2488,2489],{"class":319}," registry.example.com",[293,2491,488],{"class":303},[293,2493,2494],{"class":299}," header",[293,2496,316],{"class":303},[293,2498,2499],{"class":319}," authorization",[293,2501,488],{"class":303},[293,2503,2504],{"class":299}," format",[293,2506,316],{"class":303},[293,2508,385],{"class":303},[293,2510,2511],{"class":319},"Bearer %s",[293,2513,2514],{"class":303},"\"",[293,2516,2517],{"class":303},"}]\n",[276,2519,2520,2521,2523],{},"A variable listed under ",[279,2522,1376],{}," never reaches the container. The container gets a sentinel in its place, and Kraft's egress proxy puts the daemon's own value into the named header on requests to the named host. This holds for every sandboxed launch of the item: agent sessions, subprocess tasks, test scopes and setup commands, whose code the worker may have written. A variable not listed passes through as the Credentials row above describes. Nothing is managed unless the repository lists it.",[1634,2525,2526,2604,2637,2651,2666,2686],{},[1637,2527,2528,2531,2532,2534,2535,2538,2539,2534,2541,2544,2545,2547,2548,2534,2551,2544,2553,2555,2556,2534,2559,2544,2562,2565,2566,2568,2569,2572,2573,2576,2577,2059,2580,2583,2584,2587,2588,2590,2591,2593,2594,2597,2598,2600,2601,2603],{},[279,2529,2530],{},"env: NAME"," alone takes the rest from the harness file of the session's CLI. Claude declares ",[279,2533,1353],{}," (",[279,2536,2537],{},"x-api-key",") and ",[279,2540,1350],{},[279,2542,2543],{},"Authorization: Bearer",") on ",[279,2546,2238],{},", Codex ",[279,2549,2550],{},"CODEX_API_KEY",[279,2552,2543],{},[279,2554,2242],{},", and Gemini ",[279,2557,2558],{},"GEMINI_API_KEY",[279,2560,2561],{},"x-goog-api-key",[279,2563,2564],{},"generativelanguage.googleapis.com",". Codex's is ",[279,2567,2550],{}," because ",[279,2570,2571],{},"codex exec"," does not send an ",[279,2574,2575],{},"OPENAI_API_KEY",". Verified against the real CLI, in a Docker and a Podman sandbox, by ",[279,2578,2579],{},"e2e(\u003Ccli>)",[279,2581,2582],{},"tests\u002Fworker\u002Ftest_credentials_docker.py"," (run with ",[279,2585,2586],{},"KRAFT_E2E=1","): ",[279,2589,1353],{}," with Claude Code 2.1.284, ",[279,2592,2550],{}," with codex-cli 0.158.0 (which opens a WebSocket to the host first; against the test's fake host it fell back to HTTPS, and whether it does when the real host's ",[279,2595,2596],{},"101"," ends the connection is untested), and ",[279,2599,2558],{}," with Gemini CLI 0.61.0. ",[279,2602,1350],{}," is unverified. A CLI that pins its host's certificate, or needs HTTP\u002F2, cannot be managed this way, and its variable should stay unlisted. A CLI whose harness does not declare the name gets the sentinel and nothing injected.",[1637,2605,2606,2607,2078,2610,2613,2614,2616,2617,2620,2621,2624,2625,2628,2629,2632,2633,2636],{},"A repository's own credential gives ",[279,2608,2609],{},"service",[279,2611,2612],{},"inject",": a list of an exact ",[279,2615,2484],{},", a ",[279,2618,2619],{},"header",", and optionally a ",[279,2622,2623],{},"format"," holding ",[279,2626,2627],{},"%s"," where the value goes. ",[279,2630,2631],{},"sentinel"," sets what the container sees, ",[279,2634,2635],{},"kraft-proxy-managed"," unless the harness says otherwise.",[1637,2638,2639,2642,2643,2646,2647,2650],{},[279,2640,2641],{},"phase"," limits a credential to the launches of those phases: ",[279,2644,2645],{},"[install]"," for the setup command, ",[279,2648,2649],{},"[runtime]"," for agent sessions, subprocess tasks and test scopes. A launch in another phase gets neither its sentinel nor its value. Unset, it is in both.",[1637,2652,2653,2656,2657,2659,2660,2662,2663,2665],{},[279,2654,2655],{},"source"," names the daemon's own environment variable its value is read from, instead of ",[279,2658,856],{}," in the worker's environment. The worker's environment is never read for it, not even as a fallback, and the ",[279,2661,2655],{}," variable is kept out of the container as ",[279,2664,856],{}," is. Set but empty counts as no value.",[1637,2667,2668,2670,2671,2673,2674,2676,2677,2679,2680,2682,2683,2685],{},[279,2669,1376],{}," needs ",[279,2672,2042],{},". A repository's own credential's ",[279,2675,2484],{}," must be named in the ",[279,2678,1988],{}," list of each phase its ",[279,2681,2641],{}," lists (either phase, when unset), not only covered by a wildcard or a harness's hosts, and not denied there. A variable can be listed once per phase, and one header on one host set by one credential per phase, so one name can serve two entries whose ",[279,2684,2641],{}," lists do not overlap. Anything else fails when repos.yaml loads.",[1637,2687,2688,2690],{},[279,2689,1376],{}," is part of the sandbox, so a chain, node or task cannot change it.",[276,2692,2693,2695,2696,2698,2699,2702,2703,2705,2706,2708,2709,2712],{},[1513,2694,2054],{}," When a session manages a credential, its container's CA bundle (mounted at ",[279,2697,1427],{},", as for an extra CA) also holds Kraft's own CA. A ",[279,2700,2701],{},"CONNECT"," to a host a managed credential goes to, where the session's phase allows that host by name, is not tunnelled: the proxy answers the worker's TLS itself with a certificate Kraft's CA issued for exactly that host, refusing a handshake that names another, and makes its own TLS connection to the real host, verified against the daemon's own roots plus the extra CA, never Kraft's. It then passes each request on, one at a time, keep-alive and pipelined included. A request whose ",[279,2704,2365],{}," is not the host it connected to, or where any instance of the credential's header is not exactly the sentinel in its ",[279,2707,2623],{},", or that carries no managed credential's sentinel at all, is answered 403 and recorded as a refused host, and never reaches the host, so the worker cannot use the route with a key of its own. Otherwise every instance of the header is dropped and one set to the real value. A daemon that has no value refuses those requests rather than forward the sentinel. Every other host stays a blind tunnel, including one allowed only through a wildcard, and a plain ",[279,2710,2711],{},"http:\u002F\u002F"," request to a managed host is refused.",[276,2714,2715,2716,2718,2719,2721,2722,2724,2725,2727,2728,2730,2731,2733,2734,2736,2737,2739,2740,2742,2743,2745,2746,2748],{},"The value is read the way the rest of the worker's environment is: the daemon's own, a name in ",[279,2717,947],{},", or ",[279,2720,856],{},"; a credential with ",[279,2723,2655],{}," reads the daemon's own variable of that name alone. Kraft keeps it only in the daemon's memory and puts it only in the header on its way out. It is not in the container's environment, argv, mounts, ",[279,2726,1369],{}," (which shows the sentinel), the ",[279,2729,961],{}," client's environment, an event, a log line or the session's row. ",[279,2732,1209],{}," lists, for each sandboxed repository with ",[279,2735,1376],{},", which names are managed and on which hosts, and which names a harness declares that still pass through; a managed name with no value (under its ",[279,2738,2655],{},", for one that has one) fails the row, and a credential with ",[279,2741,2641],{}," says which. A name listed alone that no harness declares, such as ",[279,2744,2575],{}," for Codex (whose name is ",[279,2747,2550],{},"), warns, naming what each harness does declare: it holds only its sentinel and nothing injects it.",[276,2750,2751],{},[1513,2752,2348],{},[1634,2754,2755,2768,2774,2792],{},[1637,2756,2757,2760,2761,2764,2765,2767],{},[1513,2758,2759],{},"HTTP\u002F1.1 only."," The terminated connection offers only ",[279,2762,2763],{},"http\u002F1.1",", in both directions. A CLI that needs HTTP\u002F2 does not get it, and a WebSocket or any other upgrade does not work to a managed host: a ",[279,2766,2596],{}," ends the connection.",[1637,2769,2770,2773],{},[1513,2771,2772],{},"Responses pass back as they are."," Only the request is rewritten. A host that echoed the real key in a response header or body would hand it to the worker; Kraft does not strip it.",[1637,2775,2776,2781,2782,867,2784,2787,2788,2791],{},[1513,2777,2778,2779,617],{},"Listing a credential does not clean the item's ",[279,2780,870],{}," A sandboxed item keeps one ",[279,2783,870],{},[279,2785,2786],{},"$KRAFT_HOME\u002Frun\u002Fsandbox-home\u002F\u003Cwork item id>",", across its sessions. A session that ran before the variable was listed had the real value, and its CLI may have saved it there, in a login or config file. To start clean, pause the item and delete that directory; Kraft makes an empty one at the next session, and that session starts without the CLI's saved state, so it cannot resume the earlier one. ",[279,2789,2790],{},"kraft view show"," prints the work item id.",[1637,2793,2794,2797,2798,2800],{},[1513,2795,2796],{},"After a daemon restart,"," a running session's credentials are restored with the rules it launched with and their values read again through the repository entry it launched with. If that repository has been disconnected, its ",[279,2799,281],{}," no longer loads, or the value is gone, its requests carrying the sentinel are refused until the item is retried.",[1560,2802,2804],{"id":2803},"kits","Kits",[276,2806,2807,2810,2811,867,2814,2817,2818,2821],{},[1513,2808,2809],{},"Operator-authored Kits only."," Docker's published Kits (",[279,2812,2813],{},"docker\u002Fsbx-kit-shell",[279,2815,2816],{},"docker\u002Fsbx-kit-claude",") require capabilities Kraft does not enforce, so Kraft refuses them. Build a Kit for Kraft instead: ",[936,2819,2820],{"href":108},"Build a worker Kit"," gives one for Claude.",[284,2823,2825],{"className":286,"code":2824,"language":288,"meta":289,"style":289},"sandbox:\n  kind: kit\n  runtime: docker\n  kit: registry.example.com\u002Facme\u002Fkraft-worker-claude@sha256:\u003C64 hex>\n",[279,2826,2827,2833,2842,2851],{"__ignoreMap":289},[293,2828,2829,2831],{"class":295,"line":296},[293,2830,1039],{"class":299},[293,2832,304],{"class":303},[293,2834,2835,2837,2839],{"class":295,"line":307},[293,2836,1713],{"class":299},[293,2838,316],{"class":303},[293,2840,2841],{"class":319}," kit\n",[293,2843,2844,2847,2849],{"class":295,"line":323},[293,2845,2846],{"class":299},"  runtime",[293,2848,316],{"class":303},[293,2850,1718],{"class":319},[293,2852,2853,2856,2858],{"class":295,"line":334},[293,2854,2855],{"class":299},"  kit",[293,2857,316],{"class":303},[293,2859,2860],{"class":319}," registry.example.com\u002Facme\u002Fkraft-worker-claude@sha256:\u003C64 hex>\n",[276,2862,2863,2864,2869,2870,2873,2874,2877,2878,1054,2880,2883,2884,867,2887,867,2889,1054,2891,2893,2894,2896,2897,2900],{},"A ",[936,2865,2868],{"href":2866,"rel":2867},"https:\u002F\u002Fgithub.com\u002Fdocker\u002Fsandbox-kit-spec",[1931],"Docker Sandbox Kit"," is an image whose manifest carries a descriptor of what its workload may reach and hold. Kraft reads Kits written to the spec's ",[279,2871,2872],{},"v3.0.0-m.7",". Under ",[279,2875,2876],{},"kind: kit"," the Kit is the whole sandbox: ",[279,2879,2157],{},[279,2881,2882],{},"kit"," are required, and ",[279,2885,2886],{},"image",[279,2888,2042],{},[279,2890,1872],{},[279,2892,1376],{}," are refused. ",[279,2895,2882],{}," must be pinned by digest (",[279,2898,2899],{},"\u003Cname>[:\u003Ctag>]@sha256:\u003C64 hex>","); a tag alone is refused when repos.yaml loads. Like any sandbox, a chain, node or task cannot change it, and a chain's or library's policy can set it too.",[276,2902,2903,2904,2907],{},"Kraft runs exactly one Kit, a ",[279,2905,2906],{},"kind: workload",", and composes nothing:",[524,2909,2910,2920],{},[527,2911,2912],{},[530,2913,2914,2917],{},[533,2915,2916],{},"Capability",[533,2918,2919],{},"What Kraft does",[543,2921,2922,2935,2976,3008,3021],{},[530,2923,2924,2929],{},[548,2925,2926],{},[279,2927,2928],{},"network-policy@1",[548,2930,2931,2932,2934],{},"Enforced, as the ",[936,2933,1058],{"href":1057},", phase for phase. Required: a Kit without one is refused.",[530,2936,2937,2942],{},[548,2938,2939],{},[279,2940,2941],{},"credential@1",[548,2943,2944,2945,2948,2949,2951,2952,2954,2955,2957,2958,2960,2961,1417,2963,2965,2966,2968,2969,2972,2973,2975],{},"Enforced for a proxy-managed ",[279,2946,2947],{},"apiKey"," with a ",[279,2950,568],{}," and header ",[279,2953,2612],{}," rules, as a ",[936,2956,2369],{"href":1373}," scoped to its ",[279,2959,2641],{},". Its value is the daemon's variable that ",[936,2962,1231],{"href":177},[279,2964,1376],{}," binds to its ",[279,2967,2609],{},". An ",[279,2970,2971],{},"oauth"," beside the ",[279,2974,2947],{}," is recorded as ignored.",[530,2977,2978,2983],{},[548,2979,2980],{},[279,2981,2982],{},"resources@1",[548,2984,2985,2986,1054,2988,2990,2991,2534,2993,2996,2997,867,3000,3003,3004,3007],{},"Enforced: ",[279,2987,1741],{},[279,2989,1814],{}," as the ",[936,2992,1053],{"href":1052},[279,2994,2995],{},"2gib"," is ",[279,2998,2999],{},"2g",[279,3001,3002],{},"cpu: 0"," is no limit). ",[279,3005,3006],{},"gpu"," is not.",[530,3009,3010,3015],{},[548,3011,3012],{},[279,3013,3014],{},"agent-sessions@1",[548,3016,3017,3018,617],{},"Accepted and not applied: the harness file builds the command, as for ",[279,3019,3020],{},"kind: docker",[530,3022,3023,3026],{},[548,3024,3025],{},"Any other type, or a form above Kraft does not enforce",[548,3027,3028,3029,617],{},"Refused when required, naming it. Skipped when ",[279,3030,3031],{},"optional",[276,3033,2863,3034,2616,3037,3040,3041,3044,3045,3048,3049,3051],{},[279,3035,3036],{},"kind: mixin",[279,3038,3039],{},"requires",", a capability group, a ",[279,3042,3043],{},"${{ }}"," reference in a capability, an ",[279,3046,3047],{},"args"," entry exported to ",[279,3050,856],{},", and a descriptor over 512 KiB are refused too.",[276,3053,3054,3057,3058,3060,3061,3063,3064,3067,3068,3071,3072,3074],{},[1513,3055,3056],{},"What a launch runs."," The Kit, lowered to the ",[279,3059,3020],{}," sandbox it describes: the Kit's image by digest, its network lists, its credentials and its limits, and nothing else. The harness's ",[279,3062,2231],{}," hosts are not added, so a host the Kit leaves out is refused and recorded like any other. The image's ",[279,3065,3066],{},"Entrypoint"," is kept and the harness's command replaces its ",[279,3069,3070],{},"Cmd",", so a Kit whose entrypoint is the agent CLI itself does not work. The worker session records ",[279,3073,961],{}," as its backend.",[276,3076,3077,3080,3081,3084,3085,3088,3089,3092,3093,3098],{},[1513,3078,3079],{},"When it is read."," Kraft reads the descriptor with your runtime's own CLI and registry login (",[279,3082,3083],{},"docker manifest inspect","; Podman, which reads only an index that way, pulls a single-manifest Kit and reads its image), before an item's worktree is made, and caches it under ",[279,3086,3087],{},"$KRAFT_HOME\u002Frun\u002Fkit\u002F"," by its digest. A Kit that cannot be fetched or is refused stops the item for you, naming the Kit and why: see ",[936,3090,1278],{"href":3091},"\u002Fget-started\u002Ftroubleshooting#a-kit-is-refused",". Each task dispatched under it records a ",[936,3094,3095],{"href":241},[279,3096,3097],{},"sandbox_kit_resolved"," event once per Kit, with what was skipped or ignored.",[276,3100,3101,3104,3105,3107,3108,3111,3112,996],{},[1513,3102,3103],{},"Doctor."," ",[279,3106,1209],{}," fetches and lowers each Kit repository's Kit, fails its sandbox row when it cannot, and runs the sandbox, egress, proxy and credentials rows on what it lowers to. It warns about each harness host the Kit does not allow (",[279,3109,3110],{},"kit hosts",") and each credential service with no binding (",[279,3113,3114],{},"kit credentials",[1212,3116,1166],{"id":3117},"automated-review",[276,3119,3120,3122,3123,3125],{},[279,3121,1144],{}," names exactly one reviewer, one of two ways. It is the reviewer a chain's ",[279,3124,1154],{}," task waits for.",[1634,3127,3128,3133],{},[1637,3129,3130,3132],{},[279,3131,1158],{}," settles when that forge login has reviewed the merge request's current head. On GitHub, changes requested or any inline comment is actionable (one finding per comment), and anything else is clean. A dismissed review does not count. On GitLab, the bot's unresolved discussions are actionable and its approval is clean. A GitLab approval is not tied to a commit, so a bot's approval of an earlier head still reads as clean, unless the project resets approvals on push.",[1637,3134,3135,3137],{},[279,3136,1161],{}," settles when that check run or commit status on the head completes. Success is clean. Failure is actionable, with its output as the finding.",[276,3139,3140,3141,3144],{},"Unset, the repository expects no automated review: the task settles clean at once and records ",[279,3142,3143],{},"automated_review_not_configured",". A reviewer that errors stops the item for a person rather than spending a repair.",[276,3146,3147],{},"Kraft reads only the first page of 100 of each list it asks for: the pull request's reviews, a review's comments, and a GitLab merge request's discussions and commit statuses. A bot with no match on that first page reads as not having reviewed yet.",[1212,3149,3151],{"id":3150},"connecting-a-repo","Connecting a repo",[276,3153,3154,3156,3157,3159,3160,3163,3164,3167,3168,3170,3171,617],{},[279,3155,686],{}," probes a ",[279,3158,833],{}," and a test command from the repo's\nmarkers (a justfile with a ",[279,3161,3162],{},"test"," recipe proposes ",[279,3165,3166],{},"just test"," ahead of any\nmanifest) and prints the test command with the file it came from; check both\nbefore trusting them, and ",[279,3169,1209],{}," reports any connected repo still\nmissing a ",[279,3172,833],{},[1212,3174,3176],{"id":3175},"repository-steering","Repository steering",[276,3178,3179,1417,3181,3184,3185,3187,3188,3191,3192,3195,3196,3199,3200,3203],{},[279,3180,281],{},[279,3182,3183],{},"steering: [name, ...]"," names steering profiles in\n",[279,3186,1031],{},", the same ones a task's ",[279,3189,3190],{},"steering:"," selects (see the\n",[936,3193,3194],{"href":161},"library reference","). There is no other steering store. When a work item is filed,\nKraft resolves each name to its profile's ",[279,3197,3198],{},"instructions"," and freezes the text\ninto the item, so editing a profile reaches items filed afterwards and never\none already filed. Every launch gets the repository's profiles first, then\nthe task's, in the agent's system prompt under a ",[279,3201,3202],{},"## Project standards","\nheading, 8 KB at most together.",[276,3205,3206,3207,617],{},"A name the library doesn't define is refused when the repository is saved\n(Settings, Repos) and when an item is filed, and a\nlibrary save that removes a profile a repository still names is refused too.\nYou write profiles on Settings, Library, which edits ",[279,3208,1031],{},[276,3210,2863,3211,3214,3215,3217,3218,617],{},[279,3212,3213],{},"templates\u002Fsteering\u002F*.md"," directory from an older release is folded into ",[279,3216,1031],{}," as steering profiles of the same name on first start. The old directory is kept as ",[279,3219,3220],{},"templates\u002Fsteering.pre-1.0\u002F",[276,3222,3223,3224,3227,3228,3231],{},"This is not a place for target-repo files: Kraft never reads ",[279,3225,3226],{},"CLAUDE.md",",\n",[279,3229,3230],{},"AGENTS.md",", or anything else from inside the repo being worked on as a\nsource of process context.",[1212,3233,3235],{"id":3234},"in-this-section","In this section",[1634,3237,3238],{},[1637,3239,3240,3242],{},[936,3241,152],{"href":153},": a root repository with other repositories mounted as submodules.",[3244,3245,3246],"style",{},"html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":289,"searchDepth":307,"depth":307,"links":3248},[3249,3256,3257,3258,3259],{"id":1214,"depth":307,"text":939,"children":3250},[3251,3252,3253,3254,3255],{"id":1562,"depth":323,"text":1484},{"id":1696,"depth":323,"text":1697},{"id":1940,"depth":323,"text":1941},{"id":1376,"depth":323,"text":1345},{"id":2803,"depth":323,"text":2804},{"id":3117,"depth":307,"text":1166},{"id":3150,"depth":307,"text":3151},{"id":3175,"depth":307,"text":3176},{"id":3234,"depth":307,"text":3235},"Every field in repos.yaml, and how kraft repo connect fills it in.","md",null,{},{"title":10},{"title":146,"description":3260},"w4r5GJbEcyXteU8rDQcMFMnWQBdLXBBSV_SnL1-5YUk",[3268,3270],{"title":10,"path":141,"stem":142,"description":3269,"children":-1},"Where Kraft's configuration files live and which file controls what.",{"title":152,"path":153,"stem":154,"description":3271,"children":-1},"Declare a root repository with member submodules in repos.yaml, and what it means for sandboxing and publication.",1790824510710]