[{"data":1,"prerenderedAt":656},["ShallowReactive",2],{"docs-\u002Fdocs\u002Ffeatures\u002Fplugins":3},{"id":4,"title":5,"body":6,"description":649,"extension":650,"meta":651,"navigation":192,"path":652,"seo":653,"stem":654,"__hash__":655},"docs\u002Fdocs\u002Ffeatures\u002Fplugins.md","Plugins",{"type":7,"value":8,"toc":641},"minimark",[9,13,22,33,40,45,145,148,152,159,431,438,442,449,485,495,502,508,561,576,583,587,611,615,618,622,637],[10,11,5],"h1",{"id":12},"plugins",[14,15,16,17,21],"p",{},"A plugin is a Python file with a ",[18,19,20],"code",{},"register(slo)"," function. Slo calls it when a session starts, and the plugin declares what it adds: a tool the model can call, a slash command, a custom renderer, a footer status, or a rule that checks every tool call before it runs.",[14,23,24,25,28,29,32],{},"Plugins are how you teach Slo things that aren't prompts. Block ",[18,26,27],{},"git push --force"," to main. Redact tokens before the model ever sees them. Add a ",[18,30,31],{},"\u002Fdeploy-status"," command for your team.",[34,35,37],"note",{"kind":36},"warning",[14,38,39],{},"Plugins are ordinary Python running with your permissions. Only install plugins from sources you trust, the same way you'd treat any script you run locally.",[41,42,44],"h2",{"id":43},"what-a-plugin-can-do","What a plugin can do",[46,47,48,61],"table",{},[49,50,51],"thead",{},[52,53,54,58],"tr",{},[55,56,57],"th",{},"Interface",[55,59,60],{},"What it does",[62,63,64,75,85,95,105,115,125,135],"tbody",{},[52,65,66,72],{},[67,68,69],"td",{},[18,70,71],{},"slo.on(event_kind, handler)",[67,73,74],{},"Watch the live event stream: text, thinking, tool calls, tool output, run state, sub-agents.",[52,76,77,82],{},[67,78,79],{},[18,80,81],{},"slo.register_tool(tool)",[67,83,84],{},"Add a tool the model can call. Built-in tools can't be replaced.",[52,86,87,92],{},[67,88,89],{},[18,90,91],{},"slo.register_command(name, handler)",[67,93,94],{},"Add a slash command for you to run in the TUI.",[52,96,97,102],{},[67,98,99],{},[18,100,101],{},"slo.register_tool_renderer(tool_name, render)",[67,103,104],{},"Change how a tool's output is displayed. The model still sees the original.",[52,106,107,112],{},[67,108,109],{},[18,110,111],{},"slo.set_status(key, text)",[67,113,114],{},"Show a short status in the footer. Empty text clears it.",[52,116,117,122],{},[67,118,119],{},[18,120,121],{},"slo.on_before_tool_call(handler)",[67,123,124],{},"Inspect a tool call before it runs. Block it with a reason, or rewrite its input.",[52,126,127,132],{},[67,128,129],{},[18,130,131],{},"slo.on_after_tool_result(handler)",[67,133,134],{},"Change a tool result before the model sees it, for example to redact secrets.",[52,136,137,142],{},[67,138,139],{},[18,140,141],{},"slo.on_before_agent_turn(handler)",[67,143,144],{},"Add context messages before each model turn.",[14,146,147],{},"Plugins can't add new agent types, own screens or modals, handle sign-in, or run background workers.",[41,149,151],{"id":150},"a-small-example","A small example",[14,153,154,155,158],{},"This plugin blocks force-pushes to ",[18,156,157],{},"main"," and shows that it's active:",[160,161,166],"pre",{"className":162,"code":163,"language":164,"meta":165,"style":165},"language-python shiki shiki-themes github-dark-high-contrast","from typing import Any\n\nfrom src.agents.slo_agent.plugins.dao import BeforeToolCallDecisionDAO\n\nBLOCKED = (\"git push --force origin main\", \"git push -f origin main\")\n\n\ndef register(slo: Any) -> None:\n    slo.set_status(key=\"push-guard\", text=\"Push guard: on\")\n    slo.on_before_tool_call(handler=block_force_push)\n\n\ndef block_force_push(decision: BeforeToolCallDecisionDAO, tool_input: dict) -> BeforeToolCallDecisionDAO:\n    command = str(tool_input.get(\"command\", \"\"))\n    if any(pattern in command for pattern in BLOCKED):\n        return BeforeToolCallDecisionDAO(block=True, reason=\"Force-pushing to main is not allowed here.\")\n    return BeforeToolCallDecisionDAO()\n","python","",[18,167,168,187,194,207,212,238,243,248,267,295,309,314,319,336,361,393,422],{"__ignoreMap":165},[169,170,173,177,181,184],"span",{"class":171,"line":172},"line",1,[169,174,176],{"class":175},"sWyjQ","from",[169,178,180],{"class":179},"sMAXC"," typing ",[169,182,183],{"class":175},"import",[169,185,186],{"class":179}," Any\n",[169,188,190],{"class":171,"line":189},2,[169,191,193],{"emptyLinePlaceholder":192},true,"\n",[169,195,197,199,202,204],{"class":171,"line":196},3,[169,198,176],{"class":175},[169,200,201],{"class":179}," src.agents.slo_agent.plugins.dao ",[169,203,183],{"class":175},[169,205,206],{"class":179}," BeforeToolCallDecisionDAO\n",[169,208,210],{"class":171,"line":209},4,[169,211,193],{"emptyLinePlaceholder":192},[169,213,215,219,222,225,229,232,235],{"class":171,"line":214},5,[169,216,218],{"class":217},"sCcAr","BLOCKED",[169,220,221],{"class":175}," =",[169,223,224],{"class":179}," (",[169,226,228],{"class":227},"sTRMh","\"git push --force origin main\"",[169,230,231],{"class":179},", ",[169,233,234],{"class":227},"\"git push -f origin main\"",[169,236,237],{"class":179},")\n",[169,239,241],{"class":171,"line":240},6,[169,242,193],{"emptyLinePlaceholder":192},[169,244,246],{"class":171,"line":245},7,[169,247,193],{"emptyLinePlaceholder":192},[169,249,251,254,258,261,264],{"class":171,"line":250},8,[169,252,253],{"class":175},"def",[169,255,257],{"class":256},"sj_SG"," register",[169,259,260],{"class":179},"(slo: Any) -> ",[169,262,263],{"class":217},"None",[169,265,266],{"class":179},":\n",[169,268,270,273,277,280,283,285,288,290,293],{"class":171,"line":269},9,[169,271,272],{"class":179},"    slo.set_status(",[169,274,276],{"class":275},"s_sBn","key",[169,278,279],{"class":175},"=",[169,281,282],{"class":227},"\"push-guard\"",[169,284,231],{"class":179},[169,286,287],{"class":275},"text",[169,289,279],{"class":175},[169,291,292],{"class":227},"\"Push guard: on\"",[169,294,237],{"class":179},[169,296,298,301,304,306],{"class":171,"line":297},10,[169,299,300],{"class":179},"    slo.on_before_tool_call(",[169,302,303],{"class":275},"handler",[169,305,279],{"class":175},[169,307,308],{"class":179},"block_force_push)\n",[169,310,312],{"class":171,"line":311},11,[169,313,193],{"emptyLinePlaceholder":192},[169,315,317],{"class":171,"line":316},12,[169,318,193],{"emptyLinePlaceholder":192},[169,320,322,324,327,330,333],{"class":171,"line":321},13,[169,323,253],{"class":175},[169,325,326],{"class":256}," block_force_push",[169,328,329],{"class":179},"(decision: BeforeToolCallDecisionDAO, tool_input: ",[169,331,332],{"class":217},"dict",[169,334,335],{"class":179},") -> BeforeToolCallDecisionDAO:\n",[169,337,339,342,344,347,350,353,355,358],{"class":171,"line":338},14,[169,340,341],{"class":179},"    command ",[169,343,279],{"class":175},[169,345,346],{"class":217}," str",[169,348,349],{"class":179},"(tool_input.get(",[169,351,352],{"class":227},"\"command\"",[169,354,231],{"class":179},[169,356,357],{"class":227},"\"\"",[169,359,360],{"class":179},"))\n",[169,362,364,367,370,373,376,379,382,385,387,390],{"class":171,"line":363},15,[169,365,366],{"class":175},"    if",[169,368,369],{"class":217}," any",[169,371,372],{"class":179},"(pattern ",[169,374,375],{"class":175},"in",[169,377,378],{"class":179}," command ",[169,380,381],{"class":175},"for",[169,383,384],{"class":179}," pattern ",[169,386,375],{"class":175},[169,388,389],{"class":217}," BLOCKED",[169,391,392],{"class":179},"):\n",[169,394,396,399,402,405,407,410,412,415,417,420],{"class":171,"line":395},16,[169,397,398],{"class":175},"        return",[169,400,401],{"class":179}," BeforeToolCallDecisionDAO(",[169,403,404],{"class":275},"block",[169,406,279],{"class":175},[169,408,409],{"class":217},"True",[169,411,231],{"class":179},[169,413,414],{"class":275},"reason",[169,416,279],{"class":175},[169,418,419],{"class":227},"\"Force-pushing to main is not allowed here.\"",[169,421,237],{"class":179},[169,423,425,428],{"class":171,"line":424},17,[169,426,427],{"class":175},"    return",[169,429,430],{"class":179}," BeforeToolCallDecisionDAO()\n",[14,432,433,434,437],{},"Interceptors run in the order they were registered, and the first one that blocks wins. The full interface reference, with every callback signature, lives in ",[18,435,436],{},"docs\u002FEXTENSIONS.md"," in the Slo repository.",[41,439,441],{"id":440},"three-ways-to-load-a-plugin","Three ways to load a plugin",[14,443,444,448],{},[445,446,447],"strong",{},"1. While you write it."," Point Slo at the file. Explicit paths are trusted, and you can repeat the flag:",[160,450,454],{"className":451,"code":452,"language":453,"meta":165,"style":165},"language-bash shiki shiki-themes github-dark-high-contrast","slo --plugin .\u002Fmy_plugin.py\nslo run --plugin .\u002Fmy_plugin.py -p \"Tidy up the release notes\"\n","bash",[18,455,456,467],{"__ignoreMap":165},[169,457,458,461,464],{"class":171,"line":172},[169,459,460],{"class":275},"slo",[169,462,463],{"class":217}," --plugin",[169,465,466],{"class":227}," .\u002Fmy_plugin.py\n",[169,468,469,471,474,476,479,482],{"class":171,"line":189},[169,470,460],{"class":275},[169,472,473],{"class":227}," run",[169,475,463],{"class":217},[169,477,478],{"class":227}," .\u002Fmy_plugin.py",[169,480,481],{"class":217}," -p",[169,483,484],{"class":227}," \"Tidy up the release notes\"\n",[14,486,487,490,491,494],{},[445,488,489],{},"2. For one project."," Put it in ",[18,492,493],{},".slo\u002Fplugins\u002F"," inside the project. Slo won't load project plugins until you trust the project, so a repository you just cloned can't run code on your machine by surprise:",[160,496,500],{"className":497,"code":499,"language":287,"meta":165},[498],"language-text","\u002Fplugins trust     trust this project and load its plugins\n\u002Fplugins reload    reload after editing a plugin file\n\u002Fplugins           show each plugin's health: loaded, load_failed or disabled\n",[18,501,499],{"__ignoreMap":165},[14,503,504,507],{},[445,505,506],{},"3. Installed from GitHub or GitLab."," Managed installs are pinned to an exact commit:",[160,509,511],{"className":451,"code":510,"language":453,"meta":165,"style":165},"slo plugin install https:\u002F\u002Fgithub.com\u002Facme\u002Fdeploy-tools --yes\nslo plugin list\nslo plugin upgrade acme-deploy-tools\nslo plugin uninstall acme-deploy-tools\n",[18,512,513,529,538,550],{"__ignoreMap":165},[169,514,515,517,520,523,526],{"class":171,"line":172},[169,516,460],{"class":275},[169,518,519],{"class":227}," plugin",[169,521,522],{"class":227}," install",[169,524,525],{"class":227}," https:\u002F\u002Fgithub.com\u002Facme\u002Fdeploy-tools",[169,527,528],{"class":217}," --yes\n",[169,530,531,533,535],{"class":171,"line":189},[169,532,460],{"class":275},[169,534,519],{"class":227},[169,536,537],{"class":227}," list\n",[169,539,540,542,544,547],{"class":171,"line":196},[169,541,460],{"class":275},[169,543,519],{"class":227},[169,545,546],{"class":227}," upgrade",[169,548,549],{"class":227}," acme-deploy-tools\n",[169,551,552,554,556,559],{"class":171,"line":209},[169,553,460],{"class":275},[169,555,519],{"class":227},[169,557,558],{"class":227}," uninstall",[169,560,549],{"class":227},[14,562,563,564,567,568,571,572,575],{},"The plugin id comes from the URL (",[18,565,566],{},"owner-repo","). Installing never runs the plugin's code, only copies the files its manifest lists. If an upgrade fails partway, Slo restores the previous version. Sources must be ",[18,569,570],{},"https:\u002F\u002Fgithub.com\u002Fowner\u002Frepo"," or ",[18,573,574],{},"https:\u002F\u002Fgitlab.com\u002Fowner\u002Frepo",".",[14,577,578,579,582],{},"Load order is installed plugins, then trusted project plugins, then ",[18,580,581],{},"--plugin"," paths.",[41,584,586],{"id":585},"turning-plugins-off","Turning plugins off",[588,589,590,602],"ul",{},[591,592,593,594,597,598,601],"li",{},"For one run: ",[18,595,596],{},"SLO_PLUGINS_DISABLED=1 slo",". The value has to be exactly ",[18,599,600],{},"1",". Sub-agents inherit it.",[591,603,604,605,608,609,575],{},"For good: ",[18,606,607],{},"slo plugin uninstall \u003Cid>",", or delete the file from ",[18,610,493],{},[41,612,614],{"id":613},"what-stays-out-of-the-models-context","What stays out of the model's context",[14,616,617],{},"Renderer output, footer status and live tool output snapshots are for your eyes only. A plugin reaches the model through three routes: a tool it registers, a tool result it rewrites, or a message it adds before a turn.",[41,619,621],{"id":620},"when-a-plugin-misbehaves","When a plugin misbehaves",[14,623,624,625,628,629,632,633,636],{},"A plugin that throws, or an interceptor that takes longer than 5 seconds, is treated as having no opinion. The run carries on and the failure shows up in ",[18,626,627],{},"\u002Fplugins",". Raise the limit with ",[18,630,631],{},"SLO_PLUGIN_INTERCEPTION_TIMEOUT_SECONDS"," if a check genuinely needs longer. Event watchers registered with ",[18,634,635],{},"slo.on"," have no timeout, so keep them quick.",[638,639,640],"style",{},"html pre.shiki code .sWyjQ, html code.shiki .sWyjQ{--shiki-default:#FF9492}html pre.shiki code .sMAXC, html code.shiki .sMAXC{--shiki-default:#F0F3F6}html pre.shiki code .sCcAr, html code.shiki .sCcAr{--shiki-default:#91CBFF}html pre.shiki code .sTRMh, html code.shiki .sTRMh{--shiki-default:#ADDCFF}html pre.shiki code .sj_SG, html code.shiki .sj_SG{--shiki-default:#DBB7FF}html pre.shiki code .s_sBn, html code.shiki .s_sBn{--shiki-default:#FFB757}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":165,"searchDepth":189,"depth":189,"links":642},[643,644,645,646,647,648],{"id":43,"depth":189,"text":44},{"id":150,"depth":189,"text":151},{"id":440,"depth":189,"text":441},{"id":585,"depth":189,"text":586},{"id":613,"depth":189,"text":614},{"id":620,"depth":189,"text":621},"Add tools, slash commands, footer status and safety policies to Slo with a small Python file.","md",{},"\u002Fdocs\u002Ffeatures\u002Fplugins",{"title":5,"description":649},"docs\u002Ffeatures\u002Fplugins","GlGIZGrUWoYvrwGQuIYvEC9d_wzr9HgmfPA9qRgcGgA",1791641282714]