[{"data":1,"prerenderedAt":5025},["ShallowReactive",2],{"i-mdi:open-in-new":3,"i-mdi:github":8,"i-mdi:menu":10,"i-local:logo":12,"blog":15,"i-mdi:view-grid-outline":5021,"i-mdi:view-list-outline":5023},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":7},0,24,false,"\u003Cpath fill=\"currentColor\" d=\"M14 3v2h3.59l-9.83 9.83l1.41 1.41L19 6.41V10h2V3m-2 16H5V5h7V3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7h-2z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":9},"\u003Cpath fill=\"currentColor\" d=\"M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33s1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":11},"\u003Cpath fill=\"currentColor\" d=\"M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z\"\u002F>",{"left":4,"top":4,"width":13,"height":13,"rotate":4,"vFlip":6,"hFlip":6,"body":14},1200,"\u003Cdefs\n     id=\"defs1\" \u002F>\n  \u003Cpath\n     style=\"fill:#326ce5;fill-opacity:1;stroke:#326ce5;stroke-width:54.2178;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:65.7793;stroke-opacity:1;paint-order:stroke fill markers\"\n     id=\"path10\"\n     d=\"M 778.34299,825.61572 273.93527,960.77136 -95.316804,591.51929 39.838835,87.111573 544.24655,-48.044069 913.49863,321.20801 Z\"\n     transform=\"matrix(0.89078213,-0.23868436,0.23868436,0.89078213,126.66226,291.12302)\" \u002F>\n  \u003Cpath\n     style=\"fill:#ffa400;fill-opacity:1;stroke:#ffffff;stroke-width:171.118;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:65.7793;stroke-opacity:1;paint-order:stroke fill markers\"\n     id=\"path10-0-4\"\n     d=\"M 778.34299,825.61572 273.93527,960.77136 -95.316804,591.51929 39.838835,87.111573 544.24655,-48.044069 913.49863,321.20801 Z\"\n     transform=\"matrix(0.56447964,-0.15125188,0.15125188,0.56447964,300.05065,404.26778)\" \u002F>\n  \u003Cg\n     id=\"g24\"\n     transform=\"translate(-0.00289,-1.818185)\">\n    \u003Cpath\n       style=\"fill:#ffa400;fill-opacity:1;stroke:#ffffff;stroke-width:25;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:65.7793;stroke-opacity:1;paint-order:stroke fill markers\"\n       d=\"M 236.36364,809.09091 960.89972,390.77981\"\n       id=\"path22\" \u002F>\n    \u003Cpath\n       style=\"fill:#ffa400;fill-opacity:1;stroke:#ffffff;stroke-width:25;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:65.7793;stroke-opacity:1;paint-order:stroke fill markers\"\n       d=\"m 236.36364,389.09091 727.2785,419.89444\"\n       id=\"path23\" \u002F>\n    \u003Cpath\n       style=\"fill:#ffa400;fill-opacity:1;stroke:#ffffff;stroke-width:25;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:65.7793;stroke-opacity:1;paint-order:stroke fill markers\"\n       d=\"M 600.18182,1010.9091 V 192.72727\"\n       id=\"path24\" \u002F>\n  \u003C\u002Fg>\n  \u003Ccircle\n     style=\"fill:none;stroke:#ffffff;stroke-width:42.7667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:65.7793;paint-order:stroke fill markers\"\n     id=\"path1\"\n     cx=\"261.54996\"\n     cy=\"402.17349\"\n     r=\"19.439373\" \u002F>\n  \u003Ccircle\n     style=\"fill:none;stroke:#ffffff;stroke-width:42.7667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:65.7793;paint-order:stroke fill markers\"\n     id=\"path1-3\"\n     cx=\"257.91364\"\n     cy=\"793.18634\"\n     r=\"19.439373\" \u002F>\n  \u003Ccircle\n     style=\"fill:none;stroke:#ffffff;stroke-width:42.7667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:65.7793;paint-order:stroke fill markers\"\n     id=\"path1-5\"\n     cx=\"599.82275\"\n     cy=\"993.0954\"\n     r=\"19.439373\" \u002F>\n  \u003Ccircle\n     style=\"fill:none;stroke:#ffffff;stroke-width:42.7667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:65.7793;paint-order:stroke fill markers\"\n     id=\"path1-2\"\n     cx=\"939.64093\"\n     cy=\"794.36816\"\n     r=\"19.439373\" \u002F>\n  \u003Ccircle\n     style=\"fill:none;stroke:#ffffff;stroke-width:42.7667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:65.7793;paint-order:stroke fill markers\"\n     id=\"path1-8\"\n     cx=\"599.55005\"\n     cy=\"207.73181\"\n     r=\"19.439373\" \u002F>\n  \u003Ccircle\n     style=\"fill:none;stroke:#ffffff;stroke-width:42.7667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:65.7793;paint-order:stroke fill markers\"\n     id=\"path1-1\"\n     cx=\"939.91364\"\n     cy=\"403.18637\"\n     r=\"19.439373\" \u002F>\n  \u003Ccircle\n     style=\"fill:none;stroke:#ffffff;stroke-width:42.7667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:65.7793;paint-order:stroke fill markers\"\n     id=\"path1-1-7\"\n     cx=\"600\"\n     cy=\"600\"\n     r=\"19.439373\" \u002F>",[16,1201,1802,3690],{"id":17,"title":18,"author":19,"authorUrl":20,"body":21,"category":1187,"date":1188,"description":1189,"extension":1190,"image":209,"meta":1191,"navigation":348,"path":1192,"seo":1193,"series":1194,"stem":1195,"tags":1196,"__hash__":1200},"blog\u002Fblog\u002Fsearch-tree-orchestration.md","Pipelines are straight lines. Your agents need to think in trees.","rdmnl","https:\u002F\u002Fgithub.com\u002Frdmnl",{"type":22,"value":23,"toc":1177},"minimark",[24,44,47,50,53,56,59,64,67,153,156,160,163,185,200,203,434,437,441,444,447,627,630,636,640,643,715,729,740,744,747,810,816,827,831,836,878,1080,1083,1098,1102,1108,1114,1120,1123,1127,1138,1170,1173],[25,26,27],"blockquote",{},[28,29,30,34,35,39,40,43],"p",{},[31,32,33],"strong",{},"TL;DR"," - SwarmTeam gains a fourth orchestration mode: ",[36,37,38],"code",{},"search",". A planner agent\nexplores multiple approaches in parallel, an evaluator scores each one, and weak\nbranches get pruned. The search converges when a solution scores high enough or\nthe tree hits its budget. BFS and BeamSearch strategies, fully observable via\n",[36,41,42],{},"kubectl describe",", OTel metrics, and audit trail.",[45,46],"hr",{},[28,48,49],{},"\"Find the root cause of this production outage.\"",[28,51,52],{},"Hand that to a pipeline and here's what happens: step 1 checks logs, step 2 reads metrics, step 3 writes a report. One linear path. If the first hypothesis is wrong, the pipeline doesn't backtrack. It writes a confident report about the wrong thing.",[28,54,55],{},"Hand it to a human SRE and they do something completely different. They generate three hypotheses. Test the most likely one first. When it doesn't match the evidence, they drop it and go deeper on the second. They prune dead ends. They converge on the answer.",[28,57,58],{},"That's a tree search. And until now, you couldn't express it in Kubernetes.",[60,61,63],"h2",{"id":62},"the-problem-with-just-use-a-pipeline","The problem with \"just use a pipeline\"",[28,65,66],{},"kubeswarm already has three orchestration modes for SwarmTeam: pipeline (DAG), routed (LLM dispatch), and dynamic (agents self-organize). They cover most patterns. But they share a limitation.",[68,69,70,89],"table",{},[71,72,73],"thead",{},[74,75,76,80,83,86],"tr",{},[77,78,79],"th",{},"Mode",[77,81,82],{},"Execution path",[77,84,85],{},"Backtracking",[77,87,88],{},"Scoring",[90,91,92,106,118,131],"tbody",{},[74,93,94,98,101,104],{},[95,96,97],"td",{},"Pipeline",[95,99,100],{},"Fixed at YAML time",[95,102,103],{},"No",[95,105,103],{},[74,107,108,111,114,116],{},[95,109,110],{},"Routed",[95,112,113],{},"Single dispatch decision",[95,115,103],{},[95,117,103],{},[74,119,120,123,126,129],{},[95,121,122],{},"Dynamic",[95,124,125],{},"Unstructured delegation",[95,127,128],{},"Ad-hoc",[95,130,103],{},[74,132,133,138,143,148],{},[95,134,135],{},[31,136,137],{},"Search",[95,139,140],{},[31,141,142],{},"Expands at runtime",[95,144,145],{},[31,146,147],{},"Yes (pruning)",[95,149,150],{},[31,151,152],{},"Yes (evaluator)",[28,154,155],{},"Pipelines are predetermined. Routed mode makes one decision. Dynamic mode has no structure to guide exploration. None of them lets you say \"explore three approaches, score each one, drop the worst, go deeper on the best.\"",[60,157,159],{"id":158},"search-mode-structured-exploration","Search mode: structured exploration",[28,161,162],{},"Search adds a fourth mode to SwarmTeam. You define three roles:",[164,165,166,173,179],"ul",{},[167,168,169,172],"li",{},[31,170,171],{},"Planner"," - decides what to explore next, what to prune, when to stop",[167,174,175,178],{},[31,176,177],{},"Executor"," - does the actual work on each branch",[167,180,181,184],{},[31,182,183],{},"Evaluator"," (optional) - scores executor output on a 0-1000 scale",[28,186,187,188,191,192,195,196,199],{},"The planner sees the current tree state and outputs structured actions: ",[36,189,190],{},"expand"," (create branches), ",[36,193,194],{},"prune"," (kill dead ends), ",[36,197,198],{},"converge"," (declare a winner). The operator applies these atomically, dispatches executors in parallel, collects scores, and loops back to the planner.",[28,201,202],{},"Here's the simplest version - a brainstorming search where the planner self-scores:",[204,205,210],"pre",{"className":206,"code":207,"language":208,"meta":209,"style":209},"language-yaml shiki shiki-themes github-dark","# brainstorm-team.yaml\nspec:\n  roles:\n    - name: planner\n      model: claude-sonnet-4-20250514\n      prompt:\n        inline: |\n          You receive the current search tree as JSON. Decide what to explore next.\n          Respond with a JSON array of actions:\n          [{\"action\": \"expand\", \"parentNode\": 0, \"task\": \"...\", \"scoreMillis\": 700}]\n    - name: worker\n      model: claude-sonnet-4-20250514\n      prompt:\n        inline: \"Execute the given task thoroughly.\"\n\n  search:\n    strategy: BFS\n    plannerRole: planner\n    executorRole: worker\n    initialPrompt: \"{{ .input.problem }}\"\n    maxDepth: 3\n    maxNodes: 15\n    minScorePercent: 85\n","yaml","",[36,211,212,221,232,240,256,267,275,287,293,299,305,317,326,333,343,350,358,369,379,389,400,412,423],{"__ignoreMap":209},[213,214,217],"span",{"class":215,"line":216},"line",1,[213,218,220],{"class":219},"sAwPA","# brainstorm-team.yaml\n",[213,222,224,228],{"class":215,"line":223},2,[213,225,227],{"class":226},"s4JwU","spec",[213,229,231],{"class":230},"s95oV",":\n",[213,233,235,238],{"class":215,"line":234},3,[213,236,237],{"class":226},"  roles",[213,239,231],{"class":230},[213,241,243,246,249,252],{"class":215,"line":242},4,[213,244,245],{"class":230},"    - ",[213,247,248],{"class":226},"name",[213,250,251],{"class":230},": ",[213,253,255],{"class":254},"sU2Wk","planner\n",[213,257,259,262,264],{"class":215,"line":258},5,[213,260,261],{"class":226},"      model",[213,263,251],{"class":230},[213,265,266],{"class":254},"claude-sonnet-4-20250514\n",[213,268,270,273],{"class":215,"line":269},6,[213,271,272],{"class":226},"      prompt",[213,274,231],{"class":230},[213,276,278,281,283],{"class":215,"line":277},7,[213,279,280],{"class":226},"        inline",[213,282,251],{"class":230},[213,284,286],{"class":285},"snl16","|\n",[213,288,290],{"class":215,"line":289},8,[213,291,292],{"class":254},"          You receive the current search tree as JSON. Decide what to explore next.\n",[213,294,296],{"class":215,"line":295},9,[213,297,298],{"class":254},"          Respond with a JSON array of actions:\n",[213,300,302],{"class":215,"line":301},10,[213,303,304],{"class":254},"          [{\"action\": \"expand\", \"parentNode\": 0, \"task\": \"...\", \"scoreMillis\": 700}]\n",[213,306,308,310,312,314],{"class":215,"line":307},11,[213,309,245],{"class":230},[213,311,248],{"class":226},[213,313,251],{"class":230},[213,315,316],{"class":254},"worker\n",[213,318,320,322,324],{"class":215,"line":319},12,[213,321,261],{"class":226},[213,323,251],{"class":230},[213,325,266],{"class":254},[213,327,329,331],{"class":215,"line":328},13,[213,330,272],{"class":226},[213,332,231],{"class":230},[213,334,336,338,340],{"class":215,"line":335},14,[213,337,280],{"class":226},[213,339,251],{"class":230},[213,341,342],{"class":254},"\"Execute the given task thoroughly.\"\n",[213,344,346],{"class":215,"line":345},15,[213,347,349],{"emptyLinePlaceholder":348},true,"\n",[213,351,353,356],{"class":215,"line":352},16,[213,354,355],{"class":226},"  search",[213,357,231],{"class":230},[213,359,361,364,366],{"class":215,"line":360},17,[213,362,363],{"class":226},"    strategy",[213,365,251],{"class":230},[213,367,368],{"class":254},"BFS\n",[213,370,372,375,377],{"class":215,"line":371},18,[213,373,374],{"class":226},"    plannerRole",[213,376,251],{"class":230},[213,378,255],{"class":254},[213,380,382,385,387],{"class":215,"line":381},19,[213,383,384],{"class":226},"    executorRole",[213,386,251],{"class":230},[213,388,316],{"class":254},[213,390,392,395,397],{"class":215,"line":391},20,[213,393,394],{"class":226},"    initialPrompt",[213,396,251],{"class":230},[213,398,399],{"class":254},"\"{{ .input.problem }}\"\n",[213,401,403,406,408],{"class":215,"line":402},21,[213,404,405],{"class":226},"    maxDepth",[213,407,251],{"class":230},[213,409,411],{"class":410},"sDLfK","3\n",[213,413,415,418,420],{"class":215,"line":414},22,[213,416,417],{"class":226},"    maxNodes",[213,419,251],{"class":230},[213,421,422],{"class":410},"15\n",[213,424,426,429,431],{"class":215,"line":425},23,[213,427,428],{"class":226},"    minScorePercent",[213,430,251],{"class":230},[213,432,433],{"class":410},"85\n",[28,435,436],{},"Two roles, twelve lines of search config. The planner explores breadth-first, scoring its own branches. When something scores above 85%, the search stops.",[60,438,440],{"id":439},"beamsearch-keep-the-best-prune-the-rest","BeamSearch: keep the best, prune the rest",[28,442,443],{},"BFS explores everything at each level. For bigger problems, that's expensive. BeamSearch keeps only the top K branches per depth level and prunes the rest.",[28,445,446],{},"This is where the evaluator role earns its keep. A separate model scores each branch objectively, so beam pruning decisions are based on independent assessment rather than the planner grading its own work.",[204,448,450],{"className":206,"code":449,"language":208,"meta":209,"style":209},"# root-cause-analyzer.yaml\nspec:\n  roles:\n    - name: investigator\n      model: claude-sonnet-4-20250514\n    - name: tester\n      model: claude-sonnet-4-20250514\n    - name: judge\n      model: claude-haiku-4-5-20251001   # cheap model for scoring\n\n  search:\n    strategy: BeamSearch\n    plannerRole: investigator\n    executorRole: tester\n    evaluatorRole: judge\n    initialPrompt: \"{{ .input.incident }}\"\n    beamWidth: 3\n    minScorePercent: 85\n    maxDepth: 5\n    maxNodes: 30\n    maxParallel: 3\n",[36,451,452,457,463,469,480,488,499,507,518,530,534,540,549,557,565,574,583,592,600,609,618],{"__ignoreMap":209},[213,453,454],{"class":215,"line":216},[213,455,456],{"class":219},"# root-cause-analyzer.yaml\n",[213,458,459,461],{"class":215,"line":223},[213,460,227],{"class":226},[213,462,231],{"class":230},[213,464,465,467],{"class":215,"line":234},[213,466,237],{"class":226},[213,468,231],{"class":230},[213,470,471,473,475,477],{"class":215,"line":242},[213,472,245],{"class":230},[213,474,248],{"class":226},[213,476,251],{"class":230},[213,478,479],{"class":254},"investigator\n",[213,481,482,484,486],{"class":215,"line":258},[213,483,261],{"class":226},[213,485,251],{"class":230},[213,487,266],{"class":254},[213,489,490,492,494,496],{"class":215,"line":269},[213,491,245],{"class":230},[213,493,248],{"class":226},[213,495,251],{"class":230},[213,497,498],{"class":254},"tester\n",[213,500,501,503,505],{"class":215,"line":277},[213,502,261],{"class":226},[213,504,251],{"class":230},[213,506,266],{"class":254},[213,508,509,511,513,515],{"class":215,"line":289},[213,510,245],{"class":230},[213,512,248],{"class":226},[213,514,251],{"class":230},[213,516,517],{"class":254},"judge\n",[213,519,520,522,524,527],{"class":215,"line":295},[213,521,261],{"class":226},[213,523,251],{"class":230},[213,525,526],{"class":254},"claude-haiku-4-5-20251001",[213,528,529],{"class":219},"   # cheap model for scoring\n",[213,531,532],{"class":215,"line":301},[213,533,349],{"emptyLinePlaceholder":348},[213,535,536,538],{"class":215,"line":307},[213,537,355],{"class":226},[213,539,231],{"class":230},[213,541,542,544,546],{"class":215,"line":319},[213,543,363],{"class":226},[213,545,251],{"class":230},[213,547,548],{"class":254},"BeamSearch\n",[213,550,551,553,555],{"class":215,"line":328},[213,552,374],{"class":226},[213,554,251],{"class":230},[213,556,479],{"class":254},[213,558,559,561,563],{"class":215,"line":335},[213,560,384],{"class":226},[213,562,251],{"class":230},[213,564,498],{"class":254},[213,566,567,570,572],{"class":215,"line":345},[213,568,569],{"class":226},"    evaluatorRole",[213,571,251],{"class":230},[213,573,517],{"class":254},[213,575,576,578,580],{"class":215,"line":352},[213,577,394],{"class":226},[213,579,251],{"class":230},[213,581,582],{"class":254},"\"{{ .input.incident }}\"\n",[213,584,585,588,590],{"class":215,"line":360},[213,586,587],{"class":226},"    beamWidth",[213,589,251],{"class":230},[213,591,411],{"class":410},[213,593,594,596,598],{"class":215,"line":371},[213,595,428],{"class":226},[213,597,251],{"class":230},[213,599,433],{"class":410},[213,601,602,604,606],{"class":215,"line":381},[213,603,405],{"class":226},[213,605,251],{"class":230},[213,607,608],{"class":410},"5\n",[213,610,611,613,615],{"class":215,"line":391},[213,612,417],{"class":226},[213,614,251],{"class":230},[213,616,617],{"class":410},"30\n",[213,619,620,623,625],{"class":215,"line":402},[213,621,622],{"class":226},"    maxParallel",[213,624,251],{"class":230},[213,626,411],{"class":410},[28,628,629],{},"The investigator generates hypotheses. The tester checks each one against evidence. The judge scores how well the evidence supports the hypothesis. After each depth level, only the top 3 hypotheses survive. The rest get pruned.",[28,631,632,635],{},[31,633,634],{},"Cost optimization is built in."," The investigator (expensive reasoning model) runs once per iteration. The judge (cheap fast model) runs once per node. Most of the compute goes to testers running in parallel. You can easily spend 10x less on scoring than on execution.",[60,637,639],{"id":638},"the-evaluators-json-contract","The evaluator's JSON contract",[28,641,642],{},"The evaluator returns structured scores. No prompt engineering gymnastics - just a JSON object:",[204,644,648],{"className":645,"code":646,"language":647,"meta":209,"style":209},"language-json shiki shiki-themes github-dark","{\n  \"scoreMillis\": 720,\n  \"reasoning\": \"Correct approach but missing edge case handling\",\n  \"shouldPrune\": false,\n  \"metadata\": {\"evidence_strength\": \"moderate\"}\n}\n","json",[36,649,650,655,668,680,692,711],{"__ignoreMap":209},[213,651,652],{"class":215,"line":216},[213,653,654],{"class":230},"{\n",[213,656,657,660,662,665],{"class":215,"line":223},[213,658,659],{"class":410},"  \"scoreMillis\"",[213,661,251],{"class":230},[213,663,664],{"class":410},"720",[213,666,667],{"class":230},",\n",[213,669,670,673,675,678],{"class":215,"line":234},[213,671,672],{"class":410},"  \"reasoning\"",[213,674,251],{"class":230},[213,676,677],{"class":254},"\"Correct approach but missing edge case handling\"",[213,679,667],{"class":230},[213,681,682,685,687,690],{"class":215,"line":242},[213,683,684],{"class":410},"  \"shouldPrune\"",[213,686,251],{"class":230},[213,688,689],{"class":410},"false",[213,691,667],{"class":230},[213,693,694,697,700,703,705,708],{"class":215,"line":258},[213,695,696],{"class":410},"  \"metadata\"",[213,698,699],{"class":230},": {",[213,701,702],{"class":410},"\"evidence_strength\"",[213,704,251],{"class":230},[213,706,707],{"class":254},"\"moderate\"",[213,709,710],{"class":230},"}\n",[213,712,713],{"class":215,"line":269},[213,714,710],{"class":230},[28,716,717,720,721,724,725,728],{},[36,718,719],{},"scoreMillis"," is 0-1000 (milli-units, not milliseconds). ",[36,722,723],{},"shouldPrune"," lets the evaluator flag dead ends without waiting for the planner's next iteration. ",[36,726,727],{},"metadata"," carries domain-specific signals the planner can reason about.",[28,730,731,732,735,736,739],{},"If the evaluator returns garbage JSON, the operator retries up to ",[36,733,734],{},"maxEvaluatorRetries"," times, then marks the node ",[36,737,738],{},"EvalFailed",". The planner sees the failure and decides whether to retry or move on. No silent data loss.",[60,741,743],{"id":742},"five-ways-a-search-terminates","Five ways a search terminates",[28,745,746],{},"Every search is bounded. There's no way to create a runaway search.",[204,748,750],{"className":206,"code":749,"language":208,"meta":209,"style":209},"search:\n  minScorePercent: 85     # a node scores above 85% -> converge\n  maxDepth: 5             # tree gets 5 levels deep -> stop\n  maxNodes: 30            # 30 nodes created -> stop\n  maxIterations: 10       # planner invoked 10 times -> stop\n",[36,751,752,758,771,784,797],{"__ignoreMap":209},[213,753,754,756],{"class":215,"line":216},[213,755,38],{"class":226},[213,757,231],{"class":230},[213,759,760,763,765,768],{"class":215,"line":223},[213,761,762],{"class":226},"  minScorePercent",[213,764,251],{"class":230},[213,766,767],{"class":410},"85",[213,769,770],{"class":219},"     # a node scores above 85% -> converge\n",[213,772,773,776,778,781],{"class":215,"line":234},[213,774,775],{"class":226},"  maxDepth",[213,777,251],{"class":230},[213,779,780],{"class":410},"5",[213,782,783],{"class":219},"             # tree gets 5 levels deep -> stop\n",[213,785,786,789,791,794],{"class":215,"line":242},[213,787,788],{"class":226},"  maxNodes",[213,790,251],{"class":230},[213,792,793],{"class":410},"30",[213,795,796],{"class":219},"            # 30 nodes created -> stop\n",[213,798,799,802,804,807],{"class":215,"line":258},[213,800,801],{"class":226},"  maxIterations",[213,803,251],{"class":230},[213,805,806],{"class":410},"10",[213,808,809],{"class":219},"       # planner invoked 10 times -> stop\n",[28,811,812,813,815],{},"Plus: the planner can explicitly ",[36,814,198],{}," at any time, and SwarmBudget hard-stops when the token budget runs out. First condition hit wins.",[28,817,818,819,822,823,826],{},"When the search terminates, the highest-scoring node's output becomes the SwarmRun output. If nothing scored above ",[36,820,821],{},"minScorePercent",", the run fails with ",[36,824,825],{},"SearchExhausted",". No ambiguity about what happened or why.",[60,828,830],{"id":829},"the-tree-is-in-your-status","The tree is in your status",[28,832,833,835],{},[36,834,42],{}," shows the full tree. Every node, every score, every pruning decision.",[204,837,841],{"className":838,"code":839,"language":840,"meta":209,"style":209},"language-bash shiki shiki-themes github-dark","kubectl get swrun rca-demo -o jsonpath='{.status.searchTree.nodes}' | \\\n  jq '.[] | {id, depth, phase, scoreMillis, task}'\n","bash",[36,842,843,870],{"__ignoreMap":209},[213,844,845,849,852,855,858,861,864,867],{"class":215,"line":216},[213,846,848],{"class":847},"svObZ","kubectl",[213,850,851],{"class":254}," get",[213,853,854],{"class":254}," swrun",[213,856,857],{"class":254}," rca-demo",[213,859,860],{"class":410}," -o",[213,862,863],{"class":254}," jsonpath='{.status.searchTree.nodes}'",[213,865,866],{"class":285}," |",[213,868,869],{"class":410}," \\\n",[213,871,872,875],{"class":215,"line":223},[213,873,874],{"class":847},"  jq",[213,876,877],{"class":254}," '.[] | {id, depth, phase, scoreMillis, task}'\n",[204,879,881],{"className":645,"code":880,"language":647,"meta":209,"style":209},"{\"id\": 0, \"depth\": 0, \"phase\": \"Scored\", \"scoreMillis\": null, \"task\": \"Investigate latency spike\"}\n{\"id\": 1, \"depth\": 1, \"phase\": \"Scored\", \"scoreMillis\": 720, \"task\": \"Check database connections\"}\n{\"id\": 2, \"depth\": 1, \"phase\": \"Pruned\", \"scoreMillis\": 310, \"task\": \"Check network partitions\"}\n{\"id\": 3, \"depth\": 2, \"phase\": \"Solution\", \"scoreMillis\": 920, \"task\": \"Profile connection pool leaks\"}\n",[36,882,883,938,984,1032],{"__ignoreMap":209},[213,884,885,888,891,893,896,899,902,904,906,908,911,913,916,918,921,923,926,928,931,933,936],{"class":215,"line":216},[213,886,887],{"class":230},"{",[213,889,890],{"class":410},"\"id\"",[213,892,251],{"class":230},[213,894,895],{"class":410},"0",[213,897,898],{"class":230},", ",[213,900,901],{"class":410},"\"depth\"",[213,903,251],{"class":230},[213,905,895],{"class":410},[213,907,898],{"class":230},[213,909,910],{"class":410},"\"phase\"",[213,912,251],{"class":230},[213,914,915],{"class":254},"\"Scored\"",[213,917,898],{"class":230},[213,919,920],{"class":410},"\"scoreMillis\"",[213,922,251],{"class":230},[213,924,925],{"class":410},"null",[213,927,898],{"class":230},[213,929,930],{"class":410},"\"task\"",[213,932,251],{"class":230},[213,934,935],{"class":254},"\"Investigate latency spike\"",[213,937,710],{"class":230},[213,939,940,942,944,946,949,951,953,955,957,959,961,963,965,967,969,971,973,975,977,979,982],{"class":215,"line":223},[213,941,887],{"class":230},[213,943,890],{"class":410},[213,945,251],{"class":230},[213,947,948],{"class":410},"1",[213,950,898],{"class":230},[213,952,901],{"class":410},[213,954,251],{"class":230},[213,956,948],{"class":410},[213,958,898],{"class":230},[213,960,910],{"class":410},[213,962,251],{"class":230},[213,964,915],{"class":254},[213,966,898],{"class":230},[213,968,920],{"class":410},[213,970,251],{"class":230},[213,972,664],{"class":410},[213,974,898],{"class":230},[213,976,930],{"class":410},[213,978,251],{"class":230},[213,980,981],{"class":254},"\"Check database connections\"",[213,983,710],{"class":230},[213,985,986,988,990,992,995,997,999,1001,1003,1005,1007,1009,1012,1014,1016,1018,1021,1023,1025,1027,1030],{"class":215,"line":234},[213,987,887],{"class":230},[213,989,890],{"class":410},[213,991,251],{"class":230},[213,993,994],{"class":410},"2",[213,996,898],{"class":230},[213,998,901],{"class":410},[213,1000,251],{"class":230},[213,1002,948],{"class":410},[213,1004,898],{"class":230},[213,1006,910],{"class":410},[213,1008,251],{"class":230},[213,1010,1011],{"class":254},"\"Pruned\"",[213,1013,898],{"class":230},[213,1015,920],{"class":410},[213,1017,251],{"class":230},[213,1019,1020],{"class":410},"310",[213,1022,898],{"class":230},[213,1024,930],{"class":410},[213,1026,251],{"class":230},[213,1028,1029],{"class":254},"\"Check network partitions\"",[213,1031,710],{"class":230},[213,1033,1034,1036,1038,1040,1043,1045,1047,1049,1051,1053,1055,1057,1060,1062,1064,1066,1069,1071,1073,1075,1078],{"class":215,"line":242},[213,1035,887],{"class":230},[213,1037,890],{"class":410},[213,1039,251],{"class":230},[213,1041,1042],{"class":410},"3",[213,1044,898],{"class":230},[213,1046,901],{"class":410},[213,1048,251],{"class":230},[213,1050,994],{"class":410},[213,1052,898],{"class":230},[213,1054,910],{"class":410},[213,1056,251],{"class":230},[213,1058,1059],{"class":254},"\"Solution\"",[213,1061,898],{"class":230},[213,1063,920],{"class":410},[213,1065,251],{"class":230},[213,1067,1068],{"class":410},"920",[213,1070,898],{"class":230},[213,1072,930],{"class":410},[213,1074,251],{"class":230},[213,1076,1077],{"class":254},"\"Profile connection pool leaks\"",[213,1079,710],{"class":230},[28,1081,1082],{},"Node 2 was pruned (network theory disproved). Node 3 scored 920 and was selected as the solution. The planner explored, the evaluator scored, the operator pruned. All of it auditable.",[28,1084,1085,1086,1089,1090,1093,1094,1097],{},"OTel metrics track the search in real time: ",[36,1087,1088],{},"kubeswarm.search.best_score"," shows convergence progress, ",[36,1091,1092],{},"kubeswarm.search.nodes.pruned"," counts dead ends, and ",[36,1095,1096],{},"kubeswarm.search.stagnation_iterations"," warns you when the search is stuck.",[60,1099,1101],{"id":1100},"when-to-use-search-vs-pipeline","When to use search vs pipeline",[28,1103,1104,1107],{},[31,1105,1106],{},"Use search when"," the problem has multiple valid approaches and you need to systematically find the best one. Root cause analysis, code generation with test validation, adversarial testing, prompt optimization.",[28,1109,1110,1113],{},[31,1111,1112],{},"Use pipeline when"," the steps are predetermined. Research -> fact-check -> write. ETL. Anything where the path is known upfront.",[28,1115,1116,1119],{},[31,1117,1118],{},"Use dynamic when"," agents should self-organize without scoring. Collaborative brainstorming where there's no \"best answer\" - just emergent output.",[28,1121,1122],{},"Search mode is not a replacement for pipelines. It's for the class of problems where the first answer is probably wrong and the value is in the exploration.",[60,1124,1126],{"id":1125},"try-it","Try it",[28,1128,1129,1130,1137],{},"The ",[1131,1132,1136],"a",{"href":1133,"rel":1134},"https:\u002F\u002Fgithub.com\u002Fkubeswarm\u002Fkubeswarm-cookbook",[1135],"nofollow","cookbook has two examples",": a minimal BFS brainstorm (recipe 18) and a full BeamSearch root cause analyzer (recipe 19). Both work with Ollama or any OpenAI-compatible API.",[204,1139,1141],{"className":838,"code":1140,"language":840,"meta":209,"style":209},"kubectl apply -f 18-search-brainstorm\u002Fteam.yaml\nkubectl get swrun brainstorm-demo -w\n",[36,1142,1143,1156],{"__ignoreMap":209},[213,1144,1145,1147,1150,1153],{"class":215,"line":216},[213,1146,848],{"class":847},[213,1148,1149],{"class":254}," apply",[213,1151,1152],{"class":410}," -f",[213,1154,1155],{"class":254}," 18-search-brainstorm\u002Fteam.yaml\n",[213,1157,1158,1160,1162,1164,1167],{"class":215,"line":223},[213,1159,848],{"class":847},[213,1161,851],{"class":254},[213,1163,854],{"class":254},[213,1165,1166],{"class":254}," brainstorm-demo",[213,1168,1169],{"class":410}," -w\n",[28,1171,1172],{},"Watch the tree grow. Watch branches get pruned. Watch the search converge on an answer that a pipeline would never have found.",[1174,1175,1176],"style",{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}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 .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":209,"searchDepth":223,"depth":223,"links":1178},[1179,1180,1181,1182,1183,1184,1185,1186],{"id":62,"depth":223,"text":63},{"id":158,"depth":223,"text":159},{"id":439,"depth":223,"text":440},{"id":638,"depth":223,"text":639},{"id":742,"depth":223,"text":743},{"id":829,"depth":223,"text":830},{"id":1100,"depth":223,"text":1101},{"id":1125,"depth":223,"text":1126},"deep-dive","2026-05-19","kubeswarm's search mode lets agent teams explore, score, prune, and converge - turning multi-hypothesis problems into structured tree searches on Kubernetes.","md",{},"\u002Fblog\u002Fsearch-tree-orchestration",{"title":18,"description":1189},null,"blog\u002Fsearch-tree-orchestration",[1197,1198,1187,1199],"orchestration","agents","architecture","qmDFT5QcP0t1BjqM4_LYS6GYFO9x2mcOr9LW_oZrlRc",{"id":1202,"title":1203,"author":19,"authorUrl":20,"body":1204,"category":1194,"date":1792,"description":1793,"extension":1190,"image":1194,"meta":1794,"navigation":348,"path":1795,"seo":1796,"series":1194,"stem":1797,"tags":1798,"__hash__":1801},"blog\u002Fblog\u002Ftool-result-caching.md","Your agent just called the same tool 20 times",{"type":22,"value":1205,"toc":1782},[1206,1210,1213,1224,1228,1235,1238,1241,1245,1332,1338,1344,1348,1351,1416,1423,1487,1490,1494,1509,1512,1519,1523,1526,1561,1565,1568,1619,1622,1625,1753,1756,1760,1763,1766,1770,1773,1779],[1207,1208,1203],"h1",{"id":1209},"your-agent-just-called-the-same-tool-20-times",[28,1211,1212],{},"Watch an agent work through a complex task and you'll see it. The same database lookup, three times in a row. The same search query, once per reasoning iteration. The same API call, identical arguments, identical results - billed fresh every single time.",[28,1214,1215,1216,1219,1220,1223],{},"This isn't a bug. It's how reasoning loops work. The model doesn't remember that it already called ",[36,1217,1218],{},"get_project"," with ",[36,1221,1222],{},"{\"key\": \"alpha\"}"," two turns ago. So it calls it again. And again. And your external API meter keeps ticking.",[60,1225,1227],{"id":1226},"the-math-is-brutal","The math is brutal",[28,1229,1230,1231,1234],{},"A reasoning agent working through a multi-step problem makes 20-50 tool calls per task. In our benchmarks, ",[31,1232,1233],{},"80% of those calls are duplicates"," - same tool, same arguments, same result. That's 16 out of 20 calls that accomplish nothing except burning latency and API quota.",[28,1236,1237],{},"Each call hits your MCP server, which hits your database or external API, which takes 50-200ms of wall time. Multiply by the number of agents, the number of tasks per hour, and you're looking at thousands of wasted calls per day.",[28,1239,1240],{},"The worst part: the model gets identical text back every time. It doesn't know or care whether the result came from a fresh API call or from memory.",[60,1242,1244],{"id":1243},"the-fix-three-lines-of-yaml","The fix: three lines of YAML",[204,1246,1248],{"className":206,"code":1247,"language":208,"meta":209,"style":209},"# researcher-agent.yaml\ntools:\n  mcp:\n    - name: search\n      url: http:\u002F\u002Fsearch-mcp.tools.svc:8080\n      cache:\n        enabled: true\n        ttlSeconds: 600\n        excludeTools:\n          - create_bookmark\n",[36,1249,1250,1255,1262,1269,1280,1290,1297,1307,1317,1324],{"__ignoreMap":209},[213,1251,1252],{"class":215,"line":216},[213,1253,1254],{"class":219},"# researcher-agent.yaml\n",[213,1256,1257,1260],{"class":215,"line":223},[213,1258,1259],{"class":226},"tools",[213,1261,231],{"class":230},[213,1263,1264,1267],{"class":215,"line":234},[213,1265,1266],{"class":226},"  mcp",[213,1268,231],{"class":230},[213,1270,1271,1273,1275,1277],{"class":215,"line":242},[213,1272,245],{"class":230},[213,1274,248],{"class":226},[213,1276,251],{"class":230},[213,1278,1279],{"class":254},"search\n",[213,1281,1282,1285,1287],{"class":215,"line":258},[213,1283,1284],{"class":226},"      url",[213,1286,251],{"class":230},[213,1288,1289],{"class":254},"http:\u002F\u002Fsearch-mcp.tools.svc:8080\n",[213,1291,1292,1295],{"class":215,"line":269},[213,1293,1294],{"class":226},"      cache",[213,1296,231],{"class":230},[213,1298,1299,1302,1304],{"class":215,"line":277},[213,1300,1301],{"class":226},"        enabled",[213,1303,251],{"class":230},[213,1305,1306],{"class":410},"true\n",[213,1308,1309,1312,1314],{"class":215,"line":289},[213,1310,1311],{"class":226},"        ttlSeconds",[213,1313,251],{"class":230},[213,1315,1316],{"class":410},"600\n",[213,1318,1319,1322],{"class":215,"line":295},[213,1320,1321],{"class":226},"        excludeTools",[213,1323,231],{"class":230},[213,1325,1326,1329],{"class":215,"line":301},[213,1327,1328],{"class":230},"          - ",[213,1330,1331],{"class":254},"create_bookmark\n",[28,1333,1334,1335,1337],{},"That's it. Every tool on the ",[36,1336,38],{}," MCP server now caches results for 10 minutes. Same arguments, same tool name = cache hit. The agent gets the exact same response in nanoseconds instead of milliseconds. The MCP server never sees the duplicate call.",[28,1339,1340,1343],{},[36,1341,1342],{},"excludeTools"," is the safety valve. Tools that create, update, or delete - anything non-idempotent - go on this list and always hit the real server.",[60,1345,1347],{"id":1346},"what-actually-happens","What actually happens",[28,1349,1350],{},"Here's a real benchmark. 20 tool calls, 4 unique argument combinations, 50ms simulated MCP latency:",[68,1352,1353,1366],{},[71,1354,1355],{},[74,1356,1357,1360,1363],{},[77,1358,1359],{},"Metric",[77,1361,1362],{},"Without cache",[77,1364,1365],{},"With cache",[90,1367,1368,1379,1390,1401],{},[74,1369,1370,1373,1376],{},[95,1371,1372],{},"MCP server hits",[95,1374,1375],{},"20",[95,1377,1378],{},"4",[74,1380,1381,1384,1387],{},[95,1382,1383],{},"Total latency",[95,1385,1386],{},"1,029ms",[95,1388,1389],{},"205ms",[74,1391,1392,1395,1398],{},[95,1393,1394],{},"Avg per call",[95,1396,1397],{},"51ms",[95,1399,1400],{},"10ms",[74,1402,1403,1408,1411],{},[95,1404,1405],{},[31,1406,1407],{},"Savings",[95,1409,1410],{},"-",[95,1412,1413],{},[31,1414,1415],{},"80% fewer calls, 80% faster",[28,1417,1418,1419,1422],{},"On a cache hit, the response returns in ",[31,1420,1421],{},"42 nanoseconds",". Not milliseconds. Nanoseconds. The model sees identical output:",[204,1424,1426],{"className":645,"code":1425,"language":647,"meta":209,"style":209},"{\n  \"key\": \"project-alpha\",\n  \"value\": { \"budget\": 50000, \"status\": \"active\" },\n  \"created_at\": \"2026-04-27T10:27:55Z\"\n}\n",[36,1427,1428,1432,1444,1473,1483],{"__ignoreMap":209},[213,1429,1430],{"class":215,"line":216},[213,1431,654],{"class":230},[213,1433,1434,1437,1439,1442],{"class":215,"line":223},[213,1435,1436],{"class":410},"  \"key\"",[213,1438,251],{"class":230},[213,1440,1441],{"class":254},"\"project-alpha\"",[213,1443,667],{"class":230},[213,1445,1446,1449,1452,1455,1457,1460,1462,1465,1467,1470],{"class":215,"line":234},[213,1447,1448],{"class":410},"  \"value\"",[213,1450,1451],{"class":230},": { ",[213,1453,1454],{"class":410},"\"budget\"",[213,1456,251],{"class":230},[213,1458,1459],{"class":410},"50000",[213,1461,898],{"class":230},[213,1463,1464],{"class":410},"\"status\"",[213,1466,251],{"class":230},[213,1468,1469],{"class":254},"\"active\"",[213,1471,1472],{"class":230}," },\n",[213,1474,1475,1478,1480],{"class":215,"line":242},[213,1476,1477],{"class":410},"  \"created_at\"",[213,1479,251],{"class":230},[213,1481,1482],{"class":254},"\"2026-04-27T10:27:55Z\"\n",[213,1484,1485],{"class":215,"line":258},[213,1486,710],{"class":230},[28,1488,1489],{},"Same string, whether it came from the MCP server or from memory. The LLM cannot tell the difference.",[60,1491,1493],{"id":1492},"how-it-works-under-the-hood","How it works under the hood",[28,1495,1496,1497,1500,1501,1504,1505,1508],{},"The cache key is ",[36,1498,1499],{},"sha256(tool_name + canonicalized_json_args)",". Arguments are re-serialized before hashing, so ",[36,1502,1503],{},"{\"a\":1, \"b\":2}"," and ",[36,1506,1507],{},"{\"b\":2,\"a\":1}"," hit the same cache entry. Whitespace differences don't matter.",[28,1510,1511],{},"The cache lives in-memory inside the agent pod. No Redis, no external dependencies, no infrastructure to deploy. It works with any LLM provider - Anthropic, OpenAI, Ollama, vLLM, whatever you run. The cache sits between the runner and the MCP dispatch, completely provider-agnostic.",[28,1513,1514,1515,1518],{},"Cache entries expire after ",[36,1516,1517],{},"ttlSeconds",". Default is 300 (5 minutes) - long enough to cover a reasoning loop, short enough that stale data isn't a real risk.",[60,1520,1522],{"id":1521},"what-doesnt-get-cached","What doesn't get cached",[28,1524,1525],{},"Three things bypass the cache unconditionally:",[1527,1528,1529,1545,1551],"ol",{},[167,1530,1531,1534,1535,898,1538,898,1541,1544],{},[31,1532,1533],{},"Tools on the exclude list."," ",[36,1536,1537],{},"store_result",[36,1539,1540],{},"send_email",[36,1542,1543],{},"create_ticket"," - anything that changes state.",[167,1546,1547,1550],{},[31,1548,1549],{},"Failed calls."," If the MCP server returns an error, the error is not cached. The next call retries fresh.",[167,1552,1553,1560],{},[31,1554,1555,1556,1559],{},"Servers without ",[36,1557,1558],{},"cache.enabled: true","."," Default is off. You opt in per server, not globally.",[60,1562,1564],{"id":1563},"progressive-example","Progressive example",[28,1566,1567],{},"Start simple - cache everything on one server:",[204,1569,1571],{"className":206,"code":1570,"language":208,"meta":209,"style":209},"tools:\n  mcp:\n    - name: knowledge-base\n      url: http:\u002F\u002Fkb-mcp.tools.svc:8080\n      cache:\n        enabled: true\n",[36,1572,1573,1579,1585,1596,1605,1611],{"__ignoreMap":209},[213,1574,1575,1577],{"class":215,"line":216},[213,1576,1259],{"class":226},[213,1578,231],{"class":230},[213,1580,1581,1583],{"class":215,"line":223},[213,1582,1266],{"class":226},[213,1584,231],{"class":230},[213,1586,1587,1589,1591,1593],{"class":215,"line":234},[213,1588,245],{"class":230},[213,1590,248],{"class":226},[213,1592,251],{"class":230},[213,1594,1595],{"class":254},"knowledge-base\n",[213,1597,1598,1600,1602],{"class":215,"line":242},[213,1599,1284],{"class":226},[213,1601,251],{"class":230},[213,1603,1604],{"class":254},"http:\u002F\u002Fkb-mcp.tools.svc:8080\n",[213,1606,1607,1609],{"class":215,"line":258},[213,1608,1294],{"class":226},[213,1610,231],{"class":230},[213,1612,1613,1615,1617],{"class":215,"line":269},[213,1614,1301],{"class":226},[213,1616,251],{"class":230},[213,1618,1306],{"class":410},[28,1620,1621],{},"Defaults kick in: 300s TTL, no excludes. Every tool on that server gets cached.",[28,1623,1624],{},"Get specific - different TTLs per server, exclude writes:",[204,1626,1628],{"className":206,"code":1627,"language":208,"meta":209,"style":209},"tools:\n  mcp:\n    - name: search\n      url: http:\u002F\u002Fsearch.tools.svc:8080\n      cache:\n        enabled: true\n        ttlSeconds: 600\n    - name: crm\n      url: http:\u002F\u002Fcrm-mcp.tools.svc:8080\n      cache:\n        enabled: true\n        ttlSeconds: 60\n        excludeTools:\n          - update_contact\n          - create_deal\n          - send_message\n",[36,1629,1630,1636,1642,1652,1661,1667,1675,1683,1694,1703,1709,1717,1726,1732,1739,1746],{"__ignoreMap":209},[213,1631,1632,1634],{"class":215,"line":216},[213,1633,1259],{"class":226},[213,1635,231],{"class":230},[213,1637,1638,1640],{"class":215,"line":223},[213,1639,1266],{"class":226},[213,1641,231],{"class":230},[213,1643,1644,1646,1648,1650],{"class":215,"line":234},[213,1645,245],{"class":230},[213,1647,248],{"class":226},[213,1649,251],{"class":230},[213,1651,1279],{"class":254},[213,1653,1654,1656,1658],{"class":215,"line":242},[213,1655,1284],{"class":226},[213,1657,251],{"class":230},[213,1659,1660],{"class":254},"http:\u002F\u002Fsearch.tools.svc:8080\n",[213,1662,1663,1665],{"class":215,"line":258},[213,1664,1294],{"class":226},[213,1666,231],{"class":230},[213,1668,1669,1671,1673],{"class":215,"line":269},[213,1670,1301],{"class":226},[213,1672,251],{"class":230},[213,1674,1306],{"class":410},[213,1676,1677,1679,1681],{"class":215,"line":277},[213,1678,1311],{"class":226},[213,1680,251],{"class":230},[213,1682,1316],{"class":410},[213,1684,1685,1687,1689,1691],{"class":215,"line":289},[213,1686,245],{"class":230},[213,1688,248],{"class":226},[213,1690,251],{"class":230},[213,1692,1693],{"class":254},"crm\n",[213,1695,1696,1698,1700],{"class":215,"line":295},[213,1697,1284],{"class":226},[213,1699,251],{"class":230},[213,1701,1702],{"class":254},"http:\u002F\u002Fcrm-mcp.tools.svc:8080\n",[213,1704,1705,1707],{"class":215,"line":301},[213,1706,1294],{"class":226},[213,1708,231],{"class":230},[213,1710,1711,1713,1715],{"class":215,"line":307},[213,1712,1301],{"class":226},[213,1714,251],{"class":230},[213,1716,1306],{"class":410},[213,1718,1719,1721,1723],{"class":215,"line":319},[213,1720,1311],{"class":226},[213,1722,251],{"class":230},[213,1724,1725],{"class":410},"60\n",[213,1727,1728,1730],{"class":215,"line":328},[213,1729,1321],{"class":226},[213,1731,231],{"class":230},[213,1733,1734,1736],{"class":215,"line":335},[213,1735,1328],{"class":230},[213,1737,1738],{"class":254},"update_contact\n",[213,1740,1741,1743],{"class":215,"line":345},[213,1742,1328],{"class":230},[213,1744,1745],{"class":254},"create_deal\n",[213,1747,1748,1750],{"class":215,"line":352},[213,1749,1328],{"class":230},[213,1751,1752],{"class":254},"send_message\n",[28,1754,1755],{},"Search results are stable for 10 minutes. CRM lookups get a shorter window because the data changes more often. Write operations always go through.",[60,1757,1759],{"id":1758},"when-not-to-use-it","When not to use it",[28,1761,1762],{},"If your tools return different results for the same input (random sampling, current timestamp, live sensor data), caching will serve stale values. Don't enable it for those servers.",[28,1764,1765],{},"If your reasoning loops genuinely need fresh data on every iteration (polling for completion, watching for state changes), the TTL needs to be shorter than your poll interval - or just leave caching off for that server.",[60,1767,1769],{"id":1768},"the-takeaway","The takeaway",[28,1771,1772],{},"80% of your agent's tool calls are waste. Three lines of YAML eliminate them. The model gets identical results, your MCP servers get 80% less traffic, and your agent finishes faster.",[28,1774,1775,1776,1778],{},"Try it: add ",[36,1777,1558],{}," to one MCP server and watch your tool call metrics drop.",[1174,1780,1781],{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}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);}",{"title":209,"searchDepth":223,"depth":223,"links":1783},[1784,1785,1786,1787,1788,1789,1790,1791],{"id":1226,"depth":223,"text":1227},{"id":1243,"depth":223,"text":1244},{"id":1346,"depth":223,"text":1347},{"id":1492,"depth":223,"text":1493},{"id":1521,"depth":223,"text":1522},{"id":1563,"depth":223,"text":1564},{"id":1758,"depth":223,"text":1759},{"id":1768,"depth":223,"text":1769},"2026-04-29","kubeswarm's tool result cache cuts 80% of redundant MCP calls in reasoning loops - zero config changes to your tools, works with any provider.",{},"\u002Fblog\u002Ftool-result-caching",{"title":1203,"description":1793},"blog\u002Ftool-result-caching",[1799,1198,1800,1187],"cost-control","mcp","o8WoMKTlVM5B7UUhlb6qCY8MmCsIE4WMz6qzAziPECo",{"id":1803,"title":1804,"author":19,"authorUrl":20,"body":1805,"category":3679,"date":3680,"description":3681,"extension":1190,"image":209,"meta":3682,"navigation":348,"path":3683,"seo":3684,"series":1194,"stem":3685,"tags":3686,"__hash__":3689},"blog\u002Fblog\u002Fincident-response.md","Automated incident response with AI agents on Kubernetes",{"type":22,"value":1806,"toc":3667},[1807,1819,1821,1828,1858,1865,1868,1871,1875,1878,1946,1949,1953,1962,1978,1982,1985,1992,2030,2050,2058,2066,2070,2075,2251,2269,2274,2362,2368,2373,2530,2536,2591,2597,2601,2604,2814,2817,2833,2837,2840,2921,2956,2962,2965,2980,3051,3054,3059,3142,3149,3154,3173,3179,3182,3186,3189,3305,3359,3363,3366,3436,3443,3514,3517,3521,3604,3607,3611,3628,3642,3644,3664],[25,1808,1809],{},[28,1810,1811,1813,1814,1559],{},[31,1812,33],{}," - Three agents investigate a production alert, diagnose root cause, and notify\nyour team on Slack. Uses mock MCP servers for PagerDuty, Grafana, and Slack - all running\nlocally. Total time: under 3 minutes. Total API cost: $0.00.\nAll files are in the ",[1131,1815,1818],{"href":1816,"rel":1817},"https:\u002F\u002Fgithub.com\u002Fkubeswarm\u002Fkubeswarm-cookbook\u002Ftree\u002Fmain\u002Frecipes\u002F16-incident-response",[1135],"examples directory",[45,1820],{},[28,1822,1823,1824,1827],{},"This showed up in our ",[36,1825,1826],{},"#incidents"," channel at 02:52 UTC:",[25,1829,1830,1847],{},[28,1831,1832,1835,1836,1839,1840,1843,1844],{},[31,1833,1834],{},"Service:"," payments-api\n",[31,1837,1838],{},"Root Cause:"," PostgreSQL primary server failure leading to\ndatabase connection issues and exhausted connection pool\n",[31,1841,1842],{},"Severity:"," High\n",[31,1845,1846],{},"Remediation:",[164,1848,1849,1852,1855],{},[167,1850,1851],{},"Promote the standby PostgreSQL replica to primary",[167,1853,1854],{},"Restart the payments-api deployment",[167,1856,1857],{},"Clear connection pool caches",[28,1859,1860,1861,1864],{},"Nobody on the team wrote that. An agent pipeline did. It pulled logs from Grafana,\nfound 312 error entries pointing to ",[36,1862,1863],{},"postgres-primary:5432 - connection refused",",\ncorrelated the spike with a failover event at 02:47, and posted the summary.\nThe PagerDuty alert got acknowledged automatically.",[28,1866,1867],{},"I wanted to see if I could wire this up with kubeswarm - three agents, each with\ndifferent tool access, where the one reading logs can't post to Slack and the one\nposting to Slack can't read logs. Turns out you can, and it's not that much YAML.",[28,1869,1870],{},"Here's how I built it.",[60,1872,1874],{"id":1873},"the-setup","The setup",[28,1876,1877],{},"Three agents, one pipeline. I split it by access level on purpose - I don't want\na single agent that can both read production data and take actions on it.",[68,1879,1880,1896],{},[71,1881,1882],{},[74,1883,1884,1887,1890,1893],{},[77,1885,1886],{},"Agent",[77,1888,1889],{},"Role",[77,1891,1892],{},"MCP Tools",[77,1894,1895],{},"What it does",[90,1897,1898,1914,1930],{},[74,1899,1900,1905,1908,1911],{},[95,1901,1902],{},[31,1903,1904],{},"Investigator",[95,1906,1907],{},"Gather evidence",[95,1909,1910],{},"Grafana (logs, metrics), PagerDuty (read)",[95,1912,1913],{},"Queries logs and metrics around the alert",[74,1915,1916,1921,1924,1927],{},[95,1917,1918],{},[31,1919,1920],{},"Diagnostician",[95,1922,1923],{},"Analyze",[95,1925,1926],{},"None",[95,1928,1929],{},"Reads the evidence, identifies root cause",[74,1931,1932,1937,1940,1943],{},[95,1933,1934],{},[31,1935,1936],{},"Notifier",[95,1938,1939],{},"Communicate",[95,1941,1942],{},"Slack (write), PagerDuty (acknowledge)",[95,1944,1945],{},"Posts findings, acks the alert",[28,1947,1948],{},"The diagnostician is intentionally tool-less. It gets the investigator's report,\nfigures out what went wrong, and passes that to the notifier. The agent that\ndecides what happened should never be the same agent that has write access.",[60,1950,1952],{"id":1951},"prerequisites","Prerequisites",[28,1954,1955,1956,1961],{},"You need a Kubernetes cluster with kubeswarm installed\n(",[1131,1957,1960],{"href":1958,"rel":1959},"https:\u002F\u002Fdocs.kubeswarm.io\u002Fquick-start",[1135],"quick-start guide",") and Ollama running:",[204,1963,1965],{"className":838,"code":1964,"language":840,"meta":209,"style":209},"ollama pull qwen2.5:7b\n",[36,1966,1967],{"__ignoreMap":209},[213,1968,1969,1972,1975],{"class":215,"line":216},[213,1970,1971],{"class":847},"ollama",[213,1973,1974],{"class":254}," pull",[213,1976,1977],{"class":254}," qwen2.5:7b\n",[60,1979,1981],{"id":1980},"mock-mcp-servers","Mock MCP servers",[28,1983,1984],{},"Obviously I'm not going to connect this to real PagerDuty and Grafana for a blog post.\nSo I wrote three tiny mock MCP servers in Go - about 80 lines each, no dependencies\noutside stdlib. They return realistic canned data over HTTP.",[28,1986,1987,1988,1991],{},"The mock Grafana returns error logs with ",[36,1989,1990],{},"connection refused to postgres-primary:5432","\nand a metrics spike at 02:47 UTC. Classic postgres failover scenario.",[204,1993,1995],{"className":838,"code":1994,"language":840,"meta":209,"style":209},"kubectl apply -f namespace.yaml\nkubectl apply -f ollama-secret.yaml\nkubectl apply -f mock-servers.yaml\n",[36,1996,1997,2008,2019],{"__ignoreMap":209},[213,1998,1999,2001,2003,2005],{"class":215,"line":216},[213,2000,848],{"class":847},[213,2002,1149],{"class":254},[213,2004,1152],{"class":410},[213,2006,2007],{"class":254}," namespace.yaml\n",[213,2009,2010,2012,2014,2016],{"class":215,"line":223},[213,2011,848],{"class":847},[213,2013,1149],{"class":254},[213,2015,1152],{"class":410},[213,2017,2018],{"class":254}," ollama-secret.yaml\n",[213,2020,2021,2023,2025,2027],{"class":215,"line":234},[213,2022,848],{"class":847},[213,2024,1149],{"class":254},[213,2026,1152],{"class":410},[213,2028,2029],{"class":254}," mock-servers.yaml\n",[204,2031,2033],{"className":838,"code":2032,"language":840,"meta":209,"style":209},"kubectl get pods -n incident-response\n",[36,2034,2035],{"__ignoreMap":209},[213,2036,2037,2039,2041,2044,2047],{"class":215,"line":216},[213,2038,848],{"class":847},[213,2040,851],{"class":254},[213,2042,2043],{"class":254}," pods",[213,2045,2046],{"class":410}," -n",[213,2048,2049],{"class":254}," incident-response\n",[204,2051,2056],{"className":2052,"code":2054,"language":2055},[2053],"language-text","NAME                              READY   STATUS    AGE\nmock-grafana-7b4f8d6c5-x2k9p     1\u002F1     Running   5s\nmock-pagerduty-5c9d8e7f4-m3n7q   1\u002F1     Running   5s\nmock-slack-6a8b9c0d1-r4s6t       1\u002F1     Running   5s\n","text",[36,2057,2054],{"__ignoreMap":209},[28,2059,2060,2061,2065],{},"Source code for the mocks is in the\n",[1131,2062,1818],{"href":2063,"rel":2064},"https:\u002F\u002Fgithub.com\u002Fkubeswarm\u002Fkubeswarm-cookbook\u002Ftree\u002Fmain\u002Frecipes\u002F16-incident-response\u002Fmock-servers",[1135],"\nif you want to look - they're straightforward.",[60,2067,2069],{"id":2068},"the-agents","The agents",[28,2071,2072,2074],{},[31,2073,1904],{}," - read-only access to observability:",[204,2076,2078],{"className":206,"code":2077,"language":208,"meta":209,"style":209},"# investigator-agent.yaml\nspec:\n  model: qwen2.5:7b\n  prompt:\n    inline: |\n      You are an SRE investigator. When you receive a PagerDuty incident,\n      gather evidence from logs and metrics. Output a structured JSON report\n      with incident_id, error_pattern, timeline, and raw_evidence.\n  tools:\n    mcp:\n      - name: pagerduty\n        url: \"http:\u002F\u002Fmock-pagerduty.incident-response.svc:8080\"\n      - name: grafana\n        url: \"http:\u002F\u002Fmock-grafana.incident-response.svc:8080\"\n  guardrails:\n    limits:\n      tokensPerCall: 4000\n      timeoutSeconds: 90\n    tools:\n      allow:\n        - \"pagerduty\u002Fget_incident\"\n        - \"grafana\u002F*\"\n",[36,2079,2080,2085,2091,2101,2108,2117,2122,2127,2132,2139,2146,2158,2168,2179,2188,2195,2202,2212,2222,2229,2236,2244],{"__ignoreMap":209},[213,2081,2082],{"class":215,"line":216},[213,2083,2084],{"class":219},"# investigator-agent.yaml\n",[213,2086,2087,2089],{"class":215,"line":223},[213,2088,227],{"class":226},[213,2090,231],{"class":230},[213,2092,2093,2096,2098],{"class":215,"line":234},[213,2094,2095],{"class":226},"  model",[213,2097,251],{"class":230},[213,2099,2100],{"class":254},"qwen2.5:7b\n",[213,2102,2103,2106],{"class":215,"line":242},[213,2104,2105],{"class":226},"  prompt",[213,2107,231],{"class":230},[213,2109,2110,2113,2115],{"class":215,"line":258},[213,2111,2112],{"class":226},"    inline",[213,2114,251],{"class":230},[213,2116,286],{"class":285},[213,2118,2119],{"class":215,"line":269},[213,2120,2121],{"class":254},"      You are an SRE investigator. When you receive a PagerDuty incident,\n",[213,2123,2124],{"class":215,"line":277},[213,2125,2126],{"class":254},"      gather evidence from logs and metrics. Output a structured JSON report\n",[213,2128,2129],{"class":215,"line":289},[213,2130,2131],{"class":254},"      with incident_id, error_pattern, timeline, and raw_evidence.\n",[213,2133,2134,2137],{"class":215,"line":295},[213,2135,2136],{"class":226},"  tools",[213,2138,231],{"class":230},[213,2140,2141,2144],{"class":215,"line":301},[213,2142,2143],{"class":226},"    mcp",[213,2145,231],{"class":230},[213,2147,2148,2151,2153,2155],{"class":215,"line":307},[213,2149,2150],{"class":230},"      - ",[213,2152,248],{"class":226},[213,2154,251],{"class":230},[213,2156,2157],{"class":254},"pagerduty\n",[213,2159,2160,2163,2165],{"class":215,"line":319},[213,2161,2162],{"class":226},"        url",[213,2164,251],{"class":230},[213,2166,2167],{"class":254},"\"http:\u002F\u002Fmock-pagerduty.incident-response.svc:8080\"\n",[213,2169,2170,2172,2174,2176],{"class":215,"line":328},[213,2171,2150],{"class":230},[213,2173,248],{"class":226},[213,2175,251],{"class":230},[213,2177,2178],{"class":254},"grafana\n",[213,2180,2181,2183,2185],{"class":215,"line":335},[213,2182,2162],{"class":226},[213,2184,251],{"class":230},[213,2186,2187],{"class":254},"\"http:\u002F\u002Fmock-grafana.incident-response.svc:8080\"\n",[213,2189,2190,2193],{"class":215,"line":345},[213,2191,2192],{"class":226},"  guardrails",[213,2194,231],{"class":230},[213,2196,2197,2200],{"class":215,"line":352},[213,2198,2199],{"class":226},"    limits",[213,2201,231],{"class":230},[213,2203,2204,2207,2209],{"class":215,"line":360},[213,2205,2206],{"class":226},"      tokensPerCall",[213,2208,251],{"class":230},[213,2210,2211],{"class":410},"4000\n",[213,2213,2214,2217,2219],{"class":215,"line":371},[213,2215,2216],{"class":226},"      timeoutSeconds",[213,2218,251],{"class":230},[213,2220,2221],{"class":410},"90\n",[213,2223,2224,2227],{"class":215,"line":381},[213,2225,2226],{"class":226},"    tools",[213,2228,231],{"class":230},[213,2230,2231,2234],{"class":215,"line":391},[213,2232,2233],{"class":226},"      allow",[213,2235,231],{"class":230},[213,2237,2238,2241],{"class":215,"line":402},[213,2239,2240],{"class":230},"        - ",[213,2242,2243],{"class":254},"\"pagerduty\u002Fget_incident\"\n",[213,2245,2246,2248],{"class":215,"line":414},[213,2247,2240],{"class":230},[213,2249,2250],{"class":254},"\"grafana\u002F*\"\n",[28,2252,2253,2254,2257,2258,1504,2261,2264,2265,2268],{},"Note the ",[36,2255,2256],{},"tools.allow"," list. It can call ",[36,2259,2260],{},"grafana\u002Fquery_logs",[36,2262,2263],{},"grafana\u002Fquery_metrics","\nbut if I added a ",[36,2266,2267],{},"slack"," MCP server here, the allow list would block it. I like that\nthe access control is declarative and visible in the YAML.",[28,2270,2271,2273],{},[31,2272,1920],{}," - no tools, just reasoning:",[204,2275,2277],{"className":206,"code":2276,"language":208,"meta":209,"style":209},"# diagnostician-agent.yaml\nspec:\n  model: qwen2.5:7b\n  prompt:\n    inline: |\n      You are a senior SRE diagnostician. Identify the root cause from the\n      investigation report. Suggest a specific remediation command, not\n      vague advice. Output JSON with root_cause, severity, remediation,\n      and confidence.\n  guardrails:\n    limits:\n      tokensPerCall: 3000\n      timeoutSeconds: 120\n",[36,2278,2279,2284,2290,2298,2304,2312,2317,2322,2327,2332,2338,2344,2353],{"__ignoreMap":209},[213,2280,2281],{"class":215,"line":216},[213,2282,2283],{"class":219},"# diagnostician-agent.yaml\n",[213,2285,2286,2288],{"class":215,"line":223},[213,2287,227],{"class":226},[213,2289,231],{"class":230},[213,2291,2292,2294,2296],{"class":215,"line":234},[213,2293,2095],{"class":226},[213,2295,251],{"class":230},[213,2297,2100],{"class":254},[213,2299,2300,2302],{"class":215,"line":242},[213,2301,2105],{"class":226},[213,2303,231],{"class":230},[213,2305,2306,2308,2310],{"class":215,"line":258},[213,2307,2112],{"class":226},[213,2309,251],{"class":230},[213,2311,286],{"class":285},[213,2313,2314],{"class":215,"line":269},[213,2315,2316],{"class":254},"      You are a senior SRE diagnostician. Identify the root cause from the\n",[213,2318,2319],{"class":215,"line":277},[213,2320,2321],{"class":254},"      investigation report. Suggest a specific remediation command, not\n",[213,2323,2324],{"class":215,"line":289},[213,2325,2326],{"class":254},"      vague advice. Output JSON with root_cause, severity, remediation,\n",[213,2328,2329],{"class":215,"line":295},[213,2330,2331],{"class":254},"      and confidence.\n",[213,2333,2334,2336],{"class":215,"line":301},[213,2335,2192],{"class":226},[213,2337,231],{"class":230},[213,2339,2340,2342],{"class":215,"line":307},[213,2341,2199],{"class":226},[213,2343,231],{"class":230},[213,2345,2346,2348,2350],{"class":215,"line":319},[213,2347,2206],{"class":226},[213,2349,251],{"class":230},[213,2351,2352],{"class":410},"3000\n",[213,2354,2355,2357,2359],{"class":215,"line":328},[213,2356,2216],{"class":226},[213,2358,251],{"class":230},[213,2360,2361],{"class":410},"120\n",[28,2363,2364,2365,2367],{},"No ",[36,2366,1259],{}," section at all. I went back and forth on whether this agent should have\naccess to anything. Decided no - the separation between \"reading\" and \"deciding\"\nis the whole point.",[28,2369,2370,2372],{},[31,2371,1936],{}," - write access to Slack and PagerDuty:",[204,2374,2376],{"className":206,"code":2375,"language":208,"meta":209,"style":209},"# notifier-agent.yaml\nspec:\n  model: qwen2.5:7b\n  prompt:\n    inline: |\n      You are an incident communications agent.\n      You MUST call post_message with channel \"#incidents\" and a text\n      summary. Then call acknowledge_incident with the incident_id.\n  tools:\n    mcp:\n      - name: slack\n        url: \"http:\u002F\u002Fmock-slack.incident-response.svc:8080\"\n      - name: pagerduty\n        url: \"http:\u002F\u002Fmock-pagerduty.incident-response.svc:8080\"\n  guardrails:\n    limits:\n      tokensPerCall: 3000\n      timeoutSeconds: 90\n    tools:\n      allow:\n        - \"slack\u002Fpost_message\"\n        - \"pagerduty\u002Facknowledge_incident\"\n",[36,2377,2378,2383,2389,2397,2403,2411,2416,2421,2426,2432,2438,2449,2458,2468,2476,2482,2488,2496,2504,2510,2516,2523],{"__ignoreMap":209},[213,2379,2380],{"class":215,"line":216},[213,2381,2382],{"class":219},"# notifier-agent.yaml\n",[213,2384,2385,2387],{"class":215,"line":223},[213,2386,227],{"class":226},[213,2388,231],{"class":230},[213,2390,2391,2393,2395],{"class":215,"line":234},[213,2392,2095],{"class":226},[213,2394,251],{"class":230},[213,2396,2100],{"class":254},[213,2398,2399,2401],{"class":215,"line":242},[213,2400,2105],{"class":226},[213,2402,231],{"class":230},[213,2404,2405,2407,2409],{"class":215,"line":258},[213,2406,2112],{"class":226},[213,2408,251],{"class":230},[213,2410,286],{"class":285},[213,2412,2413],{"class":215,"line":269},[213,2414,2415],{"class":254},"      You are an incident communications agent.\n",[213,2417,2418],{"class":215,"line":277},[213,2419,2420],{"class":254},"      You MUST call post_message with channel \"#incidents\" and a text\n",[213,2422,2423],{"class":215,"line":289},[213,2424,2425],{"class":254},"      summary. Then call acknowledge_incident with the incident_id.\n",[213,2427,2428,2430],{"class":215,"line":295},[213,2429,2136],{"class":226},[213,2431,231],{"class":230},[213,2433,2434,2436],{"class":215,"line":301},[213,2435,2143],{"class":226},[213,2437,231],{"class":230},[213,2439,2440,2442,2444,2446],{"class":215,"line":307},[213,2441,2150],{"class":230},[213,2443,248],{"class":226},[213,2445,251],{"class":230},[213,2447,2448],{"class":254},"slack\n",[213,2450,2451,2453,2455],{"class":215,"line":319},[213,2452,2162],{"class":226},[213,2454,251],{"class":230},[213,2456,2457],{"class":254},"\"http:\u002F\u002Fmock-slack.incident-response.svc:8080\"\n",[213,2459,2460,2462,2464,2466],{"class":215,"line":328},[213,2461,2150],{"class":230},[213,2463,248],{"class":226},[213,2465,251],{"class":230},[213,2467,2157],{"class":254},[213,2469,2470,2472,2474],{"class":215,"line":335},[213,2471,2162],{"class":226},[213,2473,251],{"class":230},[213,2475,2167],{"class":254},[213,2477,2478,2480],{"class":215,"line":345},[213,2479,2192],{"class":226},[213,2481,231],{"class":230},[213,2483,2484,2486],{"class":215,"line":352},[213,2485,2199],{"class":226},[213,2487,231],{"class":230},[213,2489,2490,2492,2494],{"class":215,"line":360},[213,2491,2206],{"class":226},[213,2493,251],{"class":230},[213,2495,2352],{"class":410},[213,2497,2498,2500,2502],{"class":215,"line":371},[213,2499,2216],{"class":226},[213,2501,251],{"class":230},[213,2503,2221],{"class":410},[213,2505,2506,2508],{"class":215,"line":381},[213,2507,2226],{"class":226},[213,2509,231],{"class":230},[213,2511,2512,2514],{"class":215,"line":391},[213,2513,2233],{"class":226},[213,2515,231],{"class":230},[213,2517,2518,2520],{"class":215,"line":402},[213,2519,2240],{"class":230},[213,2521,2522],{"class":254},"\"slack\u002Fpost_message\"\n",[213,2524,2525,2527],{"class":215,"line":414},[213,2526,2240],{"class":230},[213,2528,2529],{"class":254},"\"pagerduty\u002Facknowledge_incident\"\n",[28,2531,1129,2532,2535],{},[36,2533,2534],{},"MUST call"," in the prompt is not elegant, but with a 7B model you sometimes\nneed to be blunt. Bigger models follow the instructions without the shouting.",[204,2537,2539],{"className":838,"code":2538,"language":840,"meta":209,"style":209},"kubectl apply -f investigator-agent.yaml\nkubectl apply -f diagnostician-agent.yaml\nkubectl apply -f notifier-agent.yaml\n\nkubectl get swarmagents -n incident-response\n",[36,2540,2541,2552,2563,2574,2578],{"__ignoreMap":209},[213,2542,2543,2545,2547,2549],{"class":215,"line":216},[213,2544,848],{"class":847},[213,2546,1149],{"class":254},[213,2548,1152],{"class":410},[213,2550,2551],{"class":254}," investigator-agent.yaml\n",[213,2553,2554,2556,2558,2560],{"class":215,"line":223},[213,2555,848],{"class":847},[213,2557,1149],{"class":254},[213,2559,1152],{"class":410},[213,2561,2562],{"class":254}," diagnostician-agent.yaml\n",[213,2564,2565,2567,2569,2571],{"class":215,"line":234},[213,2566,848],{"class":847},[213,2568,1149],{"class":254},[213,2570,1152],{"class":410},[213,2572,2573],{"class":254}," notifier-agent.yaml\n",[213,2575,2576],{"class":215,"line":242},[213,2577,349],{"emptyLinePlaceholder":348},[213,2579,2580,2582,2584,2587,2589],{"class":215,"line":258},[213,2581,848],{"class":847},[213,2583,851],{"class":254},[213,2585,2586],{"class":254}," swarmagents",[213,2588,2046],{"class":410},[213,2590,2049],{"class":254},[204,2592,2595],{"className":2593,"code":2594,"language":2055},[2053],"NAME                      MODEL        REPLICAS   READY   AGE\nincident-investigator     qwen2.5:7b   1          1       5s\nincident-diagnostician    qwen2.5:7b   1          1       5s\nincident-notifier         qwen2.5:7b   1          1       5s\n",[36,2596,2594],{"__ignoreMap":209},[60,2598,2600],{"id":2599},"the-pipeline","The pipeline",[28,2602,2603],{},"A SwarmTeam wires the three agents into a DAG. Diagnostician waits for investigator,\nnotifier waits for diagnostician.",[204,2605,2607],{"className":206,"code":2606,"language":208,"meta":209,"style":209},"# incident-team.yaml\nspec:\n  roles:\n    - name: investigator\n      swarmAgent: incident-investigator\n    - name: diagnostician\n      swarmAgent: incident-diagnostician\n    - name: notifier\n      swarmAgent: incident-notifier\n  pipeline:\n    - role: investigator\n      inputs:\n        alert: \"{{ .input.alert }}\"\n    - role: diagnostician\n      dependsOn: [investigator]\n      inputs:\n        investigation: \"{{ .steps.investigator.output }}\"\n        alert: \"{{ .input.alert }}\"\n    - role: notifier\n      dependsOn: [diagnostician]\n      inputs:\n        investigation: \"{{ .steps.investigator.output }}\"\n        diagnosis: \"{{ .steps.diagnostician.output }}\"\n",[36,2608,2609,2614,2620,2626,2636,2646,2657,2666,2677,2686,2693,2704,2711,2721,2731,2745,2751,2761,2769,2779,2790,2796,2804],{"__ignoreMap":209},[213,2610,2611],{"class":215,"line":216},[213,2612,2613],{"class":219},"# incident-team.yaml\n",[213,2615,2616,2618],{"class":215,"line":223},[213,2617,227],{"class":226},[213,2619,231],{"class":230},[213,2621,2622,2624],{"class":215,"line":234},[213,2623,237],{"class":226},[213,2625,231],{"class":230},[213,2627,2628,2630,2632,2634],{"class":215,"line":242},[213,2629,245],{"class":230},[213,2631,248],{"class":226},[213,2633,251],{"class":230},[213,2635,479],{"class":254},[213,2637,2638,2641,2643],{"class":215,"line":258},[213,2639,2640],{"class":226},"      swarmAgent",[213,2642,251],{"class":230},[213,2644,2645],{"class":254},"incident-investigator\n",[213,2647,2648,2650,2652,2654],{"class":215,"line":269},[213,2649,245],{"class":230},[213,2651,248],{"class":226},[213,2653,251],{"class":230},[213,2655,2656],{"class":254},"diagnostician\n",[213,2658,2659,2661,2663],{"class":215,"line":277},[213,2660,2640],{"class":226},[213,2662,251],{"class":230},[213,2664,2665],{"class":254},"incident-diagnostician\n",[213,2667,2668,2670,2672,2674],{"class":215,"line":289},[213,2669,245],{"class":230},[213,2671,248],{"class":226},[213,2673,251],{"class":230},[213,2675,2676],{"class":254},"notifier\n",[213,2678,2679,2681,2683],{"class":215,"line":295},[213,2680,2640],{"class":226},[213,2682,251],{"class":230},[213,2684,2685],{"class":254},"incident-notifier\n",[213,2687,2688,2691],{"class":215,"line":301},[213,2689,2690],{"class":226},"  pipeline",[213,2692,231],{"class":230},[213,2694,2695,2697,2700,2702],{"class":215,"line":307},[213,2696,245],{"class":230},[213,2698,2699],{"class":226},"role",[213,2701,251],{"class":230},[213,2703,479],{"class":254},[213,2705,2706,2709],{"class":215,"line":319},[213,2707,2708],{"class":226},"      inputs",[213,2710,231],{"class":230},[213,2712,2713,2716,2718],{"class":215,"line":328},[213,2714,2715],{"class":226},"        alert",[213,2717,251],{"class":230},[213,2719,2720],{"class":254},"\"{{ .input.alert }}\"\n",[213,2722,2723,2725,2727,2729],{"class":215,"line":335},[213,2724,245],{"class":230},[213,2726,2699],{"class":226},[213,2728,251],{"class":230},[213,2730,2656],{"class":254},[213,2732,2733,2736,2739,2742],{"class":215,"line":345},[213,2734,2735],{"class":226},"      dependsOn",[213,2737,2738],{"class":230},": [",[213,2740,2741],{"class":254},"investigator",[213,2743,2744],{"class":230},"]\n",[213,2746,2747,2749],{"class":215,"line":352},[213,2748,2708],{"class":226},[213,2750,231],{"class":230},[213,2752,2753,2756,2758],{"class":215,"line":360},[213,2754,2755],{"class":226},"        investigation",[213,2757,251],{"class":230},[213,2759,2760],{"class":254},"\"{{ .steps.investigator.output }}\"\n",[213,2762,2763,2765,2767],{"class":215,"line":371},[213,2764,2715],{"class":226},[213,2766,251],{"class":230},[213,2768,2720],{"class":254},[213,2770,2771,2773,2775,2777],{"class":215,"line":381},[213,2772,245],{"class":230},[213,2774,2699],{"class":226},[213,2776,251],{"class":230},[213,2778,2676],{"class":254},[213,2780,2781,2783,2785,2788],{"class":215,"line":391},[213,2782,2735],{"class":226},[213,2784,2738],{"class":230},[213,2786,2787],{"class":254},"diagnostician",[213,2789,2744],{"class":230},[213,2791,2792,2794],{"class":215,"line":402},[213,2793,2708],{"class":226},[213,2795,231],{"class":230},[213,2797,2798,2800,2802],{"class":215,"line":414},[213,2799,2755],{"class":226},[213,2801,251],{"class":230},[213,2803,2760],{"class":254},[213,2805,2806,2809,2811],{"class":215,"line":425},[213,2807,2808],{"class":226},"        diagnosis",[213,2810,251],{"class":230},[213,2812,2813],{"class":254},"\"{{ .steps.diagnostician.output }}\"\n",[28,2815,2816],{},"The notifier gets both the investigation and the diagnosis as input, so it has\nfull context when writing the Slack message.",[204,2818,2820],{"className":838,"code":2819,"language":840,"meta":209,"style":209},"kubectl apply -f incident-team.yaml\n",[36,2821,2822],{"__ignoreMap":209},[213,2823,2824,2826,2828,2830],{"class":215,"line":216},[213,2825,848],{"class":847},[213,2827,1149],{"class":254},[213,2829,1152],{"class":410},[213,2831,2832],{"class":254}," incident-team.yaml\n",[60,2834,2836],{"id":2835},"running-it","Running it",[28,2838,2839],{},"I fed it a simulated PagerDuty alert - high error rate on the payments API:",[204,2841,2843],{"className":206,"code":2842,"language":208,"meta":209,"style":209},"# sample-incident.yaml\nspec:\n  teamRef: incident-responder\n  input:\n    alert: |\n      PagerDuty Incident P-48291: High error rate on payments-api\n\n      Severity: High\n      Service: payments-api\n      Triggered: 2026-04-23T02:45:00Z\n      Description: Error rate on payments-api exceeded 5% threshold.\n      Current rate: 12.3% 5xx responses over the last 5 minutes.\n      Alert count: 47 alerts in the last 8 minutes\n",[36,2844,2845,2850,2856,2866,2873,2882,2887,2891,2896,2901,2906,2911,2916],{"__ignoreMap":209},[213,2846,2847],{"class":215,"line":216},[213,2848,2849],{"class":219},"# sample-incident.yaml\n",[213,2851,2852,2854],{"class":215,"line":223},[213,2853,227],{"class":226},[213,2855,231],{"class":230},[213,2857,2858,2861,2863],{"class":215,"line":234},[213,2859,2860],{"class":226},"  teamRef",[213,2862,251],{"class":230},[213,2864,2865],{"class":254},"incident-responder\n",[213,2867,2868,2871],{"class":215,"line":242},[213,2869,2870],{"class":226},"  input",[213,2872,231],{"class":230},[213,2874,2875,2878,2880],{"class":215,"line":258},[213,2876,2877],{"class":226},"    alert",[213,2879,251],{"class":230},[213,2881,286],{"class":285},[213,2883,2884],{"class":215,"line":269},[213,2885,2886],{"class":254},"      PagerDuty Incident P-48291: High error rate on payments-api\n",[213,2888,2889],{"class":215,"line":277},[213,2890,349],{"emptyLinePlaceholder":348},[213,2892,2893],{"class":215,"line":289},[213,2894,2895],{"class":254},"      Severity: High\n",[213,2897,2898],{"class":215,"line":295},[213,2899,2900],{"class":254},"      Service: payments-api\n",[213,2902,2903],{"class":215,"line":301},[213,2904,2905],{"class":254},"      Triggered: 2026-04-23T02:45:00Z\n",[213,2907,2908],{"class":215,"line":307},[213,2909,2910],{"class":254},"      Description: Error rate on payments-api exceeded 5% threshold.\n",[213,2912,2913],{"class":215,"line":319},[213,2914,2915],{"class":254},"      Current rate: 12.3% 5xx responses over the last 5 minutes.\n",[213,2917,2918],{"class":215,"line":328},[213,2919,2920],{"class":254},"      Alert count: 47 alerts in the last 8 minutes\n",[204,2922,2924],{"className":838,"code":2923,"language":840,"meta":209,"style":209},"kubectl apply -f sample-incident.yaml\nkubectl get swarmrun incident-001 -n incident-response -w\n",[36,2925,2926,2937],{"__ignoreMap":209},[213,2927,2928,2930,2932,2934],{"class":215,"line":216},[213,2929,848],{"class":847},[213,2931,1149],{"class":254},[213,2933,1152],{"class":410},[213,2935,2936],{"class":254}," sample-incident.yaml\n",[213,2938,2939,2941,2943,2946,2949,2951,2954],{"class":215,"line":223},[213,2940,848],{"class":847},[213,2942,851],{"class":254},[213,2944,2945],{"class":254}," swarmrun",[213,2947,2948],{"class":254}," incident-001",[213,2950,2046],{"class":410},[213,2952,2953],{"class":254}," incident-response",[213,2955,1169],{"class":410},[204,2957,2960],{"className":2958,"code":2959,"language":2055},[2053],"NAME           PHASE      AGE\nincident-001   Pending    0s\nincident-001   Running    2s\nincident-001   Succeeded  2m18s\n",[36,2961,2959],{"__ignoreMap":209},[28,2963,2964],{},"Here's what each agent actually produced.",[28,2966,2967,2969,2970,898,2973,667,2976,2979],{},[31,2968,1904],{}," - it called all three MCP tools (",[36,2971,2972],{},"get_incident",[36,2974,2975],{},"query_logs",[36,2977,2978],{},"query_metrics",") and pulled together this report:",[204,2981,2983],{"className":645,"code":2982,"language":647,"meta":209,"style":209},"{\n  \"incident_id\": \"P-48291\",\n  \"service\": \"payments-api\",\n  \"error_pattern\": \"Connection timeout and pool exhaustion errors involving PostgreSQL primary server failure\",\n  \"timeline\": \"Incident started at 02:47 UTC when a postgres failover occurred, leading to an error rate spike from 0.1% to 12.3% by 02:50 UTC\",\n  \"metrics_summary\": \"Error rate on payments-api exceeded baseline starting at 02:47 UTC during a PostgreSQL failover event, peaking at 12.3%\"\n}\n",[36,2984,2985,2989,3001,3013,3025,3037,3047],{"__ignoreMap":209},[213,2986,2987],{"class":215,"line":216},[213,2988,654],{"class":230},[213,2990,2991,2994,2996,2999],{"class":215,"line":223},[213,2992,2993],{"class":410},"  \"incident_id\"",[213,2995,251],{"class":230},[213,2997,2998],{"class":254},"\"P-48291\"",[213,3000,667],{"class":230},[213,3002,3003,3006,3008,3011],{"class":215,"line":234},[213,3004,3005],{"class":410},"  \"service\"",[213,3007,251],{"class":230},[213,3009,3010],{"class":254},"\"payments-api\"",[213,3012,667],{"class":230},[213,3014,3015,3018,3020,3023],{"class":215,"line":242},[213,3016,3017],{"class":410},"  \"error_pattern\"",[213,3019,251],{"class":230},[213,3021,3022],{"class":254},"\"Connection timeout and pool exhaustion errors involving PostgreSQL primary server failure\"",[213,3024,667],{"class":230},[213,3026,3027,3030,3032,3035],{"class":215,"line":258},[213,3028,3029],{"class":410},"  \"timeline\"",[213,3031,251],{"class":230},[213,3033,3034],{"class":254},"\"Incident started at 02:47 UTC when a postgres failover occurred, leading to an error rate spike from 0.1% to 12.3% by 02:50 UTC\"",[213,3036,667],{"class":230},[213,3038,3039,3042,3044],{"class":215,"line":269},[213,3040,3041],{"class":410},"  \"metrics_summary\"",[213,3043,251],{"class":230},[213,3045,3046],{"class":254},"\"Error rate on payments-api exceeded baseline starting at 02:47 UTC during a PostgreSQL failover event, peaking at 12.3%\"\n",[213,3048,3049],{"class":215,"line":277},[213,3050,710],{"class":230},[28,3052,3053],{},"I checked the audit logs - every data point traces back to the mock Grafana\nresponse. No hallucination.",[28,3055,3056,3058],{},[31,3057,1920],{}," - correctly identified the root cause:",[204,3060,3062],{"className":645,"code":3061,"language":647,"meta":209,"style":209},"{\n  \"root_cause\": \"PostgreSQL primary server failure leading to database connection issues and exhausted connection pool\",\n  \"severity\": \"high\",\n  \"blast_radius\": \"payments-api service experiencing high error rates (12.3%)\",\n  \"remediation\": \"Promote the standby PostgreSQL replica to primary and restart the payments-api deployment to clear connection pool caches\",\n  \"runbook\": \"\",\n  \"confidence\": \"high\"\n}\n",[36,3063,3064,3068,3080,3092,3104,3116,3128,3138],{"__ignoreMap":209},[213,3065,3066],{"class":215,"line":216},[213,3067,654],{"class":230},[213,3069,3070,3073,3075,3078],{"class":215,"line":223},[213,3071,3072],{"class":410},"  \"root_cause\"",[213,3074,251],{"class":230},[213,3076,3077],{"class":254},"\"PostgreSQL primary server failure leading to database connection issues and exhausted connection pool\"",[213,3079,667],{"class":230},[213,3081,3082,3085,3087,3090],{"class":215,"line":234},[213,3083,3084],{"class":410},"  \"severity\"",[213,3086,251],{"class":230},[213,3088,3089],{"class":254},"\"high\"",[213,3091,667],{"class":230},[213,3093,3094,3097,3099,3102],{"class":215,"line":242},[213,3095,3096],{"class":410},"  \"blast_radius\"",[213,3098,251],{"class":230},[213,3100,3101],{"class":254},"\"payments-api service experiencing high error rates (12.3%)\"",[213,3103,667],{"class":230},[213,3105,3106,3109,3111,3114],{"class":215,"line":258},[213,3107,3108],{"class":410},"  \"remediation\"",[213,3110,251],{"class":230},[213,3112,3113],{"class":254},"\"Promote the standby PostgreSQL replica to primary and restart the payments-api deployment to clear connection pool caches\"",[213,3115,667],{"class":230},[213,3117,3118,3121,3123,3126],{"class":215,"line":269},[213,3119,3120],{"class":410},"  \"runbook\"",[213,3122,251],{"class":230},[213,3124,3125],{"class":254},"\"\"",[213,3127,667],{"class":230},[213,3129,3130,3133,3135],{"class":215,"line":277},[213,3131,3132],{"class":410},"  \"confidence\"",[213,3134,251],{"class":230},[213,3136,3137],{"class":254},"\"high\"\n",[213,3139,3140],{"class":215,"line":289},[213,3141,710],{"class":230},[28,3143,3144,3145,3148],{},"The empty ",[36,3146,3147],{},"runbook"," field is correct - none was provided in the evidence, and I\nexplicitly told the model not to invent URLs. (It tried to on an earlier run.\nSmall models love making up links.)",[28,3150,3151,3153],{},[31,3152,1936],{}," - the mock Slack server logged this:",[204,3155,3157],{"className":838,"code":3156,"language":840,"meta":209,"style":209},"kubectl logs -n incident-response deploy\u002Fmock-slack\n",[36,3158,3159],{"__ignoreMap":209},[213,3160,3161,3163,3166,3168,3170],{"class":215,"line":216},[213,3162,848],{"class":847},[213,3164,3165],{"class":254}," logs",[213,3167,2046],{"class":410},[213,3169,2953],{"class":254},[213,3171,3172],{"class":254}," deploy\u002Fmock-slack\n",[204,3174,3177],{"className":3175,"code":3176,"language":2055},[2053],"========================================\nSLACK MESSAGE to #incidents\n========================================\n*Incident Summary*\n**Service:** payments-api\n**Root Cause:** PostgreSQL primary server failure leading to\ndatabase connection issues and exhausted connection pool\n**Severity:** High\n**Blast Radius:** payments-api service experiencing high error rates (12.3%)\n*Remediation Steps:*\n- Promote the standby PostgreSQL replica to primary\n- Restart the payments-api deployment\n- Clear connection pool caches\n- Monitor for any persistent issues\n========================================\n",[36,3178,3176],{"__ignoreMap":209},[28,3180,3181],{},"And the PagerDuty mock logged the acknowledgment. Both tools called, both\nactions completed.",[60,3183,3185],{"id":3184},"policy","Policy",[28,3187,3188],{},"Once this works, you want to make sure nobody deploys a rogue agent with\nshell access in this namespace:",[204,3190,3192],{"className":206,"code":3191,"language":208,"meta":209,"style":209},"# incident-policy.yaml\nspec:\n  enforcementMode: Enforce\n  limits:\n    maxDailyTokens: 200000\n    maxTokensPerCall: 3000\n    maxTimeoutSeconds: 600\n  tools:\n    deny:\n      - \"shell\u002F*\"\n      - \"filesystem\u002F*\"\n  models:\n    allowed:\n      - \"qwen*\"\n      - \"llama*\"\n",[36,3193,3194,3199,3205,3215,3222,3232,3241,3250,3256,3263,3270,3277,3284,3291,3298],{"__ignoreMap":209},[213,3195,3196],{"class":215,"line":216},[213,3197,3198],{"class":219},"# incident-policy.yaml\n",[213,3200,3201,3203],{"class":215,"line":223},[213,3202,227],{"class":226},[213,3204,231],{"class":230},[213,3206,3207,3210,3212],{"class":215,"line":234},[213,3208,3209],{"class":226},"  enforcementMode",[213,3211,251],{"class":230},[213,3213,3214],{"class":254},"Enforce\n",[213,3216,3217,3220],{"class":215,"line":242},[213,3218,3219],{"class":226},"  limits",[213,3221,231],{"class":230},[213,3223,3224,3227,3229],{"class":215,"line":258},[213,3225,3226],{"class":226},"    maxDailyTokens",[213,3228,251],{"class":230},[213,3230,3231],{"class":410},"200000\n",[213,3233,3234,3237,3239],{"class":215,"line":269},[213,3235,3236],{"class":226},"    maxTokensPerCall",[213,3238,251],{"class":230},[213,3240,2352],{"class":410},[213,3242,3243,3246,3248],{"class":215,"line":277},[213,3244,3245],{"class":226},"    maxTimeoutSeconds",[213,3247,251],{"class":230},[213,3249,1316],{"class":410},[213,3251,3252,3254],{"class":215,"line":289},[213,3253,2136],{"class":226},[213,3255,231],{"class":230},[213,3257,3258,3261],{"class":215,"line":295},[213,3259,3260],{"class":226},"    deny",[213,3262,231],{"class":230},[213,3264,3265,3267],{"class":215,"line":301},[213,3266,2150],{"class":230},[213,3268,3269],{"class":254},"\"shell\u002F*\"\n",[213,3271,3272,3274],{"class":215,"line":307},[213,3273,2150],{"class":230},[213,3275,3276],{"class":254},"\"filesystem\u002F*\"\n",[213,3278,3279,3282],{"class":215,"line":319},[213,3280,3281],{"class":226},"  models",[213,3283,231],{"class":230},[213,3285,3286,3289],{"class":215,"line":328},[213,3287,3288],{"class":226},"    allowed",[213,3290,231],{"class":230},[213,3292,3293,3295],{"class":215,"line":335},[213,3294,2150],{"class":230},[213,3296,3297],{"class":254},"\"qwen*\"\n",[213,3299,3300,3302],{"class":215,"line":345},[213,3301,2150],{"class":230},[213,3303,3304],{"class":254},"\"llama*\"\n",[68,3306,3307,3317],{},[71,3308,3309],{},[74,3310,3311,3314],{},[77,3312,3313],{},"Rule",[77,3315,3316],{},"Why",[90,3318,3319,3329,3339,3349],{},[74,3320,3321,3326],{},[95,3322,3323],{},[36,3324,3325],{},"tools.deny: shell\u002F*, filesystem\u002F*",[95,3327,3328],{},"Agents can use MCP tools but never execute commands or write files",[74,3330,3331,3336],{},[95,3332,3333],{},[36,3334,3335],{},"models.allowed: qwen*, llama*",[95,3337,3338],{},"Only approved local models - no surprise API bills",[74,3340,3341,3346],{},[95,3342,3343],{},[36,3344,3345],{},"maxDailyTokens: 200000",[95,3347,3348],{},"200K tokens\u002Fday ceiling - enough for ~20 incidents",[74,3350,3351,3356],{},[95,3352,3353],{},[36,3354,3355],{},"maxTokensPerCall: 3000",[95,3357,3358],{},"No single LLM call burns more than 3K tokens",[60,3360,3362],{"id":3361},"going-further","Going further",[28,3364,3365],{},"To connect real services, swap the mock URLs:",[204,3367,3369],{"className":206,"code":3368,"language":208,"meta":209,"style":209},"tools:\n  mcp:\n    - name: pagerduty\n      url: \"http:\u002F\u002Fpagerduty-mcp.incident-response.svc:8080\"\n      auth:\n        type: bearer\n        secretRef:\n          name: pagerduty-api-key\n",[36,3370,3371,3377,3383,3393,3402,3409,3419,3426],{"__ignoreMap":209},[213,3372,3373,3375],{"class":215,"line":216},[213,3374,1259],{"class":226},[213,3376,231],{"class":230},[213,3378,3379,3381],{"class":215,"line":223},[213,3380,1266],{"class":226},[213,3382,231],{"class":230},[213,3384,3385,3387,3389,3391],{"class":215,"line":234},[213,3386,245],{"class":230},[213,3388,248],{"class":226},[213,3390,251],{"class":230},[213,3392,2157],{"class":254},[213,3394,3395,3397,3399],{"class":215,"line":242},[213,3396,1284],{"class":226},[213,3398,251],{"class":230},[213,3400,3401],{"class":254},"\"http:\u002F\u002Fpagerduty-mcp.incident-response.svc:8080\"\n",[213,3403,3404,3407],{"class":215,"line":258},[213,3405,3406],{"class":226},"      auth",[213,3408,231],{"class":230},[213,3410,3411,3414,3416],{"class":215,"line":269},[213,3412,3413],{"class":226},"        type",[213,3415,251],{"class":230},[213,3417,3418],{"class":254},"bearer\n",[213,3420,3421,3424],{"class":215,"line":277},[213,3422,3423],{"class":226},"        secretRef",[213,3425,231],{"class":230},[213,3427,3428,3431,3433],{"class":215,"line":289},[213,3429,3430],{"class":226},"          name",[213,3432,251],{"class":230},[213,3434,3435],{"class":254},"pagerduty-api-key\n",[28,3437,3438,3439,3442],{},"To trigger automatically from PagerDuty webhooks instead of ",[36,3440,3441],{},"kubectl apply",",\nuse a SwarmEvent:",[204,3444,3446],{"className":206,"code":3445,"language":208,"meta":209,"style":209},"spec:\n  source:\n    type: webhook\n  targets:\n    - team: incident-responder\n      inputs:\n        alert: \"{{ .trigger.body.incident.title }}: {{ .trigger.body.incident.description }}\"\n  concurrencyPolicy: Allow\n",[36,3447,3448,3454,3461,3471,3478,3489,3495,3504],{"__ignoreMap":209},[213,3449,3450,3452],{"class":215,"line":216},[213,3451,227],{"class":226},[213,3453,231],{"class":230},[213,3455,3456,3459],{"class":215,"line":223},[213,3457,3458],{"class":226},"  source",[213,3460,231],{"class":230},[213,3462,3463,3466,3468],{"class":215,"line":234},[213,3464,3465],{"class":226},"    type",[213,3467,251],{"class":230},[213,3469,3470],{"class":254},"webhook\n",[213,3472,3473,3476],{"class":215,"line":242},[213,3474,3475],{"class":226},"  targets",[213,3477,231],{"class":230},[213,3479,3480,3482,3485,3487],{"class":215,"line":258},[213,3481,245],{"class":230},[213,3483,3484],{"class":226},"team",[213,3486,251],{"class":230},[213,3488,2865],{"class":254},[213,3490,3491,3493],{"class":215,"line":269},[213,3492,2708],{"class":226},[213,3494,231],{"class":230},[213,3496,3497,3499,3501],{"class":215,"line":277},[213,3498,2715],{"class":226},[213,3500,251],{"class":230},[213,3502,3503],{"class":254},"\"{{ .trigger.body.incident.title }}: {{ .trigger.body.incident.description }}\"\n",[213,3505,3506,3509,3511],{"class":215,"line":289},[213,3507,3508],{"class":226},"  concurrencyPolicy",[213,3510,251],{"class":230},[213,3512,3513],{"class":254},"Allow\n",[28,3515,3516],{},"The operator generates a webhook URL. Point PagerDuty at it and every incident\nfires the pipeline.",[60,3518,3520],{"id":3519},"numbers","Numbers",[68,3522,3523,3532],{},[71,3524,3525],{},[74,3526,3527,3529],{},[77,3528,1359],{},[77,3530,3531],{},"Value",[90,3533,3534,3544,3554,3564,3574,3584,3594],{},[74,3535,3536,3541],{},[95,3537,3538],{},[31,3539,3540],{},"Agents",[95,3542,3543],{},"3 (investigator + diagnostician + notifier)",[74,3545,3546,3551],{},[95,3547,3548],{},[31,3549,3550],{},"Model",[95,3552,3553],{},"qwen2.5:7b (any model works)",[74,3555,3556,3561],{},[95,3557,3558],{},[31,3559,3560],{},"Time per incident",[95,3562,3563],{},"~2.5 minutes",[74,3565,3566,3571],{},[95,3567,3568],{},[31,3569,3570],{},"Tokens per incident",[95,3572,3573],{},"~10,000",[74,3575,3576,3581],{},[95,3577,3578],{},[31,3579,3580],{},"Cost per incident",[95,3582,3583],{},"$0.00 (local model)",[74,3585,3586,3591],{},[95,3587,3588],{},[31,3589,3590],{},"Tools",[95,3592,3593],{},"PagerDuty, Grafana, Slack via MCP",[74,3595,3596,3601],{},[95,3597,3598],{},[31,3599,3600],{},"Guardrails",[95,3602,3603],{},"Per-agent tool allowlists, namespace policy, token budgets",[28,3605,3606],{},"The 2.5 minutes is mostly Ollama thinking on my laptop. With a faster model\nor GPU, this would be well under a minute.",[60,3608,3610],{"id":3609},"cleanup","Cleanup",[204,3612,3614],{"className":838,"code":3613,"language":840,"meta":209,"style":209},"kubectl delete namespace incident-response\n",[36,3615,3616],{"__ignoreMap":209},[213,3617,3618,3620,3623,3626],{"class":215,"line":216},[213,3619,848],{"class":847},[213,3621,3622],{"class":254}," delete",[213,3624,3625],{"class":254}," namespace",[213,3627,2049],{"class":254},[28,3629,3630,3631,3635,3636,3641],{},"All the files are in the\n",[1131,3632,3634],{"href":1816,"rel":3633},[1135],"cookbook",".\nThe ",[1131,3637,3640],{"href":3638,"rel":3639},"https:\u002F\u002Fdocs.kubeswarm.io",[1135],"docs"," have more on MCP integration and SwarmEvents.",[45,3643],{},[28,3645,3646],{},[3647,3648,3649,3650,3654,3655,3654,3659],"em",{},"kubeswarm is an open-source Kubernetes operator for managing AI agents.\n",[1131,3651,3653],{"href":3638,"rel":3652},[1135],"Docs"," |\n",[1131,3656,3658],{"href":1133,"rel":3657},[1135],"Cookbook",[1131,3660,3663],{"href":3661,"rel":3662},"https:\u002F\u002Fgithub.com\u002Fkubeswarm\u002Fkubeswarm",[1135],"GitHub",[1174,3665,3666],{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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 .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}",{"title":209,"searchDepth":223,"depth":223,"links":3668},[3669,3670,3671,3672,3673,3674,3675,3676,3677,3678],{"id":1873,"depth":223,"text":1874},{"id":1951,"depth":223,"text":1952},{"id":1980,"depth":223,"text":1981},{"id":2068,"depth":223,"text":2069},{"id":2599,"depth":223,"text":2600},{"id":2835,"depth":223,"text":2836},{"id":3184,"depth":223,"text":3185},{"id":3361,"depth":223,"text":3362},{"id":3519,"depth":223,"text":3520},{"id":3609,"depth":223,"text":3610},"tutorial","2026-04-25","Three AI agents investigate a PagerDuty alert, diagnose the root cause from Grafana logs, and post findings to Slack - all on a local cluster.",{},"\u002Fblog\u002Fincident-response",{"title":1804,"description":3681},"blog\u002Fincident-response",[3687,3688,1800],"incident-response","multi-agent","kwhHJ9YejmpAWobp2aIReM6geYjPitZAV4Ihk4F_KK0",{"id":3691,"title":3692,"author":19,"authorUrl":20,"body":3693,"category":3679,"date":5012,"description":5013,"extension":1190,"image":209,"meta":5014,"navigation":348,"path":5015,"seo":5016,"series":1194,"stem":5017,"tags":5018,"__hash__":5020},"blog\u002Fblog\u002Fsupport-triage.md","Run AI agents on Kubernetes: a support triage pipeline with budget guardrails",{"type":22,"value":3694,"toc":5002},[3695,3710,3712,3715,3718,3721,3727,3730,3824,3826,3834,3838,3843,3892,3897,4003,4006,4011,4088,4100,4139,4145,4148,4152,4158,4297,4313,4317,4323,4410,4443,4449,4454,4500,4505,4533,4539,4543,4548,4568,4575,4674,4726,4731,4737,4744,4748,4754,4802,4808,4848,4855,4858,4862,4938,4944,4946,4961,4964,4984,4986,4999],[25,3696,3697],{},[28,3698,3699,3701,3702,3705,3706,1559],{},[31,3700,33],{}," - Two AI agents classify support tickets and draft replies on Kubernetes.\nRuns on a local 7B model. Total cost per ticket: ",[31,3703,3704],{},"1,612 tokens, $0.00","\n(compute costs for self-hosted inference still apply).\nA namespace policy blocks unauthorized models and tools at admission time.\nAll YAML files are in the ",[1131,3707,1818],{"href":3708,"rel":3709},"https:\u002F\u002Fgithub.com\u002Fkubeswarm\u002Fkubeswarm-cookbook\u002Ftree\u002Fmain\u002Frecipes\u002F15-support-triage",[1135],[45,3711],{},[28,3713,3714],{},"You already have ticket automation. Zendesk triggers, Intercom bots, maybe a\ncustom rule engine your team built three years ago. It matches keywords, routes\nto queues, fires templates.",[28,3716,3717],{},"And it gets things wrong constantly.",[28,3719,3720],{},"A customer writes \"I can't access my dashboard, we have a client demo in 2 hours\"\nand your automation routes it to the billing queue because the word \"account\"\nappeared somewhere in the thread. The template replies with \"we'll look into it\nwithin 24 hours.\" The customer's demo fails.",[28,3722,3723,3726],{},[31,3724,3725],{},"The problem isn't that you lack automation. It's that your automation can't read.","\nIt pattern-matches keywords. It doesn't understand that \"demo in 2 hours\" means\nthis is critical, or that \"blank white screen after login\" is a bug, not a billing issue.",[28,3728,3729],{},"An LLM agent actually reads the ticket. It understands context, urgency, and intent.\nBut running LLM agents in production creates new problems: cost control, model\ngovernance, audit trails, tool restrictions. That's the gap kubeswarm fills.",[68,3731,3732,3744],{},[71,3733,3734],{},[74,3735,3736,3738,3741],{},[77,3737],{},[77,3739,3740],{},"Rule-based automation",[77,3742,3743],{},"kubeswarm pipeline",[90,3745,3746,3759,3772,3785,3798,3811],{},[74,3747,3748,3753,3756],{},[95,3749,3750],{},[31,3751,3752],{},"Classification",[95,3754,3755],{},"Keyword matching",[95,3757,3758],{},"Reads and understands context",[74,3760,3761,3766,3769],{},[95,3762,3763],{},[31,3764,3765],{},"Priority",[95,3767,3768],{},"Static rules",[95,3770,3771],{},"Infers urgency from content",[74,3773,3774,3779,3782],{},[95,3775,3776],{},[31,3777,3778],{},"Responses",[95,3780,3781],{},"Templates",[95,3783,3784],{},"Contextual, specific to the issue",[74,3786,3787,3792,3795],{},[95,3788,3789],{},[31,3790,3791],{},"Changing logic",[95,3793,3794],{},"Edit flowcharts in admin UI",[95,3796,3797],{},"Change the prompt",[74,3799,3800,3805,3808],{},[95,3801,3802],{},[31,3803,3804],{},"Cost control",[95,3806,3807],{},"N\u002FA",[95,3809,3810],{},"Token budgets enforced per namespace",[74,3812,3813,3818,3821],{},[95,3814,3815],{},[31,3816,3817],{},"Audit",[95,3819,3820],{},"Logs which rule fired",[95,3822,3823],{},"Tracks every token, every agent, every run",[60,3825,1952],{"id":1951},[28,3827,3828,3829,3833],{},"You need a Kubernetes cluster with kubeswarm installed. See the\n",[1131,3830,3832],{"href":1958,"rel":3831},[1135],"installation guide"," for setup.",[60,3835,3837],{"id":3836},"two-agents-one-pipeline-zero-api-cost","Two agents, one pipeline, zero API cost",[28,3839,3840],{},[31,3841,3842],{},"First, create the namespace and a Secret with your LLM endpoint:",[204,3844,3846],{"className":838,"code":3845,"language":840,"meta":209,"style":209},"kubectl create namespace support-triage\nkubectl create secret generic ollama-credentials \\\n  -n support-triage \\\n  --from-literal=LLM_ENDPOINT=http:\u002F\u002Follama.ollama.svc:11434\n",[36,3847,3848,3860,3877,3887],{"__ignoreMap":209},[213,3849,3850,3852,3855,3857],{"class":215,"line":216},[213,3851,848],{"class":847},[213,3853,3854],{"class":254}," create",[213,3856,3625],{"class":254},[213,3858,3859],{"class":254}," support-triage\n",[213,3861,3862,3864,3866,3869,3872,3875],{"class":215,"line":223},[213,3863,848],{"class":847},[213,3865,3854],{"class":254},[213,3867,3868],{"class":254}," secret",[213,3870,3871],{"class":254}," generic",[213,3873,3874],{"class":254}," ollama-credentials",[213,3876,869],{"class":410},[213,3878,3879,3882,3885],{"class":215,"line":234},[213,3880,3881],{"class":410},"  -n",[213,3883,3884],{"class":254}," support-triage",[213,3886,869],{"class":410},[213,3888,3889],{"class":215,"line":242},[213,3890,3891],{"class":410},"  --from-literal=LLM_ENDPOINT=http:\u002F\u002Follama.ollama.svc:11434\n",[28,3893,3894],{},[31,3895,3896],{},"The classifier reads the ticket and outputs a structured JSON with category, priority, and summary.",[204,3898,3900],{"className":206,"code":3899,"language":208,"meta":209,"style":209},"# classifier-agent.yaml\nspec:\n  model: qwen2.5:7b\n  prompt:\n    inline: |\n      You are a support ticket classifier. For every ticket you receive,\n      respond with ONLY a JSON object:\n\n      {\n        \"category\": \"\u003Cbilling|technical|bug|feature-request>\",\n        \"priority\": \"\u003Clow|medium|high|critical>\",\n        \"summary\": \"\u003Cone sentence summary>\"\n      }\n  guardrails:\n    limits:\n      tokensPerCall: 500\n      timeoutSeconds: 30\n",[36,3901,3902,3907,3913,3921,3927,3935,3940,3945,3949,3954,3959,3964,3969,3974,3980,3986,3995],{"__ignoreMap":209},[213,3903,3904],{"class":215,"line":216},[213,3905,3906],{"class":219},"# classifier-agent.yaml\n",[213,3908,3909,3911],{"class":215,"line":223},[213,3910,227],{"class":226},[213,3912,231],{"class":230},[213,3914,3915,3917,3919],{"class":215,"line":234},[213,3916,2095],{"class":226},[213,3918,251],{"class":230},[213,3920,2100],{"class":254},[213,3922,3923,3925],{"class":215,"line":242},[213,3924,2105],{"class":226},[213,3926,231],{"class":230},[213,3928,3929,3931,3933],{"class":215,"line":258},[213,3930,2112],{"class":226},[213,3932,251],{"class":230},[213,3934,286],{"class":285},[213,3936,3937],{"class":215,"line":269},[213,3938,3939],{"class":254},"      You are a support ticket classifier. For every ticket you receive,\n",[213,3941,3942],{"class":215,"line":277},[213,3943,3944],{"class":254},"      respond with ONLY a JSON object:\n",[213,3946,3947],{"class":215,"line":289},[213,3948,349],{"emptyLinePlaceholder":348},[213,3950,3951],{"class":215,"line":295},[213,3952,3953],{"class":254},"      {\n",[213,3955,3956],{"class":215,"line":301},[213,3957,3958],{"class":254},"        \"category\": \"\u003Cbilling|technical|bug|feature-request>\",\n",[213,3960,3961],{"class":215,"line":307},[213,3962,3963],{"class":254},"        \"priority\": \"\u003Clow|medium|high|critical>\",\n",[213,3965,3966],{"class":215,"line":319},[213,3967,3968],{"class":254},"        \"summary\": \"\u003Cone sentence summary>\"\n",[213,3970,3971],{"class":215,"line":328},[213,3972,3973],{"class":254},"      }\n",[213,3975,3976,3978],{"class":215,"line":335},[213,3977,2192],{"class":226},[213,3979,231],{"class":230},[213,3981,3982,3984],{"class":215,"line":345},[213,3983,2199],{"class":226},[213,3985,231],{"class":230},[213,3987,3988,3990,3992],{"class":215,"line":352},[213,3989,2206],{"class":226},[213,3991,251],{"class":230},[213,3993,3994],{"class":410},"500\n",[213,3996,3997,3999,4001],{"class":215,"line":360},[213,3998,2216],{"class":226},[213,4000,251],{"class":230},[213,4002,617],{"class":410},[28,4004,4005],{},"500 tokens. 30 second timeout. That's all a classifier needs.",[28,4007,4008],{},[31,4009,4010],{},"The responder takes that classification and drafts an empathetic customer reply.",[204,4012,4014],{"className":206,"code":4013,"language":208,"meta":209,"style":209},"# responder-agent.yaml\nspec:\n  model: qwen2.5:7b\n  prompt:\n    inline: |\n      You are a customer support agent. You receive a ticket with its\n      classification. Write a helpful, empathetic reply under 150 words.\n  guardrails:\n    limits:\n      tokensPerCall: 2000\n      timeoutSeconds: 60\n",[36,4015,4016,4021,4027,4035,4041,4049,4054,4059,4065,4071,4080],{"__ignoreMap":209},[213,4017,4018],{"class":215,"line":216},[213,4019,4020],{"class":219},"# responder-agent.yaml\n",[213,4022,4023,4025],{"class":215,"line":223},[213,4024,227],{"class":226},[213,4026,231],{"class":230},[213,4028,4029,4031,4033],{"class":215,"line":234},[213,4030,2095],{"class":226},[213,4032,251],{"class":230},[213,4034,2100],{"class":254},[213,4036,4037,4039],{"class":215,"line":242},[213,4038,2105],{"class":226},[213,4040,231],{"class":230},[213,4042,4043,4045,4047],{"class":215,"line":258},[213,4044,2112],{"class":226},[213,4046,251],{"class":230},[213,4048,286],{"class":285},[213,4050,4051],{"class":215,"line":269},[213,4052,4053],{"class":254},"      You are a customer support agent. You receive a ticket with its\n",[213,4055,4056],{"class":215,"line":277},[213,4057,4058],{"class":254},"      classification. Write a helpful, empathetic reply under 150 words.\n",[213,4060,4061,4063],{"class":215,"line":289},[213,4062,2192],{"class":226},[213,4064,231],{"class":230},[213,4066,4067,4069],{"class":215,"line":295},[213,4068,2199],{"class":226},[213,4070,231],{"class":230},[213,4072,4073,4075,4077],{"class":215,"line":301},[213,4074,2206],{"class":226},[213,4076,251],{"class":230},[213,4078,4079],{"class":410},"2000\n",[213,4081,4082,4084,4086],{"class":215,"line":307},[213,4083,2216],{"class":226},[213,4085,251],{"class":230},[213,4087,1725],{"class":410},[28,4089,4090,4091,4094,4095,4099],{},"Both agents reference the Secret you created earlier via ",[36,4092,4093],{},"infrastructure.envFrom"," -\nsee the ",[1131,4096,4098],{"href":3708,"rel":4097},[1135],"full YAML files"," for the complete setup.",[204,4101,4103],{"className":838,"code":4102,"language":840,"meta":209,"style":209},"kubectl apply -f classifier-agent.yaml\nkubectl apply -f responder-agent.yaml\nkubectl get swarmagents -n support-triage\n",[36,4104,4105,4116,4127],{"__ignoreMap":209},[213,4106,4107,4109,4111,4113],{"class":215,"line":216},[213,4108,848],{"class":847},[213,4110,1149],{"class":254},[213,4112,1152],{"class":410},[213,4114,4115],{"class":254}," classifier-agent.yaml\n",[213,4117,4118,4120,4122,4124],{"class":215,"line":223},[213,4119,848],{"class":847},[213,4121,1149],{"class":254},[213,4123,1152],{"class":410},[213,4125,4126],{"class":254}," responder-agent.yaml\n",[213,4128,4129,4131,4133,4135,4137],{"class":215,"line":234},[213,4130,848],{"class":847},[213,4132,851],{"class":254},[213,4134,2586],{"class":254},[213,4136,2046],{"class":410},[213,4138,3859],{"class":254},[204,4140,4143],{"className":4141,"code":4142,"language":2055},[2053],"NAME                MODEL        REPLICAS   READY   AGE\nticket-classifier   qwen2.5:7b   1          1       5s\nticket-responder    qwen2.5:7b   1          1       5s\n",[36,4144,4142],{"__ignoreMap":209},[28,4146,4147],{},"Two agents. Ready in seconds.",[60,4149,4151],{"id":4150},"building-a-multi-agent-pipeline-with-swarmteam","Building a multi-agent pipeline with SwarmTeam",[28,4153,4154,4157],{},[31,4155,4156],{},"Agents alone are just pods. A SwarmTeam wires them into a pipeline"," where\none agent's output feeds into the next.",[204,4159,4161],{"className":206,"code":4160,"language":208,"meta":209,"style":209},"# support-team.yaml\nspec:\n  roles:\n    - name: classifier\n      swarmAgent: ticket-classifier\n    - name: responder\n      swarmAgent: ticket-responder\n  pipeline:\n    - role: classifier\n      inputs:\n        ticket: \"{{ .input.ticket }}\"\n    - role: responder\n      dependsOn: [classifier]\n      inputs:\n        classification: \"{{ .steps.classifier.output }}\"\n        ticket: \"{{ .input.ticket }}\"\n",[36,4162,4163,4168,4174,4180,4191,4200,4211,4220,4226,4236,4242,4252,4262,4273,4279,4289],{"__ignoreMap":209},[213,4164,4165],{"class":215,"line":216},[213,4166,4167],{"class":219},"# support-team.yaml\n",[213,4169,4170,4172],{"class":215,"line":223},[213,4171,227],{"class":226},[213,4173,231],{"class":230},[213,4175,4176,4178],{"class":215,"line":234},[213,4177,237],{"class":226},[213,4179,231],{"class":230},[213,4181,4182,4184,4186,4188],{"class":215,"line":242},[213,4183,245],{"class":230},[213,4185,248],{"class":226},[213,4187,251],{"class":230},[213,4189,4190],{"class":254},"classifier\n",[213,4192,4193,4195,4197],{"class":215,"line":258},[213,4194,2640],{"class":226},[213,4196,251],{"class":230},[213,4198,4199],{"class":254},"ticket-classifier\n",[213,4201,4202,4204,4206,4208],{"class":215,"line":269},[213,4203,245],{"class":230},[213,4205,248],{"class":226},[213,4207,251],{"class":230},[213,4209,4210],{"class":254},"responder\n",[213,4212,4213,4215,4217],{"class":215,"line":277},[213,4214,2640],{"class":226},[213,4216,251],{"class":230},[213,4218,4219],{"class":254},"ticket-responder\n",[213,4221,4222,4224],{"class":215,"line":289},[213,4223,2690],{"class":226},[213,4225,231],{"class":230},[213,4227,4228,4230,4232,4234],{"class":215,"line":295},[213,4229,245],{"class":230},[213,4231,2699],{"class":226},[213,4233,251],{"class":230},[213,4235,4190],{"class":254},[213,4237,4238,4240],{"class":215,"line":301},[213,4239,2708],{"class":226},[213,4241,231],{"class":230},[213,4243,4244,4247,4249],{"class":215,"line":307},[213,4245,4246],{"class":226},"        ticket",[213,4248,251],{"class":230},[213,4250,4251],{"class":254},"\"{{ .input.ticket }}\"\n",[213,4253,4254,4256,4258,4260],{"class":215,"line":319},[213,4255,245],{"class":230},[213,4257,2699],{"class":226},[213,4259,251],{"class":230},[213,4261,4210],{"class":254},[213,4263,4264,4266,4268,4271],{"class":215,"line":328},[213,4265,2735],{"class":226},[213,4267,2738],{"class":230},[213,4269,4270],{"class":254},"classifier",[213,4272,2744],{"class":230},[213,4274,4275,4277],{"class":215,"line":335},[213,4276,2708],{"class":226},[213,4278,231],{"class":230},[213,4280,4281,4284,4286],{"class":215,"line":345},[213,4282,4283],{"class":226},"        classification",[213,4285,251],{"class":230},[213,4287,4288],{"class":254},"\"{{ .steps.classifier.output }}\"\n",[213,4290,4291,4293,4295],{"class":215,"line":352},[213,4292,4246],{"class":226},[213,4294,251],{"class":230},[213,4296,4251],{"class":254},[25,4298,4299],{},[28,4300,4301,4304,4305,4308,4309,4312],{},[31,4302,4303],{},"Think of it like a Makefile target with dependencies."," The responder declares\n",[36,4306,4307],{},"dependsOn: [classifier]",", so it waits for the classifier to finish. The\n",[36,4310,4311],{},"{{ .steps.classifier.output }}"," template is how data flows between steps.",[60,4314,4316],{"id":4315},"running-a-real-support-ticket-through-the-pipeline","Running a real support ticket through the pipeline",[28,4318,4319,4322],{},[31,4320,4321],{},"Here's a real ticket."," Sarah's team can't access their dashboard, client demo in two hours.",[204,4324,4326],{"className":206,"code":4325,"language":208,"meta":209,"style":209},"# sample-run.yaml\nspec:\n  teamRef: support-triage\n  input:\n    ticket: |\n      Subject: Can't access my dashboard since this morning\n\n      I've been using your platform for 3 months and everything was fine\n      until today. When I try to log in, I get a blank white screen after\n      entering my credentials. Tried Chrome, Firefox, incognito. Nothing.\n\n      This is blocking my entire team - we have a demo with a client\n      in 2 hours and need access to our analytics dashboard urgently.\n\n      Sarah Chen, Acme Corp - Enterprise Plan\n",[36,4327,4328,4333,4339,4348,4354,4363,4368,4372,4377,4382,4387,4391,4396,4401,4405],{"__ignoreMap":209},[213,4329,4330],{"class":215,"line":216},[213,4331,4332],{"class":219},"# sample-run.yaml\n",[213,4334,4335,4337],{"class":215,"line":223},[213,4336,227],{"class":226},[213,4338,231],{"class":230},[213,4340,4341,4343,4345],{"class":215,"line":234},[213,4342,2860],{"class":226},[213,4344,251],{"class":230},[213,4346,4347],{"class":254},"support-triage\n",[213,4349,4350,4352],{"class":215,"line":242},[213,4351,2870],{"class":226},[213,4353,231],{"class":230},[213,4355,4356,4359,4361],{"class":215,"line":258},[213,4357,4358],{"class":226},"    ticket",[213,4360,251],{"class":230},[213,4362,286],{"class":285},[213,4364,4365],{"class":215,"line":269},[213,4366,4367],{"class":254},"      Subject: Can't access my dashboard since this morning\n",[213,4369,4370],{"class":215,"line":277},[213,4371,349],{"emptyLinePlaceholder":348},[213,4373,4374],{"class":215,"line":289},[213,4375,4376],{"class":254},"      I've been using your platform for 3 months and everything was fine\n",[213,4378,4379],{"class":215,"line":295},[213,4380,4381],{"class":254},"      until today. When I try to log in, I get a blank white screen after\n",[213,4383,4384],{"class":215,"line":301},[213,4385,4386],{"class":254},"      entering my credentials. Tried Chrome, Firefox, incognito. Nothing.\n",[213,4388,4389],{"class":215,"line":307},[213,4390,349],{"emptyLinePlaceholder":348},[213,4392,4393],{"class":215,"line":319},[213,4394,4395],{"class":254},"      This is blocking my entire team - we have a demo with a client\n",[213,4397,4398],{"class":215,"line":328},[213,4399,4400],{"class":254},"      in 2 hours and need access to our analytics dashboard urgently.\n",[213,4402,4403],{"class":215,"line":335},[213,4404,349],{"emptyLinePlaceholder":348},[213,4406,4407],{"class":215,"line":345},[213,4408,4409],{"class":254},"      Sarah Chen, Acme Corp - Enterprise Plan\n",[204,4411,4413],{"className":838,"code":4412,"language":840,"meta":209,"style":209},"kubectl apply -f sample-run.yaml\nkubectl get swarmrun ticket-001 -n support-triage -w\n",[36,4414,4415,4426],{"__ignoreMap":209},[213,4416,4417,4419,4421,4423],{"class":215,"line":216},[213,4418,848],{"class":847},[213,4420,1149],{"class":254},[213,4422,1152],{"class":410},[213,4424,4425],{"class":254}," sample-run.yaml\n",[213,4427,4428,4430,4432,4434,4437,4439,4441],{"class":215,"line":223},[213,4429,848],{"class":847},[213,4431,851],{"class":254},[213,4433,2945],{"class":254},[213,4435,4436],{"class":254}," ticket-001",[213,4438,2046],{"class":410},[213,4440,3884],{"class":254},[213,4442,1169],{"class":410},[204,4444,4447],{"className":4445,"code":4446,"language":2055},[2053],"NAME         PHASE      AGE\nticket-001   Pending    0s\nticket-001   Running    2s\nticket-001   Succeeded  20s\n",[36,4448,4446],{"__ignoreMap":209},[28,4450,4451],{},[31,4452,4453],{},"The classifier returns:",[204,4455,4457],{"className":645,"code":4456,"language":647,"meta":209,"style":209},"{\n  \"category\": \"technical\",\n  \"priority\": \"high\",\n  \"summary\": \"Blank white screen on dashboard login from Chrome and Firefox\"\n}\n",[36,4458,4459,4463,4475,4486,4496],{"__ignoreMap":209},[213,4460,4461],{"class":215,"line":216},[213,4462,654],{"class":230},[213,4464,4465,4468,4470,4473],{"class":215,"line":223},[213,4466,4467],{"class":410},"  \"category\"",[213,4469,251],{"class":230},[213,4471,4472],{"class":254},"\"technical\"",[213,4474,667],{"class":230},[213,4476,4477,4480,4482,4484],{"class":215,"line":234},[213,4478,4479],{"class":410},"  \"priority\"",[213,4481,251],{"class":230},[213,4483,3089],{"class":254},[213,4485,667],{"class":230},[213,4487,4488,4491,4493],{"class":215,"line":242},[213,4489,4490],{"class":410},"  \"summary\"",[213,4492,251],{"class":230},[213,4494,4495],{"class":254},"\"Blank white screen on dashboard login from Chrome and Firefox\"\n",[213,4497,4498],{"class":215,"line":258},[213,4499,710],{"class":230},[28,4501,4502],{},[31,4503,4504],{},"The responder drafts:",[25,4506,4507,4510,4513,4516,4527,4530],{},[28,4508,4509],{},"Hi Sarah,",[28,4511,4512],{},"Thank you for reaching out and I'm sorry to hear about the issue on your\ndashboard. We've marked this as a high-priority task.",[28,4514,4515],{},"Could you please provide us with:",[164,4517,4518,4521,4524],{},[167,4519,4520],{},"Your exact version of Chrome\u002FFirefox",[167,4522,4523],{},"Any error messages shown (if any)",[167,4525,4526],{},"A screenshot if possible",[28,4528,4529],{},"This will help us diagnose the problem faster. Rest assured, we are\nescalating this and will keep you updated.",[28,4531,4532],{},"Support Team",[28,4534,4535,4536],{},"Category: correct. Priority: correct. Response: empathetic and actionable.\n",[31,4537,4538],{},"From a 7B model running locally. Total cost: 1,612 tokens, $0.00.",[60,4540,4542],{"id":4541},"ai-agent-guardrails-budget-limits-and-model-policies-on-kubernetes","AI agent guardrails: budget limits and model policies on Kubernetes",[28,4544,4545],{},[31,4546,4547],{},"Your triage pipeline works. You deploy it for the team. Then three things happen:",[1527,4549,4550,4556,4562],{},[167,4551,4552,4553],{},"Someone deploys a \"helper\" agent using GPT-4 that burns through ",[31,4554,4555],{},"$200 in a day",[167,4557,4558,4559],{},"Another agent gets access to a shell tool and starts ",[31,4560,4561],{},"executing commands from ticket text",[167,4563,4564,4565],{},"Nobody knows which agent processed which ticket, or ",[31,4566,4567],{},"how many tokens it cost",[28,4569,4570,4571,4574],{},"This is where kubeswarm is different from a Python script. ",[31,4572,4573],{},"A SwarmPolicy enforces\nrules at the namespace level"," - before the pod even starts:",[204,4576,4578],{"className":206,"code":4577,"language":208,"meta":209,"style":209},"# budget-policy.yaml\nspec:\n  enforcementMode: Enforce\n  limits:\n    maxDailyTokens: 500000\n    maxTokensPerCall: 2000\n    maxTimeoutSeconds: 120\n  tools:\n    deny:\n      - \"*\"\n  models:\n    allowed:\n      - \"qwen*\"\n      - \"gpt-oss-*\"\n",[36,4579,4580,4585,4591,4599,4605,4614,4622,4630,4636,4642,4649,4655,4661,4667],{"__ignoreMap":209},[213,4581,4582],{"class":215,"line":216},[213,4583,4584],{"class":219},"# budget-policy.yaml\n",[213,4586,4587,4589],{"class":215,"line":223},[213,4588,227],{"class":226},[213,4590,231],{"class":230},[213,4592,4593,4595,4597],{"class":215,"line":234},[213,4594,3209],{"class":226},[213,4596,251],{"class":230},[213,4598,3214],{"class":254},[213,4600,4601,4603],{"class":215,"line":242},[213,4602,3219],{"class":226},[213,4604,231],{"class":230},[213,4606,4607,4609,4611],{"class":215,"line":258},[213,4608,3226],{"class":226},[213,4610,251],{"class":230},[213,4612,4613],{"class":410},"500000\n",[213,4615,4616,4618,4620],{"class":215,"line":269},[213,4617,3236],{"class":226},[213,4619,251],{"class":230},[213,4621,4079],{"class":410},[213,4623,4624,4626,4628],{"class":215,"line":277},[213,4625,3245],{"class":226},[213,4627,251],{"class":230},[213,4629,2361],{"class":410},[213,4631,4632,4634],{"class":215,"line":289},[213,4633,2136],{"class":226},[213,4635,231],{"class":230},[213,4637,4638,4640],{"class":215,"line":295},[213,4639,3260],{"class":226},[213,4641,231],{"class":230},[213,4643,4644,4646],{"class":215,"line":301},[213,4645,2150],{"class":230},[213,4647,4648],{"class":254},"\"*\"\n",[213,4650,4651,4653],{"class":215,"line":307},[213,4652,3281],{"class":226},[213,4654,231],{"class":230},[213,4656,4657,4659],{"class":215,"line":319},[213,4658,3288],{"class":226},[213,4660,231],{"class":230},[213,4662,4663,4665],{"class":215,"line":328},[213,4664,2150],{"class":230},[213,4666,3297],{"class":254},[213,4668,4669,4671],{"class":215,"line":335},[213,4670,2150],{"class":230},[213,4672,4673],{"class":254},"\"gpt-oss-*\"\n",[68,4675,4676,4684],{},[71,4677,4678],{},[74,4679,4680,4682],{},[77,4681,3313],{},[77,4683,1895],{},[90,4685,4686,4696,4706,4716],{},[74,4687,4688,4693],{},[95,4689,4690],{},[36,4691,4692],{},"maxDailyTokens: 500000",[95,4694,4695],{},"Hard ceiling on daily spend per namespace",[74,4697,4698,4703],{},[95,4699,4700],{},[36,4701,4702],{},"maxTokensPerCall: 2000",[95,4704,4705],{},"No single call burns more than 2K tokens",[74,4707,4708,4713],{},[95,4709,4710],{},[36,4711,4712],{},"tools.deny: [\"*\"]",[95,4714,4715],{},"No tools at all - these agents read and write text, nothing else",[74,4717,4718,4723],{},[95,4719,4720],{},[36,4721,4722],{},"models.allowed",[95,4724,4725],{},"Only approved models can be deployed",[28,4727,4728],{},[31,4729,4730],{},"Try to sneak in a different model:",[204,4732,4735],{"className":4733,"code":4734,"language":2055},[2053],"Error from server (Forbidden): model \"gpt-4o\" is not allowed\nby SwarmPolicy \"support-budget\" (allowed: qwen*, gpt-oss-*)\n",[36,4736,4734],{"__ignoreMap":209},[28,4738,4739,4740,4743],{},"Rejected at the API level. Not at runtime. Not after it already spent your money.\n",[31,4741,4742],{},"At admission time."," The pod never starts.",[60,4745,4747],{"id":4746},"connecting-agents-to-jira-zendesk-or-slack-via-mcp","Connecting agents to Jira, Zendesk, or Slack via MCP",[28,4749,4750,4753],{},[31,4751,4752],{},"In production the agent should create the ticket itself, not just draft a reply.","\nThat's what MCP tools are for. Add an MCP server to the responder agent:",[204,4755,4757],{"className":206,"code":4756,"language":208,"meta":209,"style":209},"# give the responder access to your ticketing system\nspec:\n  tools:\n    mcp:\n      - name: jira\n        url: \"http:\u002F\u002Fjira-mcp-server.support-triage.svc:8080\u002Fsse\"\n",[36,4758,4759,4764,4770,4776,4782,4793],{"__ignoreMap":209},[213,4760,4761],{"class":215,"line":216},[213,4762,4763],{"class":219},"# give the responder access to your ticketing system\n",[213,4765,4766,4768],{"class":215,"line":223},[213,4767,227],{"class":226},[213,4769,231],{"class":230},[213,4771,4772,4774],{"class":215,"line":234},[213,4773,2136],{"class":226},[213,4775,231],{"class":230},[213,4777,4778,4780],{"class":215,"line":242},[213,4779,2143],{"class":226},[213,4781,231],{"class":230},[213,4783,4784,4786,4788,4790],{"class":215,"line":258},[213,4785,2150],{"class":230},[213,4787,248],{"class":226},[213,4789,251],{"class":230},[213,4791,4792],{"class":254},"jira\n",[213,4794,4795,4797,4799],{"class":215,"line":269},[213,4796,2162],{"class":226},[213,4798,251],{"class":230},[213,4800,4801],{"class":254},"\"http:\u002F\u002Fjira-mcp-server.support-triage.svc:8080\u002Fsse\"\n",[28,4803,4804,4805,4807],{},"You'll also need to update the SwarmPolicy - the policy we created earlier\ndenies all tools with ",[36,4806,4712],{},". Narrow the deny list to only block\nwhat you don't want:",[204,4809,4811],{"className":206,"code":4810,"language":208,"meta":209,"style":209},"# updated policy - block shell and filesystem tools, allow everything else\nspec:\n  tools:\n    deny:\n      - \"shell\u002F*\"\n      - \"filesystem\u002F*\"\n",[36,4812,4813,4818,4824,4830,4836,4842],{"__ignoreMap":209},[213,4814,4815],{"class":215,"line":216},[213,4816,4817],{"class":219},"# updated policy - block shell and filesystem tools, allow everything else\n",[213,4819,4820,4822],{"class":215,"line":223},[213,4821,227],{"class":226},[213,4823,231],{"class":230},[213,4825,4826,4828],{"class":215,"line":234},[213,4827,2136],{"class":226},[213,4829,231],{"class":230},[213,4831,4832,4834],{"class":215,"line":242},[213,4833,3260],{"class":226},[213,4835,231],{"class":230},[213,4837,4838,4840],{"class":215,"line":258},[213,4839,2150],{"class":230},[213,4841,3269],{"class":254},[213,4843,4844,4846],{"class":215,"line":269},[213,4845,2150],{"class":230},[213,4847,3276],{"class":254},[28,4849,4850,4851,4854],{},"Now the agent can call ",[36,4852,4853],{},"jira_create_issue"," as part of its response - classify the\nticket, draft the reply, and file it in Jira in one step. Any MCP-compatible server\nworks: Zendesk, Linear, PagerDuty, or your own internal API.",[28,4856,4857],{},"The pipeline stays the same. You just gave the agent a new tool.",[60,4859,4861],{"id":4860},"what-you-end-up-with","What you end up with",[68,4863,4864,4872],{},[71,4865,4866],{},[74,4867,4868,4870],{},[77,4869,1359],{},[77,4871,3531],{},[90,4873,4874,4883,4891,4901,4911,4920,4929],{},[74,4875,4876,4880],{},[95,4877,4878],{},[31,4879,3540],{},[95,4881,4882],{},"2 (classifier + responder)",[74,4884,4885,4889],{},[95,4886,4887],{},[31,4888,3550],{},[95,4890,3553],{},[74,4892,4893,4898],{},[95,4894,4895],{},[31,4896,4897],{},"Time per ticket",[95,4899,4900],{},"~20 seconds",[74,4902,4903,4908],{},[95,4904,4905],{},[31,4906,4907],{},"Tokens per ticket",[95,4909,4910],{},"1,612",[74,4912,4913,4918],{},[95,4914,4915],{},[31,4916,4917],{},"Cost per ticket",[95,4919,3583],{},[74,4921,4922,4926],{},[95,4923,4924],{},[31,4925,3600],{},[95,4927,4928],{},"Token budget, model allowlist, tool deny-all",[74,4930,4931,4935],{},[95,4932,4933],{},[31,4934,3817],{},[95,4936,4937],{},"Full token tracking per agent, per run",[28,4939,4940,4941,4943],{},"All defined in YAML. All managed with ",[36,4942,848],{},". No custom orchestration code.\nNo vendor lock-in. No $400 surprise bills.",[60,4945,3610],{"id":3609},[204,4947,4949],{"className":838,"code":4948,"language":840,"meta":209,"style":209},"kubectl delete namespace support-triage\n",[36,4950,4951],{"__ignoreMap":209},[213,4952,4953,4955,4957,4959],{"class":215,"line":216},[213,4954,848],{"class":847},[213,4956,3622],{"class":254},[213,4958,3625],{"class":254},[213,4960,3859],{"class":254},[28,4962,4963],{},"This removes all agents, teams, runs, and policies in the namespace.",[28,4965,4966,4969,4970,4974,4975,4979,4980,4983],{},[31,4967,4968],{},"Ready to try it?"," Grab the\n",[1131,4971,4973],{"href":3708,"rel":4972},[1135],"example files",",\ncheck out the ",[1131,4976,4978],{"href":3638,"rel":4977},[1135],"documentation"," for setup instructions,\nand the ",[1131,4981,3634],{"href":1133,"rel":4982},[1135]," for more pipeline patterns.",[45,4985],{},[28,4987,4988],{},[3647,4989,4990,4991,4994,4995,4998],{},"kubeswarm is an open-source Kubernetes operator for managing AI agents. Check out the\n",[1131,4992,4978],{"href":3638,"rel":4993},[1135]," or the\n",[1131,4996,3634],{"href":1133,"rel":4997},[1135]," for more patterns.",[1174,5000,5001],{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}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 .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}",{"title":209,"searchDepth":223,"depth":223,"links":5003},[5004,5005,5006,5007,5008,5009,5010,5011],{"id":1951,"depth":223,"text":1952},{"id":3836,"depth":223,"text":3837},{"id":4150,"depth":223,"text":4151},{"id":4315,"depth":223,"text":4316},{"id":4541,"depth":223,"text":4542},{"id":4746,"depth":223,"text":4747},{"id":4860,"depth":223,"text":4861},{"id":3609,"depth":223,"text":3610},"2026-04-23","Build a multi-agent support ticket triage pipeline on Kubernetes using kubeswarm - with policy enforcement, token budgets, and model restrictions. Runs locally with any LLM.",{},"\u002Fblog\u002Fsupport-triage",{"title":3692,"description":5013},"blog\u002Fsupport-triage",[5019,3688,1799],"support-triage","T9G2-ttOq_I6AEdVDkboZkHF0qWgV2i0RU4bnWxUa4c",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":5022},"\u003Cpath fill=\"currentColor\" d=\"M3 11h8V3H3m2 2h4v4H5m8 12h8v-8h-8m2 2h4v4h-4M3 21h8v-8H3m2 2h4v4H5m8-16v8h8V3m-2 6h-4V5h4Z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":5024},"\u003Cpath fill=\"currentColor\" d=\"M3 5v14h17V5zm4 2v2H5V7zm-2 6v-2h2v2zm0 2h2v2H5zm13 2H9v-2h9zm0-4H9v-2h9zm0-4H9V7h9z\"\u002F>",1779206063336]