GridOS loads every subdirectory of this folder as a plugin — a small Python module that can register custom formulas, specialist agents, and provider models.
plugins/my_plugin/
manifest.json # name + description; surfaced in the SaaS marketplace
plugin.py # defines register(kernel)
manifest.json:
{
"name": "My Plugin",
"description": "One-line pitch shown on the marketplace card.",
"category": "finance",
"author": "Your Name",
"version": "0.1.0"
}plugin.py:
def register(kernel):
@kernel.formula("BLACK_SCHOLES")
def black_scholes(S, K, T, r, sigma, option_type="call"):
...
kernel.agent({
"id": "real_estate",
"display_name": "Real Estate Copilot",
"router_description": "cap rate, NOI, cash-on-cash, DSCR",
"system_prompt": "You are a real-estate underwriting specialist. ..."
})
kernel.model({
"id": "my-org/custom-model-v1",
"provider": "openrouter",
"display_name": "My Custom Model",
"description": "OpenRouter-hosted model shipped with this plugin."
})That's it. Start the server, and GridOS picks up your plugin on boot.
| Seam | Call | What it does |
|---|---|---|
| Formulas | @kernel.formula("NAME") |
Registers a callable into the global formula registry. Available from any cell as =NAME(...). Underlying registry lives in core/functions.py. |
| Agents | kernel.agent({...}) |
Adds a specialist agent the router can pick. Same shape as agents/*.json. Required keys: id, system_prompt. |
| Models | kernel.model({...}) |
Extends core/providers/catalog.py. Required keys: id, provider, display_name, description. |
- Formulas:
core/functions.py— the formula registry +FormulaEvaluator. Your formula becomes a first-class primitive alongsideSUM,AVERAGE, etc. - Agents:
agents/__init__.py— how built-in agents load. Plugin-registered agents are merged into the sameAGENTSdict at boot. - Model catalog:
core/providers/catalog.py— the static list the chat composer reads. New entries appear in the picker on next page load (assuming the owning provider has a key configured). - Plugin loader:
core/plugins.py— thePluginKernelfacade anddiscover_and_load()walker.
Plugins run in-process with full Python access — no sandbox, no capability system. That means:
- OSS / self-hosted: you own the process, so you own the trust decision. Plugins are auto-loaded on boot.
- Hosted SaaS (gridos.onrender.com): plugin loading is gated by
GRIDOS_PLUGINS_ENABLED; only operator-vetted plugins ship in this directory. The in-app Marketplace lets users toggle which vetted plugins apply to their workbook — it's a visibility/discovery layer, not a sandbox.
One bad plugin can't take down the server. If a plugin's register() raises, the loader records the error and continues. Check GET /plugins at runtime:
{
"loaded": [
{"slug": "hello_world", "name": "Hello World", "formulas": ["GREET"], "agents": ["greeter"]}
],
"errors": [
{"plugin": "broken_example", "error": "register() failed: NameError: ..."}
]
}hello_world/— minimal template: one formula + one agent.black_scholes/—=BLACK_SCHOLES(S, K, T, r, sigma, type)options pricer.real_estate/— domain specialist agent +=CAP_RATE/=DSCRprimitives.
Copy any of them as a starting point.