[{"data":1,"prerenderedAt":1489},["ShallowReactive",2],{"i-mdi:open-in-new":3,"i-mdi:github":8,"i-mdi:menu":10,"i-local:logo":12,"blog-support-triage":15},{"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>",{"id":16,"title":17,"author":18,"authorUrl":19,"body":20,"category":1475,"date":1476,"description":1477,"extension":1478,"image":196,"meta":1479,"navigation":331,"path":1480,"seo":1481,"series":1482,"stem":1483,"tags":1484,"__hash__":1488},"blog\u002Fblog\u002Fsupport-triage.md","Run AI agents on Kubernetes: a support triage pipeline with budget guardrails","rdmnl","https:\u002F\u002Fgithub.com\u002Frdmnl",{"type":21,"value":22,"toc":1465},"minimark",[23,46,49,52,55,58,64,67,167,172,181,185,190,256,261,401,404,409,487,499,543,551,554,558,564,713,729,733,739,828,863,869,874,926,931,961,967,971,976,997,1004,1116,1170,1175,1181,1188,1192,1198,1248,1254,1296,1303,1306,1310,1393,1399,1403,1419,1422,1445,1447,1461],[24,25,26],"blockquote",{},[27,28,29,33,34,37,38,45],"p",{},[30,31,32],"strong",{},"TL;DR"," - Two AI agents classify support tickets and draft replies on Kubernetes.\nRuns on a local 7B model. Total cost per ticket: ",[30,35,36],{},"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 ",[39,40,44],"a",{"href":41,"rel":42},"https:\u002F\u002Fgithub.com\u002Fkubeswarm\u002Fkubeswarm-cookbook\u002Ftree\u002Fmain\u002Frecipes\u002F15-support-triage",[43],"nofollow","examples directory",".",[47,48],"hr",{},[27,50,51],{},"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.",[27,53,54],{},"And it gets things wrong constantly.",[27,56,57],{},"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.",[27,59,60,63],{},[30,61,62],{},"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.",[27,65,66],{},"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,69,70,85],"table",{},[71,72,73],"thead",{},[74,75,76,79,82],"tr",{},[77,78],"th",{},[77,80,81],{},"Rule-based automation",[77,83,84],{},"kubeswarm pipeline",[86,87,88,102,115,128,141,154],"tbody",{},[74,89,90,96,99],{},[91,92,93],"td",{},[30,94,95],{},"Classification",[91,97,98],{},"Keyword matching",[91,100,101],{},"Reads and understands context",[74,103,104,109,112],{},[91,105,106],{},[30,107,108],{},"Priority",[91,110,111],{},"Static rules",[91,113,114],{},"Infers urgency from content",[74,116,117,122,125],{},[91,118,119],{},[30,120,121],{},"Responses",[91,123,124],{},"Templates",[91,126,127],{},"Contextual, specific to the issue",[74,129,130,135,138],{},[91,131,132],{},[30,133,134],{},"Changing logic",[91,136,137],{},"Edit flowcharts in admin UI",[91,139,140],{},"Change the prompt",[74,142,143,148,151],{},[91,144,145],{},[30,146,147],{},"Cost control",[91,149,150],{},"N\u002FA",[91,152,153],{},"Token budgets enforced per namespace",[74,155,156,161,164],{},[91,157,158],{},[30,159,160],{},"Audit",[91,162,163],{},"Logs which rule fired",[91,165,166],{},"Tracks every token, every agent, every run",[168,169,171],"h2",{"id":170},"prerequisites","Prerequisites",[27,173,174,175,180],{},"You need a Kubernetes cluster with kubeswarm installed. See the\n",[39,176,179],{"href":177,"rel":178},"https:\u002F\u002Fdocs.kubeswarm.io\u002Fquick-start",[43],"installation guide"," for setup.",[168,182,184],{"id":183},"two-agents-one-pipeline-zero-api-cost","Two agents, one pipeline, zero API cost",[27,186,187],{},[30,188,189],{},"First, create the namespace and a Secret with your LLM endpoint:",[191,192,197],"pre",{"className":193,"code":194,"language":195,"meta":196,"style":196},"language-bash shiki shiki-themes github-dark","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","bash","",[198,199,200,219,239,250],"code",{"__ignoreMap":196},[201,202,205,209,213,216],"span",{"class":203,"line":204},"line",1,[201,206,208],{"class":207},"svObZ","kubectl",[201,210,212],{"class":211},"sU2Wk"," create",[201,214,215],{"class":211}," namespace",[201,217,218],{"class":211}," support-triage\n",[201,220,222,224,226,229,232,235],{"class":203,"line":221},2,[201,223,208],{"class":207},[201,225,212],{"class":211},[201,227,228],{"class":211}," secret",[201,230,231],{"class":211}," generic",[201,233,234],{"class":211}," ollama-credentials",[201,236,238],{"class":237},"sDLfK"," \\\n",[201,240,242,245,248],{"class":203,"line":241},3,[201,243,244],{"class":237},"  -n",[201,246,247],{"class":211}," support-triage",[201,249,238],{"class":237},[201,251,253],{"class":203,"line":252},4,[201,254,255],{"class":237},"  --from-literal=LLM_ENDPOINT=http:\u002F\u002Follama.ollama.svc:11434\n",[27,257,258],{},[30,259,260],{},"The classifier reads the ticket and outputs a structured JSON with category, priority, and summary.",[191,262,266],{"className":263,"code":264,"language":265,"meta":196,"style":196},"language-yaml shiki shiki-themes github-dark","# 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","yaml",[198,267,268,274,284,295,302,314,320,326,333,339,345,351,357,363,371,379,390],{"__ignoreMap":196},[201,269,270],{"class":203,"line":204},[201,271,273],{"class":272},"sAwPA","# classifier-agent.yaml\n",[201,275,276,280],{"class":203,"line":221},[201,277,279],{"class":278},"s4JwU","spec",[201,281,283],{"class":282},"s95oV",":\n",[201,285,286,289,292],{"class":203,"line":241},[201,287,288],{"class":278},"  model",[201,290,291],{"class":282},": ",[201,293,294],{"class":211},"qwen2.5:7b\n",[201,296,297,300],{"class":203,"line":252},[201,298,299],{"class":278},"  prompt",[201,301,283],{"class":282},[201,303,305,308,310],{"class":203,"line":304},5,[201,306,307],{"class":278},"    inline",[201,309,291],{"class":282},[201,311,313],{"class":312},"snl16","|\n",[201,315,317],{"class":203,"line":316},6,[201,318,319],{"class":211},"      You are a support ticket classifier. For every ticket you receive,\n",[201,321,323],{"class":203,"line":322},7,[201,324,325],{"class":211},"      respond with ONLY a JSON object:\n",[201,327,329],{"class":203,"line":328},8,[201,330,332],{"emptyLinePlaceholder":331},true,"\n",[201,334,336],{"class":203,"line":335},9,[201,337,338],{"class":211},"      {\n",[201,340,342],{"class":203,"line":341},10,[201,343,344],{"class":211},"        \"category\": \"\u003Cbilling|technical|bug|feature-request>\",\n",[201,346,348],{"class":203,"line":347},11,[201,349,350],{"class":211},"        \"priority\": \"\u003Clow|medium|high|critical>\",\n",[201,352,354],{"class":203,"line":353},12,[201,355,356],{"class":211},"        \"summary\": \"\u003Cone sentence summary>\"\n",[201,358,360],{"class":203,"line":359},13,[201,361,362],{"class":211},"      }\n",[201,364,366,369],{"class":203,"line":365},14,[201,367,368],{"class":278},"  guardrails",[201,370,283],{"class":282},[201,372,374,377],{"class":203,"line":373},15,[201,375,376],{"class":278},"    limits",[201,378,283],{"class":282},[201,380,382,385,387],{"class":203,"line":381},16,[201,383,384],{"class":278},"      tokensPerCall",[201,386,291],{"class":282},[201,388,389],{"class":237},"500\n",[201,391,393,396,398],{"class":203,"line":392},17,[201,394,395],{"class":278},"      timeoutSeconds",[201,397,291],{"class":282},[201,399,400],{"class":237},"30\n",[27,402,403],{},"500 tokens. 30 second timeout. That's all a classifier needs.",[27,405,406],{},[30,407,408],{},"The responder takes that classification and drafts an empathetic customer reply.",[191,410,412],{"className":263,"code":411,"language":265,"meta":196,"style":196},"# 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",[198,413,414,419,425,433,439,447,452,457,463,469,478],{"__ignoreMap":196},[201,415,416],{"class":203,"line":204},[201,417,418],{"class":272},"# responder-agent.yaml\n",[201,420,421,423],{"class":203,"line":221},[201,422,279],{"class":278},[201,424,283],{"class":282},[201,426,427,429,431],{"class":203,"line":241},[201,428,288],{"class":278},[201,430,291],{"class":282},[201,432,294],{"class":211},[201,434,435,437],{"class":203,"line":252},[201,436,299],{"class":278},[201,438,283],{"class":282},[201,440,441,443,445],{"class":203,"line":304},[201,442,307],{"class":278},[201,444,291],{"class":282},[201,446,313],{"class":312},[201,448,449],{"class":203,"line":316},[201,450,451],{"class":211},"      You are a customer support agent. You receive a ticket with its\n",[201,453,454],{"class":203,"line":322},[201,455,456],{"class":211},"      classification. Write a helpful, empathetic reply under 150 words.\n",[201,458,459,461],{"class":203,"line":328},[201,460,368],{"class":278},[201,462,283],{"class":282},[201,464,465,467],{"class":203,"line":335},[201,466,376],{"class":278},[201,468,283],{"class":282},[201,470,471,473,475],{"class":203,"line":341},[201,472,384],{"class":278},[201,474,291],{"class":282},[201,476,477],{"class":237},"2000\n",[201,479,480,482,484],{"class":203,"line":347},[201,481,395],{"class":278},[201,483,291],{"class":282},[201,485,486],{"class":237},"60\n",[27,488,489,490,493,494,498],{},"Both agents reference the Secret you created earlier via ",[198,491,492],{},"infrastructure.envFrom"," -\nsee the ",[39,495,497],{"href":41,"rel":496},[43],"full YAML files"," for the complete setup.",[191,500,502],{"className":193,"code":501,"language":195,"meta":196,"style":196},"kubectl apply -f classifier-agent.yaml\nkubectl apply -f responder-agent.yaml\nkubectl get swarmagents -n support-triage\n",[198,503,504,517,528],{"__ignoreMap":196},[201,505,506,508,511,514],{"class":203,"line":204},[201,507,208],{"class":207},[201,509,510],{"class":211}," apply",[201,512,513],{"class":237}," -f",[201,515,516],{"class":211}," classifier-agent.yaml\n",[201,518,519,521,523,525],{"class":203,"line":221},[201,520,208],{"class":207},[201,522,510],{"class":211},[201,524,513],{"class":237},[201,526,527],{"class":211}," responder-agent.yaml\n",[201,529,530,532,535,538,541],{"class":203,"line":241},[201,531,208],{"class":207},[201,533,534],{"class":211}," get",[201,536,537],{"class":211}," swarmagents",[201,539,540],{"class":237}," -n",[201,542,218],{"class":211},[191,544,549],{"className":545,"code":547,"language":548},[546],"language-text","NAME                MODEL        REPLICAS   READY   AGE\nticket-classifier   qwen2.5:7b   1          1       5s\nticket-responder    qwen2.5:7b   1          1       5s\n","text",[198,550,547],{"__ignoreMap":196},[27,552,553],{},"Two agents. Ready in seconds.",[168,555,557],{"id":556},"building-a-multi-agent-pipeline-with-swarmteam","Building a multi-agent pipeline with SwarmTeam",[27,559,560,563],{},[30,561,562],{},"Agents alone are just pods. A SwarmTeam wires them into a pipeline"," where\none agent's output feeds into the next.",[191,565,567],{"className":263,"code":566,"language":265,"meta":196,"style":196},"# 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",[198,568,569,574,580,587,600,610,621,630,637,648,655,665,675,689,695,705],{"__ignoreMap":196},[201,570,571],{"class":203,"line":204},[201,572,573],{"class":272},"# support-team.yaml\n",[201,575,576,578],{"class":203,"line":221},[201,577,279],{"class":278},[201,579,283],{"class":282},[201,581,582,585],{"class":203,"line":241},[201,583,584],{"class":278},"  roles",[201,586,283],{"class":282},[201,588,589,592,595,597],{"class":203,"line":252},[201,590,591],{"class":282},"    - ",[201,593,594],{"class":278},"name",[201,596,291],{"class":282},[201,598,599],{"class":211},"classifier\n",[201,601,602,605,607],{"class":203,"line":304},[201,603,604],{"class":278},"      swarmAgent",[201,606,291],{"class":282},[201,608,609],{"class":211},"ticket-classifier\n",[201,611,612,614,616,618],{"class":203,"line":316},[201,613,591],{"class":282},[201,615,594],{"class":278},[201,617,291],{"class":282},[201,619,620],{"class":211},"responder\n",[201,622,623,625,627],{"class":203,"line":322},[201,624,604],{"class":278},[201,626,291],{"class":282},[201,628,629],{"class":211},"ticket-responder\n",[201,631,632,635],{"class":203,"line":328},[201,633,634],{"class":278},"  pipeline",[201,636,283],{"class":282},[201,638,639,641,644,646],{"class":203,"line":335},[201,640,591],{"class":282},[201,642,643],{"class":278},"role",[201,645,291],{"class":282},[201,647,599],{"class":211},[201,649,650,653],{"class":203,"line":341},[201,651,652],{"class":278},"      inputs",[201,654,283],{"class":282},[201,656,657,660,662],{"class":203,"line":347},[201,658,659],{"class":278},"        ticket",[201,661,291],{"class":282},[201,663,664],{"class":211},"\"{{ .input.ticket }}\"\n",[201,666,667,669,671,673],{"class":203,"line":353},[201,668,591],{"class":282},[201,670,643],{"class":278},[201,672,291],{"class":282},[201,674,620],{"class":211},[201,676,677,680,683,686],{"class":203,"line":359},[201,678,679],{"class":278},"      dependsOn",[201,681,682],{"class":282},": [",[201,684,685],{"class":211},"classifier",[201,687,688],{"class":282},"]\n",[201,690,691,693],{"class":203,"line":365},[201,692,652],{"class":278},[201,694,283],{"class":282},[201,696,697,700,702],{"class":203,"line":373},[201,698,699],{"class":278},"        classification",[201,701,291],{"class":282},[201,703,704],{"class":211},"\"{{ .steps.classifier.output }}\"\n",[201,706,707,709,711],{"class":203,"line":381},[201,708,659],{"class":278},[201,710,291],{"class":282},[201,712,664],{"class":211},[24,714,715],{},[27,716,717,720,721,724,725,728],{},[30,718,719],{},"Think of it like a Makefile target with dependencies."," The responder declares\n",[198,722,723],{},"dependsOn: [classifier]",", so it waits for the classifier to finish. The\n",[198,726,727],{},"{{ .steps.classifier.output }}"," template is how data flows between steps.",[168,730,732],{"id":731},"running-a-real-support-ticket-through-the-pipeline","Running a real support ticket through the pipeline",[27,734,735,738],{},[30,736,737],{},"Here's a real ticket."," Sarah's team can't access their dashboard, client demo in two hours.",[191,740,742],{"className":263,"code":741,"language":265,"meta":196,"style":196},"# 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",[198,743,744,749,755,765,772,781,786,790,795,800,805,809,814,819,823],{"__ignoreMap":196},[201,745,746],{"class":203,"line":204},[201,747,748],{"class":272},"# sample-run.yaml\n",[201,750,751,753],{"class":203,"line":221},[201,752,279],{"class":278},[201,754,283],{"class":282},[201,756,757,760,762],{"class":203,"line":241},[201,758,759],{"class":278},"  teamRef",[201,761,291],{"class":282},[201,763,764],{"class":211},"support-triage\n",[201,766,767,770],{"class":203,"line":252},[201,768,769],{"class":278},"  input",[201,771,283],{"class":282},[201,773,774,777,779],{"class":203,"line":304},[201,775,776],{"class":278},"    ticket",[201,778,291],{"class":282},[201,780,313],{"class":312},[201,782,783],{"class":203,"line":316},[201,784,785],{"class":211},"      Subject: Can't access my dashboard since this morning\n",[201,787,788],{"class":203,"line":322},[201,789,332],{"emptyLinePlaceholder":331},[201,791,792],{"class":203,"line":328},[201,793,794],{"class":211},"      I've been using your platform for 3 months and everything was fine\n",[201,796,797],{"class":203,"line":335},[201,798,799],{"class":211},"      until today. When I try to log in, I get a blank white screen after\n",[201,801,802],{"class":203,"line":341},[201,803,804],{"class":211},"      entering my credentials. Tried Chrome, Firefox, incognito. Nothing.\n",[201,806,807],{"class":203,"line":347},[201,808,332],{"emptyLinePlaceholder":331},[201,810,811],{"class":203,"line":353},[201,812,813],{"class":211},"      This is blocking my entire team - we have a demo with a client\n",[201,815,816],{"class":203,"line":359},[201,817,818],{"class":211},"      in 2 hours and need access to our analytics dashboard urgently.\n",[201,820,821],{"class":203,"line":365},[201,822,332],{"emptyLinePlaceholder":331},[201,824,825],{"class":203,"line":373},[201,826,827],{"class":211},"      Sarah Chen, Acme Corp - Enterprise Plan\n",[191,829,831],{"className":193,"code":830,"language":195,"meta":196,"style":196},"kubectl apply -f sample-run.yaml\nkubectl get swarmrun ticket-001 -n support-triage -w\n",[198,832,833,844],{"__ignoreMap":196},[201,834,835,837,839,841],{"class":203,"line":204},[201,836,208],{"class":207},[201,838,510],{"class":211},[201,840,513],{"class":237},[201,842,843],{"class":211}," sample-run.yaml\n",[201,845,846,848,850,853,856,858,860],{"class":203,"line":221},[201,847,208],{"class":207},[201,849,534],{"class":211},[201,851,852],{"class":211}," swarmrun",[201,854,855],{"class":211}," ticket-001",[201,857,540],{"class":237},[201,859,247],{"class":211},[201,861,862],{"class":237}," -w\n",[191,864,867],{"className":865,"code":866,"language":548},[546],"NAME         PHASE      AGE\nticket-001   Pending    0s\nticket-001   Running    2s\nticket-001   Succeeded  20s\n",[198,868,866],{"__ignoreMap":196},[27,870,871],{},[30,872,873],{},"The classifier returns:",[191,875,879],{"className":876,"code":877,"language":878,"meta":196,"style":196},"language-json shiki shiki-themes github-dark","{\n  \"category\": \"technical\",\n  \"priority\": \"high\",\n  \"summary\": \"Blank white screen on dashboard login from Chrome and Firefox\"\n}\n","json",[198,880,881,886,899,911,921],{"__ignoreMap":196},[201,882,883],{"class":203,"line":204},[201,884,885],{"class":282},"{\n",[201,887,888,891,893,896],{"class":203,"line":221},[201,889,890],{"class":237},"  \"category\"",[201,892,291],{"class":282},[201,894,895],{"class":211},"\"technical\"",[201,897,898],{"class":282},",\n",[201,900,901,904,906,909],{"class":203,"line":241},[201,902,903],{"class":237},"  \"priority\"",[201,905,291],{"class":282},[201,907,908],{"class":211},"\"high\"",[201,910,898],{"class":282},[201,912,913,916,918],{"class":203,"line":252},[201,914,915],{"class":237},"  \"summary\"",[201,917,291],{"class":282},[201,919,920],{"class":211},"\"Blank white screen on dashboard login from Chrome and Firefox\"\n",[201,922,923],{"class":203,"line":304},[201,924,925],{"class":282},"}\n",[27,927,928],{},[30,929,930],{},"The responder drafts:",[24,932,933,936,939,942,955,958],{},[27,934,935],{},"Hi Sarah,",[27,937,938],{},"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.",[27,940,941],{},"Could you please provide us with:",[943,944,945,949,952],"ul",{},[946,947,948],"li",{},"Your exact version of Chrome\u002FFirefox",[946,950,951],{},"Any error messages shown (if any)",[946,953,954],{},"A screenshot if possible",[27,956,957],{},"This will help us diagnose the problem faster. Rest assured, we are\nescalating this and will keep you updated.",[27,959,960],{},"Support Team",[27,962,963,964],{},"Category: correct. Priority: correct. Response: empathetic and actionable.\n",[30,965,966],{},"From a 7B model running locally. Total cost: 1,612 tokens, $0.00.",[168,968,970],{"id":969},"ai-agent-guardrails-budget-limits-and-model-policies-on-kubernetes","AI agent guardrails: budget limits and model policies on Kubernetes",[27,972,973],{},[30,974,975],{},"Your triage pipeline works. You deploy it for the team. Then three things happen:",[977,978,979,985,991],"ol",{},[946,980,981,982],{},"Someone deploys a \"helper\" agent using GPT-4 that burns through ",[30,983,984],{},"$200 in a day",[946,986,987,988],{},"Another agent gets access to a shell tool and starts ",[30,989,990],{},"executing commands from ticket text",[946,992,993,994],{},"Nobody knows which agent processed which ticket, or ",[30,995,996],{},"how many tokens it cost",[27,998,999,1000,1003],{},"This is where kubeswarm is different from a Python script. ",[30,1001,1002],{},"A SwarmPolicy enforces\nrules at the namespace level"," - before the pod even starts:",[191,1005,1007],{"className":263,"code":1006,"language":265,"meta":196,"style":196},"# 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",[198,1008,1009,1014,1020,1030,1037,1047,1056,1066,1073,1080,1088,1095,1102,1109],{"__ignoreMap":196},[201,1010,1011],{"class":203,"line":204},[201,1012,1013],{"class":272},"# budget-policy.yaml\n",[201,1015,1016,1018],{"class":203,"line":221},[201,1017,279],{"class":278},[201,1019,283],{"class":282},[201,1021,1022,1025,1027],{"class":203,"line":241},[201,1023,1024],{"class":278},"  enforcementMode",[201,1026,291],{"class":282},[201,1028,1029],{"class":211},"Enforce\n",[201,1031,1032,1035],{"class":203,"line":252},[201,1033,1034],{"class":278},"  limits",[201,1036,283],{"class":282},[201,1038,1039,1042,1044],{"class":203,"line":304},[201,1040,1041],{"class":278},"    maxDailyTokens",[201,1043,291],{"class":282},[201,1045,1046],{"class":237},"500000\n",[201,1048,1049,1052,1054],{"class":203,"line":316},[201,1050,1051],{"class":278},"    maxTokensPerCall",[201,1053,291],{"class":282},[201,1055,477],{"class":237},[201,1057,1058,1061,1063],{"class":203,"line":322},[201,1059,1060],{"class":278},"    maxTimeoutSeconds",[201,1062,291],{"class":282},[201,1064,1065],{"class":237},"120\n",[201,1067,1068,1071],{"class":203,"line":328},[201,1069,1070],{"class":278},"  tools",[201,1072,283],{"class":282},[201,1074,1075,1078],{"class":203,"line":335},[201,1076,1077],{"class":278},"    deny",[201,1079,283],{"class":282},[201,1081,1082,1085],{"class":203,"line":341},[201,1083,1084],{"class":282},"      - ",[201,1086,1087],{"class":211},"\"*\"\n",[201,1089,1090,1093],{"class":203,"line":347},[201,1091,1092],{"class":278},"  models",[201,1094,283],{"class":282},[201,1096,1097,1100],{"class":203,"line":353},[201,1098,1099],{"class":278},"    allowed",[201,1101,283],{"class":282},[201,1103,1104,1106],{"class":203,"line":359},[201,1105,1084],{"class":282},[201,1107,1108],{"class":211},"\"qwen*\"\n",[201,1110,1111,1113],{"class":203,"line":365},[201,1112,1084],{"class":282},[201,1114,1115],{"class":211},"\"gpt-oss-*\"\n",[68,1117,1118,1128],{},[71,1119,1120],{},[74,1121,1122,1125],{},[77,1123,1124],{},"Rule",[77,1126,1127],{},"What it does",[86,1129,1130,1140,1150,1160],{},[74,1131,1132,1137],{},[91,1133,1134],{},[198,1135,1136],{},"maxDailyTokens: 500000",[91,1138,1139],{},"Hard ceiling on daily spend per namespace",[74,1141,1142,1147],{},[91,1143,1144],{},[198,1145,1146],{},"maxTokensPerCall: 2000",[91,1148,1149],{},"No single call burns more than 2K tokens",[74,1151,1152,1157],{},[91,1153,1154],{},[198,1155,1156],{},"tools.deny: [\"*\"]",[91,1158,1159],{},"No tools at all - these agents read and write text, nothing else",[74,1161,1162,1167],{},[91,1163,1164],{},[198,1165,1166],{},"models.allowed",[91,1168,1169],{},"Only approved models can be deployed",[27,1171,1172],{},[30,1173,1174],{},"Try to sneak in a different model:",[191,1176,1179],{"className":1177,"code":1178,"language":548},[546],"Error from server (Forbidden): model \"gpt-4o\" is not allowed\nby SwarmPolicy \"support-budget\" (allowed: qwen*, gpt-oss-*)\n",[198,1180,1178],{"__ignoreMap":196},[27,1182,1183,1184,1187],{},"Rejected at the API level. Not at runtime. Not after it already spent your money.\n",[30,1185,1186],{},"At admission time."," The pod never starts.",[168,1189,1191],{"id":1190},"connecting-agents-to-jira-zendesk-or-slack-via-mcp","Connecting agents to Jira, Zendesk, or Slack via MCP",[27,1193,1194,1197],{},[30,1195,1196],{},"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:",[191,1199,1201],{"className":263,"code":1200,"language":265,"meta":196,"style":196},"# 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",[198,1202,1203,1208,1214,1220,1227,1238],{"__ignoreMap":196},[201,1204,1205],{"class":203,"line":204},[201,1206,1207],{"class":272},"# give the responder access to your ticketing system\n",[201,1209,1210,1212],{"class":203,"line":221},[201,1211,279],{"class":278},[201,1213,283],{"class":282},[201,1215,1216,1218],{"class":203,"line":241},[201,1217,1070],{"class":278},[201,1219,283],{"class":282},[201,1221,1222,1225],{"class":203,"line":252},[201,1223,1224],{"class":278},"    mcp",[201,1226,283],{"class":282},[201,1228,1229,1231,1233,1235],{"class":203,"line":304},[201,1230,1084],{"class":282},[201,1232,594],{"class":278},[201,1234,291],{"class":282},[201,1236,1237],{"class":211},"jira\n",[201,1239,1240,1243,1245],{"class":203,"line":316},[201,1241,1242],{"class":278},"        url",[201,1244,291],{"class":282},[201,1246,1247],{"class":211},"\"http:\u002F\u002Fjira-mcp-server.support-triage.svc:8080\u002Fsse\"\n",[27,1249,1250,1251,1253],{},"You'll also need to update the SwarmPolicy - the policy we created earlier\ndenies all tools with ",[198,1252,1156],{},". Narrow the deny list to only block\nwhat you don't want:",[191,1255,1257],{"className":263,"code":1256,"language":265,"meta":196,"style":196},"# updated policy - block shell and filesystem tools, allow everything else\nspec:\n  tools:\n    deny:\n      - \"shell\u002F*\"\n      - \"filesystem\u002F*\"\n",[198,1258,1259,1264,1270,1276,1282,1289],{"__ignoreMap":196},[201,1260,1261],{"class":203,"line":204},[201,1262,1263],{"class":272},"# updated policy - block shell and filesystem tools, allow everything else\n",[201,1265,1266,1268],{"class":203,"line":221},[201,1267,279],{"class":278},[201,1269,283],{"class":282},[201,1271,1272,1274],{"class":203,"line":241},[201,1273,1070],{"class":278},[201,1275,283],{"class":282},[201,1277,1278,1280],{"class":203,"line":252},[201,1279,1077],{"class":278},[201,1281,283],{"class":282},[201,1283,1284,1286],{"class":203,"line":304},[201,1285,1084],{"class":282},[201,1287,1288],{"class":211},"\"shell\u002F*\"\n",[201,1290,1291,1293],{"class":203,"line":316},[201,1292,1084],{"class":282},[201,1294,1295],{"class":211},"\"filesystem\u002F*\"\n",[27,1297,1298,1299,1302],{},"Now the agent can call ",[198,1300,1301],{},"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.",[27,1304,1305],{},"The pipeline stays the same. You just gave the agent a new tool.",[168,1307,1309],{"id":1308},"what-you-end-up-with","What you end up with",[68,1311,1312,1322],{},[71,1313,1314],{},[74,1315,1316,1319],{},[77,1317,1318],{},"Metric",[77,1320,1321],{},"Value",[86,1323,1324,1334,1344,1354,1364,1374,1384],{},[74,1325,1326,1331],{},[91,1327,1328],{},[30,1329,1330],{},"Agents",[91,1332,1333],{},"2 (classifier + responder)",[74,1335,1336,1341],{},[91,1337,1338],{},[30,1339,1340],{},"Model",[91,1342,1343],{},"qwen2.5:7b (any model works)",[74,1345,1346,1351],{},[91,1347,1348],{},[30,1349,1350],{},"Time per ticket",[91,1352,1353],{},"~20 seconds",[74,1355,1356,1361],{},[91,1357,1358],{},[30,1359,1360],{},"Tokens per ticket",[91,1362,1363],{},"1,612",[74,1365,1366,1371],{},[91,1367,1368],{},[30,1369,1370],{},"Cost per ticket",[91,1372,1373],{},"$0.00 (local model)",[74,1375,1376,1381],{},[91,1377,1378],{},[30,1379,1380],{},"Guardrails",[91,1382,1383],{},"Token budget, model allowlist, tool deny-all",[74,1385,1386,1390],{},[91,1387,1388],{},[30,1389,160],{},[91,1391,1392],{},"Full token tracking per agent, per run",[27,1394,1395,1396,1398],{},"All defined in YAML. All managed with ",[198,1397,208],{},". No custom orchestration code.\nNo vendor lock-in. No $400 surprise bills.",[168,1400,1402],{"id":1401},"cleanup","Cleanup",[191,1404,1406],{"className":193,"code":1405,"language":195,"meta":196,"style":196},"kubectl delete namespace support-triage\n",[198,1407,1408],{"__ignoreMap":196},[201,1409,1410,1412,1415,1417],{"class":203,"line":204},[201,1411,208],{"class":207},[201,1413,1414],{"class":211}," delete",[201,1416,215],{"class":211},[201,1418,218],{"class":211},[27,1420,1421],{},"This removes all agents, teams, runs, and policies in the namespace.",[27,1423,1424,1427,1428,1432,1433,1438,1439,1444],{},[30,1425,1426],{},"Ready to try it?"," Grab the\n",[39,1429,1431],{"href":41,"rel":1430},[43],"example files",",\ncheck out the ",[39,1434,1437],{"href":1435,"rel":1436},"https:\u002F\u002Fdocs.kubeswarm.io",[43],"documentation"," for setup instructions,\nand the ",[39,1440,1443],{"href":1441,"rel":1442},"https:\u002F\u002Fgithub.com\u002Fkubeswarm\u002Fkubeswarm-cookbook",[43],"cookbook"," for more pipeline patterns.",[47,1446],{},[27,1448,1449],{},[1450,1451,1452,1453,1456,1457,1460],"em",{},"kubeswarm is an open-source Kubernetes operator for managing AI agents. Check out the\n",[39,1454,1437],{"href":1435,"rel":1455},[43]," or the\n",[39,1458,1443],{"href":1441,"rel":1459},[43]," for more patterns.",[1462,1463,1464],"style",{},"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":196,"searchDepth":221,"depth":221,"links":1466},[1467,1468,1469,1470,1471,1472,1473,1474],{"id":170,"depth":221,"text":171},{"id":183,"depth":221,"text":184},{"id":556,"depth":221,"text":557},{"id":731,"depth":221,"text":732},{"id":969,"depth":221,"text":970},{"id":1190,"depth":221,"text":1191},{"id":1308,"depth":221,"text":1309},{"id":1401,"depth":221,"text":1402},"tutorial","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.","md",{},"\u002Fblog\u002Fsupport-triage",{"title":17,"description":1477},null,"blog\u002Fsupport-triage",[1485,1486,1487],"support-triage","multi-agent","cost-control","T9G2-ttOq_I6AEdVDkboZkHF0qWgV2i0RU4bnWxUa4c",1777113023353]