So I built a runtime to fix that.
The problem
If you use Claude Code, Copilot, or Codex, you've probably created Agent Skills, those SKILL.md files that tell the AI what to do.
I had a bunch of them. But they were stuck. I couldn't plug them into a product, trigger them from a webhook, or let any service call them with a POST request.
Each skill was trapped inside the tool that created it.
What I wanted
take a SKILL.md → get a POST /run endpoint
No new framework to learn. No infrastructure to set up. Just point at a skill, configure the model, and deploy.
What I built
Skrun, an open-source runtime that takes Agent Skills and turns them into callable APIs.
skrun init --from-skill ./my-existing-skill
# reads SKILL.md, generates agent.yaml
skrun deploy
# validates, builds, pushes
# → POST http://localhost:4000/api/agents/dev/my-skill/run
Then you call it:
curl -X POST http://localhost:4000/api/agents/dev/code-review/run \
-H "Authorization: Bearer dev-token" \
-H "Content-Type: application/json" \
-d '{"input": {"code": "function add(a,b) { return a + b; }"}}'
{
"status": "completed",
"output": {
"score": 60,
"issues": [
{"severity": "warning", "description": "Use const instead of var"}
],
"review": "Lacks error handling..."
}
}
Multi-model
You pick the provider in agent.yaml, not in your code. Anthropic, OpenAI, Google, Mistral, Groq. If one fails, it falls back to the next.
model:
provider: google
name: gemini-2.5-flash
fallback:
provider: openai
name: gpt-4o
Tool calling
Two approaches.
You can bundle your own CLI tools with the agent. Create a scripts/ directory, write whatever you want (shell, Node, Python), declare them in agent.yaml:
tools:
- name: eslint_check
script: scripts/eslint-check.sh
description: "Run ESLint on JavaScript code"
The LLM calls the tool when it needs to. Skrun executes the script, returns the result. Your agent can run a linter, query a database, call an internal API.
Or use MCP servers. Any MCP server from the npm ecosystem works via npx:
mcp_servers:
- name: browser
transport: stdio
command: npx
args: ["-y", "@playwright/mcp", "--headless"]
Stateful
Agents can persist key-value state across runs. Run the same agent twice, it remembers what happened last time.
Roadmap
v0.1 runs on a local registry server. Next up:
Cloud deploy (the architecture has a RuntimeAdapter interface ready for sandboxed VMs), caller-provided API keys, streaming responses, and a hub to discover and share agents.
The numbers
4 packages (@skrun-dev/schema, cli, runtime, api), 10 CLI commands, 154 tests, 6 demo agents, MIT license.
Try it
npm install -g @skrun-dev/cli
git clone https://github.com/skrun-dev/skrun.git
cd skrun && pnpm install && pnpm build
Set a Google API key in .env, start the registry (pnpm dev:registry), and follow the "Try an example" section in the README.
I'd love feedback on:
The agent.yaml format (does the I/O contract make sense?), the skill import flow, and what agents you'd build with this.
Top comments (3)
This solves a problem I've been bumping into. I've built 14 Claude Skills (SKILL.md + reference files packaged as .skill files) and they're all stuck in the "only works inside Claude" box. Being able to expose them as POST endpoints would open up a lot of integration possibilities — triggering a skill from a webhook, chaining skills together, or building lightweight UIs on top of them.
The multi-model fallback in agent.yaml is a smart design choice. I've had provider outages kill scheduled workflows before, and having that fallback baked into the config rather than handled in application code is way cleaner.
The
skrun init --from-skillflow is exactly what matters here — zero friction to go from an existing SKILL.md to a deployed endpoint. Curious about the stateful agent persistence: is the key-value store local or is there a plan for something like SQLite/Redis backing so it survives container restarts in the cloud deploy phase?Following the repo — excited to see where the cloud runtime and agent hub go.
Thanks! Right now the state store is in-memory so it doesn't survive restarts. For the cloud version it'll be backed by a persistent store. The good thing is it's behind a StateStore interface so it's easy to swap backends.
Smart move abstracting behind an interface early — that's the kind of decision that pays off massively once you need to support SQLite for local dev and something like Redis or Postgres for production. The swap-ability is exactly what makes this production-ready vs. a weekend prototype. Looking forward to the cloud version.