Puras offline runner — run a skill's agent loop locally on your own LLM key.
Project description
Puras — local skill runner
Run Puras AI skills on your own machine, on your own LLM key — no account.
Docs · Cloud · Examples · Build a skill
A skill is a small folder — a prompt, an input/output schema, optional Python tools, and evals — that an agent runs end to end. This is the open-source runner that executes one entirely on your laptop: no Postgres, no bucket, no platform API, no sign-up. It's the same agent loop the hosted platform runs (one loop, two environments), so a skill behaves identically locally and in prod.
pip install "puras[local]"
export ANTHROPIC_API_KEY=sk-ant-... # BYO key — you call the provider, you pay the bill
puras run --local greeter --dir ./examples/hello-world -i name=Ada
- 🧑💻 Local-first — run and iterate on a skill before deploying anything.
- 🔌 Local API —
puras serveexposes the hosted job API onlocalhost, so you build and test your app offline before you deploy. - 🔑 Bring your own key — your provider, your bill, nothing billed by a platform.
- 🪶 Dependency-light — the offline path needs no DB/bucket/openai stack.
- 🔁 Prod parity — the same loop and contracts as Puras Cloud.
Getting started
Run it locally
The whole point of this repo — a skill's agent loop on your machine, on your key:
pip install "puras[local]" # the puras CLI + the offline runner
export ANTHROPIC_API_KEY=sk-ant-...
# the bundled "hello world" skillpack has two skills: greeter + formatter
puras run --local greeter --dir ./examples/hello-world -i name="the Puras team"
From a checkout of this repo instead of PyPI:
pip install -e . # puras-runner (the runtime)
pip install -e worker/sdk # the puras CLI + SDK
…or use Puras Cloud (recommended for production)
The fastest way to run a skill with the full tool surface — media generation, web search/fetch, shared memory, persistent storage, durable resume, and eval suites at scale — is to sign up free at puras.co. No infrastructure to manage, automatic scaling, and a one-line MCP connect for Claude Code. The local runner here gives you the free, offline core; Cloud adds the managed, hosted surface for when you ship. The comparison below spells out exactly which is which.
Run a skill
puras run --local <skill> --dir <skillpack> -i KEY=VALUE [-i KEY2=VALUE2 ...]
<skill>— the skill to run; omit it when the bundle has exactly one.--dir— the skillpack bundle root (a folder of<skill>/skill.yaml). Defaults to..-i KEY=VALUE— an input, repeatable; validated against the skill'sinput_schema.--model claude/sonnet-4-6— override the skill's model for this run.--api-key sk-...— your LLM key, if it isn't already in the environment.
Events stream to your console as the agent works; the final JSON output and a token tally (informational — you paid your provider, not Puras) print at the end.
Programmatic use is the same loop:
from worker.local_run import run_local
res = run_local("./examples/hello-world", {"name": "Ada"}, skill="greeter")
print(res["output"])
Run a skill's evals
Evals are to a skill what unit tests are to code. If a skill declares an evals:
block, run its suite locally and gate on it:
puras eval --local content-repurposer --dir ./examples/content-studio --threshold 80
check / exact_match / schema graders run free; a rubric (LLM-as-judge)
grader runs on your BYO key. --threshold N is a CI gate — non-zero exit if the
pass-rate is below N.
Build your app against a local API
puras run --local answers "does my skill work?". When you're building the
app that calls the skill, you want the other half: a local server that speaks
the same API your app will hit in production. That's puras serve:
puras serve --dir ./examples/hello-world # → http://127.0.0.1:8787
It mirrors the hosted job API (POST /v1/jobs, GET /v1/jobs/{id},
…/events, …/spans) backed by the offline runner — in-memory, zero extra
dependencies. Point any Puras SDK at it by changing one thing — the base URL —
and your app runs unchanged, offline, on your own key:
import puras
# api_base is the only thing that differs between local and prod
client = puras.Client(api_key="local", api_base="http://127.0.0.1:8787", skillpack="local")
print(client.run("greeter", {"name": "Ada"}))
import { Puras } from "puras";
const puras = new Puras({ apiKey: "local", apiBase: "http://127.0.0.1:8787", skillpack: "local" });
console.log(await puras.run("greeter", { name: "Ada" }));
…or just curl it:
curl -s "http://127.0.0.1:8787/v1/jobs?wait=true" \
-H "content-type: application/json" \
-d '{"skill": "greeter", "inputs": {"name": "Ada"}}'
When you ship, change the base URL to https://api.puras.co and puras deploy —
the same app code now runs against the managed platform. Auth is open
locally; --require-key <token> emulates API-key auth, and --host / --port
change where it binds. (The Python and React-Native SDKs poll, so they work
as-is; live SSE streaming is a Cloud feature.)
Build your own skill
cp -r examples/skillpack-template my-skillpack
$EDITOR my-skillpack/my-skill/SKILL.md # the prompt
$EDITOR my-skillpack/my-skill/skill.yaml # schema, model, tools, evals
puras run --local --dir ./my-skillpack -i topic=otters
my-skillpack/
my-skill/
SKILL.md # the agent's instructions (system prompt)
skill.yaml # input/output schema + model + tools + evals
tools/... # optional deterministic Python tools the agent can call
When you're happy, the same bundle deploys to Puras Cloud unchanged. See the docs to deploy and call skills over the API.
Open-source vs Cloud
Puras is open-core: the runner — the agent loop and the local tool surface — is MIT-licensed and runs fully offline, forever. The hosted platform at puras.co is how the project is sustainably funded, and it adds the managed surface that can't exist on a single laptop. Premium isn't a crippled core — it's the capabilities that need real infrastructure.
| Local runner (this repo, MIT) | Puras Cloud (hosted) | |
|---|---|---|
| Setup & maintenance | pip install, you run it |
Fully managed, nothing to install |
| LLM key & billing | Bring your own key, you pay the provider | Managed, usage-based, transparent pricing |
| Agent loop & local tools | ✓ text, bash, file tools, your Python tools, in-process subagents |
✓ same loop |
| Job API for your app | ✓ puras serve — the job API on localhost |
✓ api.puras.co — managed, scaled, durable |
Evals (check/schema/rubric) |
✓ per run + offline suites | ✓ + suites at scale, CI gating, version diffs |
| Media (image/video/audio) | — | ✓ generation + persistence |
| Web search / fetch / browser | — | ✓ |
| Shared memory & storage | — | ✓ persistent, workspace-scoped |
| Durable resume | — | ✓ checkpointed, survives worker restarts |
| Budgets, tracing, dashboard | console events + a token tally | ✓ spend budgets, OTel spans, run timelines |
| Marketplace & sharing | — | ✓ |
| Support | Issues & Discussions | priority / SLA |
The hosted-only tools (worker/agent_runner.py:PLATFORM_ONLY_TOOLS) are simply
not offered to the model offline, so a skill that needs them still runs — it just
won't see those tools locally. The included examples (hello-world,
skillpack-template, content-studio) use only the local surface and run
end-to-end offline.
How it works
The runner runs the agent on a LocalRunContext with platform_enabled=False.
That RunContext seam is the whole trick: one agent loop, two environments. The
offline import path is kept dependency-light — no Postgres/bucket/openai at
import time — and that's enforced by
tests/dry/test_local_import_isolation.py.
| Path | What |
|---|---|
worker/ |
The puras-runner runtime — the agent loop (agent_runner.py), the RunContext seam, the local entrypoint (local_run.py), skill loading, the eval runner. |
worker/sdk/ |
The puras package — the CLI and the SDK skills import at runtime. Ships as its own wheel. |
examples/ |
Runnable, offline-capable skillpacks. |
tests/ |
The dependency-light import-isolation guard, a CLI smoke test, and the puras serve API tests. |
Community & contributing
Questions, bugs, and skill ideas are welcome in Issues and Discussions. PRs that improve the runner, the docs, or the examples are appreciated.
License
MIT. The hosted platform's server-side code is separate and commercial — this runner, the SDK, and the examples are MIT and yours to use, modify, and self-run.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file puras_runner-0.1.1.tar.gz.
File metadata
- Download URL: puras_runner-0.1.1.tar.gz
- Upload date:
- Size: 179.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3300a4dd648daeefe20c1717a0970d4a28b293c26c56f60d5a7f95ac014a3c20
|
|
| MD5 |
7bb7071569ec56a8b14726cfa6a72001
|
|
| BLAKE2b-256 |
75ff83c5bcc650436e9605974034d3c86de70c01c16dc9b6214b6631831ecc6d
|
Provenance
The following attestation bundles were made for puras_runner-0.1.1.tar.gz:
Publisher:
publish.yml on PurasAI/puras
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
puras_runner-0.1.1.tar.gz -
Subject digest:
3300a4dd648daeefe20c1717a0970d4a28b293c26c56f60d5a7f95ac014a3c20 - Sigstore transparency entry: 1817340641
- Sigstore integration time:
-
Permalink:
PurasAI/puras@92ff1e17f2d7ebd86e818d98be336ccee05fd887 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/PurasAI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@92ff1e17f2d7ebd86e818d98be336ccee05fd887 -
Trigger Event:
push
-
Statement type:
File details
Details for the file puras_runner-0.1.1-py3-none-any.whl.
File metadata
- Download URL: puras_runner-0.1.1-py3-none-any.whl
- Upload date:
- Size: 201.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13f0a8d67ba7706334b7d05d79eaa3e78ac78f7f5bf6418b87d4c90bee0405af
|
|
| MD5 |
487d905223bb39791dffa9f7a343edbb
|
|
| BLAKE2b-256 |
153cc1b2172379e89ab095dc5461abe867a7f733eca641b7e44c8142a0129bb4
|
Provenance
The following attestation bundles were made for puras_runner-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on PurasAI/puras
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
puras_runner-0.1.1-py3-none-any.whl -
Subject digest:
13f0a8d67ba7706334b7d05d79eaa3e78ac78f7f5bf6418b87d4c90bee0405af - Sigstore transparency entry: 1817340714
- Sigstore integration time:
-
Permalink:
PurasAI/puras@92ff1e17f2d7ebd86e818d98be336ccee05fd887 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/PurasAI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@92ff1e17f2d7ebd86e818d98be336ccee05fd887 -
Trigger Event:
push
-
Statement type: