puras-runner 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- puras_runner-0.1.1/.gitignore +31 -0
- puras_runner-0.1.1/LICENSE +21 -0
- puras_runner-0.1.1/PKG-INFO +241 -0
- puras_runner-0.1.1/README.md +221 -0
- puras_runner-0.1.1/pyproject.toml +58 -0
- puras_runner-0.1.1/worker/__init__.py +9 -0
- puras_runner-0.1.1/worker/agent_runner.py +3685 -0
- puras_runner-0.1.1/worker/agent_tool_specs.py +1030 -0
- puras_runner-0.1.1/worker/analytics.py +77 -0
- puras_runner-0.1.1/worker/approvals.py +130 -0
- puras_runner-0.1.1/worker/attachments.py +332 -0
- puras_runner-0.1.1/worker/browser.py +210 -0
- puras_runner-0.1.1/worker/checkpoint.py +108 -0
- puras_runner-0.1.1/worker/config.py +305 -0
- puras_runner-0.1.1/worker/db.py +67 -0
- puras_runner-0.1.1/worker/deployment.py +162 -0
- puras_runner-0.1.1/worker/drive.py +103 -0
- puras_runner-0.1.1/worker/embeddings.py +103 -0
- puras_runner-0.1.1/worker/eval_local.py +199 -0
- puras_runner-0.1.1/worker/eval_runner.py +406 -0
- puras_runner-0.1.1/worker/event_ctx.py +20 -0
- puras_runner-0.1.1/worker/function_runner.py +138 -0
- puras_runner-0.1.1/worker/health.py +92 -0
- puras_runner-0.1.1/worker/llm_models.py +112 -0
- puras_runner-0.1.1/worker/local_run.py +210 -0
- puras_runner-0.1.1/worker/local_server.py +374 -0
- puras_runner-0.1.1/worker/main.py +846 -0
- puras_runner-0.1.1/worker/manifest.py +1125 -0
- puras_runner-0.1.1/worker/memory.py +337 -0
- puras_runner-0.1.1/worker/memory_store.py +571 -0
- puras_runner-0.1.1/worker/pricing.py +83 -0
- puras_runner-0.1.1/worker/proc_env.py +52 -0
- puras_runner-0.1.1/worker/proc_limits.py +67 -0
- puras_runner-0.1.1/worker/prompt_cache.py +234 -0
- puras_runner-0.1.1/worker/providers/__init__.py +58 -0
- puras_runner-0.1.1/worker/providers/anthropic_provider.py +178 -0
- puras_runner-0.1.1/worker/providers/base.py +77 -0
- puras_runner-0.1.1/worker/providers/openrouter_provider.py +237 -0
- puras_runner-0.1.1/worker/queue.py +575 -0
- puras_runner-0.1.1/worker/resources.py +77 -0
- puras_runner-0.1.1/worker/run_context.py +252 -0
- puras_runner-0.1.1/worker/schema_dialect.py +197 -0
- puras_runner-0.1.1/worker/secret_crypto.py +63 -0
- puras_runner-0.1.1/worker/skill_loader.py +328 -0
- puras_runner-0.1.1/worker/storage.py +463 -0
- puras_runner-0.1.1/worker/workdir.py +89 -0
- puras_runner-0.1.1/worker/worker_prompt.md +54 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
env/
|
|
11
|
+
|
|
12
|
+
# Tooling
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
.mypy_cache/
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
.coverage
|
|
17
|
+
htmlcov/
|
|
18
|
+
|
|
19
|
+
# Local runner scratch (workdirs + drive live under the temp dir by default,
|
|
20
|
+
# but ignore any that land in-tree)
|
|
21
|
+
puras-local/
|
|
22
|
+
*.local-drive/
|
|
23
|
+
|
|
24
|
+
# Env / secrets — a local run is BYO key; never commit it
|
|
25
|
+
.env
|
|
26
|
+
.env.*
|
|
27
|
+
|
|
28
|
+
# Editors / OS
|
|
29
|
+
.DS_Store
|
|
30
|
+
.idea/
|
|
31
|
+
.vscode/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PurasAI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: puras-runner
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Puras offline runner — run a skill's agent loop locally on your own LLM key.
|
|
5
|
+
Author: Puras
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: agents,ai,local,offline,puras,runner
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Requires-Dist: anthropic<1,>=0.42
|
|
13
|
+
Requires-Dist: httpx<0.28,>=0.27
|
|
14
|
+
Requires-Dist: jsonschema<5,>=4.20
|
|
15
|
+
Requires-Dist: pydantic-settings<3,>=2.6
|
|
16
|
+
Requires-Dist: pydantic<3,>=2.10
|
|
17
|
+
Requires-Dist: pyyaml<7,>=6.0
|
|
18
|
+
Requires-Dist: structlog>=24.4
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
<div align="center">
|
|
22
|
+
|
|
23
|
+
# Puras — local skill runner
|
|
24
|
+
|
|
25
|
+
**Run [Puras](https://puras.co) AI skills on your own machine, on your own LLM key — no account.**
|
|
26
|
+
|
|
27
|
+
[](./LICENSE)
|
|
28
|
+
[](https://pypi.org/project/puras/)
|
|
29
|
+
[](https://www.python.org/)
|
|
30
|
+
|
|
31
|
+
[Docs](https://puras.co/docs) · [Cloud](https://puras.co) · [Examples](./examples) · [Build a skill](#build-your-own-skill)
|
|
32
|
+
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
A **skill** is a small folder — a prompt, an input/output schema, optional Python
|
|
38
|
+
tools, and evals — that an agent runs end to end. This is the open-source runner
|
|
39
|
+
that executes one entirely **on your laptop**: no Postgres, no bucket, no platform
|
|
40
|
+
API, no sign-up. It's the *same* agent loop the hosted platform runs (one loop,
|
|
41
|
+
two environments), so a skill behaves identically locally and in prod.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install "puras[local]"
|
|
45
|
+
export ANTHROPIC_API_KEY=sk-ant-... # BYO key — you call the provider, you pay the bill
|
|
46
|
+
puras run --local greeter --dir ./examples/hello-world -i name=Ada
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- 🧑💻 **Local-first** — run and iterate on a skill before deploying anything.
|
|
50
|
+
- 🔌 **Local API** — `puras serve` exposes the hosted job API on `localhost`, so you build and test your app offline before you deploy.
|
|
51
|
+
- 🔑 **Bring your own key** — your provider, your bill, nothing billed by a platform.
|
|
52
|
+
- 🪶 **Dependency-light** — the offline path needs no DB/bucket/openai stack.
|
|
53
|
+
- 🔁 **Prod parity** — the same loop and contracts as [Puras Cloud](https://puras.co).
|
|
54
|
+
|
|
55
|
+
## Getting started
|
|
56
|
+
|
|
57
|
+
### Run it locally
|
|
58
|
+
|
|
59
|
+
The whole point of this repo — a skill's agent loop on your machine, on your key:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install "puras[local]" # the puras CLI + the offline runner
|
|
63
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
64
|
+
|
|
65
|
+
# the bundled "hello world" skillpack has two skills: greeter + formatter
|
|
66
|
+
puras run --local greeter --dir ./examples/hello-world -i name="the Puras team"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
From a checkout of this repo instead of PyPI:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install -e . # puras-runner (the runtime)
|
|
73
|
+
pip install -e worker/sdk # the puras CLI + SDK
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### …or use Puras Cloud (recommended for production)
|
|
77
|
+
|
|
78
|
+
The fastest way to run a skill with the **full tool surface** — media generation,
|
|
79
|
+
web search/fetch, shared memory, persistent storage, durable resume, and
|
|
80
|
+
eval suites at scale — is to [sign up free at puras.co](https://puras.co). No
|
|
81
|
+
infrastructure to manage, automatic scaling, and a one-line MCP connect for
|
|
82
|
+
Claude Code. The local runner here gives you the free, offline core; Cloud adds
|
|
83
|
+
the managed, hosted surface for when you ship. The [comparison below](#open-source-vs-cloud)
|
|
84
|
+
spells out exactly which is which.
|
|
85
|
+
|
|
86
|
+
## Run a skill
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
puras run --local <skill> --dir <skillpack> -i KEY=VALUE [-i KEY2=VALUE2 ...]
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- `<skill>` — the skill to run; omit it when the bundle has exactly one.
|
|
93
|
+
- `--dir` — the skillpack bundle root (a folder of `<skill>/skill.yaml`). Defaults to `.`.
|
|
94
|
+
- `-i KEY=VALUE` — an input, repeatable; validated against the skill's `input_schema`.
|
|
95
|
+
- `--model claude/sonnet-4-6` — override the skill's model for this run.
|
|
96
|
+
- `--api-key sk-...` — your LLM key, if it isn't already in the environment.
|
|
97
|
+
|
|
98
|
+
Events stream to your console as the agent works; the final JSON output and a
|
|
99
|
+
token tally (informational — you paid your provider, not Puras) print at the end.
|
|
100
|
+
|
|
101
|
+
Programmatic use is the same loop:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from worker.local_run import run_local
|
|
105
|
+
|
|
106
|
+
res = run_local("./examples/hello-world", {"name": "Ada"}, skill="greeter")
|
|
107
|
+
print(res["output"])
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Run a skill's evals
|
|
111
|
+
|
|
112
|
+
Evals are to a skill what unit tests are to code. If a skill declares an `evals:`
|
|
113
|
+
block, run its suite locally and gate on it:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
puras eval --local content-repurposer --dir ./examples/content-studio --threshold 80
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`check` / `exact_match` / `schema` graders run free; a `rubric` (LLM-as-judge)
|
|
120
|
+
grader runs on your BYO key. `--threshold N` is a CI gate — non-zero exit if the
|
|
121
|
+
pass-rate is below `N`.
|
|
122
|
+
|
|
123
|
+
## Build your app against a local API
|
|
124
|
+
|
|
125
|
+
`puras run --local` answers *"does my skill work?"*. When you're building the
|
|
126
|
+
**app** that calls the skill, you want the other half: a local server that speaks
|
|
127
|
+
the same API your app will hit in production. That's `puras serve`:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
puras serve --dir ./examples/hello-world # → http://127.0.0.1:8787
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
It mirrors the hosted **job API** (`POST /v1/jobs`, `GET /v1/jobs/{id}`,
|
|
134
|
+
`…/events`, `…/spans`) backed by the offline runner — in-memory, zero extra
|
|
135
|
+
dependencies. Point any Puras SDK at it by changing one thing — the base URL —
|
|
136
|
+
and your app runs unchanged, offline, on your own key:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
import puras
|
|
140
|
+
|
|
141
|
+
# api_base is the only thing that differs between local and prod
|
|
142
|
+
client = puras.Client(api_key="local", api_base="http://127.0.0.1:8787", skillpack="local")
|
|
143
|
+
print(client.run("greeter", {"name": "Ada"}))
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { Puras } from "puras";
|
|
148
|
+
const puras = new Puras({ apiKey: "local", apiBase: "http://127.0.0.1:8787", skillpack: "local" });
|
|
149
|
+
console.log(await puras.run("greeter", { name: "Ada" }));
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
…or just curl it:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
curl -s "http://127.0.0.1:8787/v1/jobs?wait=true" \
|
|
156
|
+
-H "content-type: application/json" \
|
|
157
|
+
-d '{"skill": "greeter", "inputs": {"name": "Ada"}}'
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
When you ship, change the base URL to `https://api.puras.co` and `puras deploy` —
|
|
161
|
+
the **same app code** now runs against the managed platform. Auth is open
|
|
162
|
+
locally; `--require-key <token>` emulates API-key auth, and `--host` / `--port`
|
|
163
|
+
change where it binds. (The Python and React-Native SDKs poll, so they work
|
|
164
|
+
as-is; live SSE streaming is a Cloud feature.)
|
|
165
|
+
|
|
166
|
+
## Build your own skill
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
cp -r examples/skillpack-template my-skillpack
|
|
170
|
+
$EDITOR my-skillpack/my-skill/SKILL.md # the prompt
|
|
171
|
+
$EDITOR my-skillpack/my-skill/skill.yaml # schema, model, tools, evals
|
|
172
|
+
puras run --local --dir ./my-skillpack -i topic=otters
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
my-skillpack/
|
|
177
|
+
my-skill/
|
|
178
|
+
SKILL.md # the agent's instructions (system prompt)
|
|
179
|
+
skill.yaml # input/output schema + model + tools + evals
|
|
180
|
+
tools/... # optional deterministic Python tools the agent can call
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
When you're happy, the same bundle deploys to [Puras Cloud](https://puras.co)
|
|
184
|
+
unchanged. See the [docs](https://puras.co/docs) to deploy and call skills over
|
|
185
|
+
the API.
|
|
186
|
+
|
|
187
|
+
## Open-source vs Cloud
|
|
188
|
+
|
|
189
|
+
Puras is **open-core**: the runner — the agent loop and the local tool surface —
|
|
190
|
+
is MIT-licensed and runs fully offline, forever. The hosted platform at
|
|
191
|
+
[puras.co](https://puras.co) is how the project is sustainably funded, and it
|
|
192
|
+
*adds* the managed surface that can't exist on a single laptop. Premium isn't a
|
|
193
|
+
crippled core — it's the capabilities that need real infrastructure.
|
|
194
|
+
|
|
195
|
+
| | **Local runner** (this repo, MIT) | **Puras Cloud** (hosted) |
|
|
196
|
+
| ---------------------------- | ----------------------------------------- | ---------------------------------------------- |
|
|
197
|
+
| Setup & maintenance | `pip install`, you run it | Fully managed, nothing to install |
|
|
198
|
+
| LLM key & billing | Bring your own key, you pay the provider | Managed, usage-based, transparent pricing |
|
|
199
|
+
| Agent loop & local tools | ✓ text, `bash`, file tools, your Python tools, in-process subagents | ✓ same loop |
|
|
200
|
+
| Job API for your app | ✓ `puras serve` — the job API on localhost | ✓ api.puras.co — managed, scaled, durable |
|
|
201
|
+
| Evals (`check`/`schema`/`rubric`) | ✓ per run + offline suites | ✓ + suites at scale, CI gating, version diffs |
|
|
202
|
+
| Media (image/video/audio) | — | ✓ generation + persistence |
|
|
203
|
+
| Web search / fetch / browser | — | ✓ |
|
|
204
|
+
| Shared memory & storage | — | ✓ persistent, workspace-scoped |
|
|
205
|
+
| Durable resume | — | ✓ checkpointed, survives worker restarts |
|
|
206
|
+
| Budgets, tracing, dashboard | console events + a token tally | ✓ spend budgets, OTel spans, run timelines |
|
|
207
|
+
| Marketplace & sharing | — | ✓ |
|
|
208
|
+
| Support | [Issues](../../issues) & [Discussions](../../discussions) | priority / SLA |
|
|
209
|
+
|
|
210
|
+
The hosted-only tools (`worker/agent_runner.py:PLATFORM_ONLY_TOOLS`) are simply
|
|
211
|
+
not offered to the model offline, so a skill that needs them still runs — it just
|
|
212
|
+
won't see those tools locally. The included examples (`hello-world`,
|
|
213
|
+
`skillpack-template`, `content-studio`) use only the local surface and run
|
|
214
|
+
end-to-end offline.
|
|
215
|
+
|
|
216
|
+
## How it works
|
|
217
|
+
|
|
218
|
+
The runner runs the agent on a `LocalRunContext` with `platform_enabled=False`.
|
|
219
|
+
That `RunContext` seam is the whole trick: one agent loop, two environments. The
|
|
220
|
+
offline import path is kept **dependency-light** — no Postgres/bucket/openai at
|
|
221
|
+
import time — and that's enforced by
|
|
222
|
+
`tests/dry/test_local_import_isolation.py`.
|
|
223
|
+
|
|
224
|
+
| Path | What |
|
|
225
|
+
| --- | --- |
|
|
226
|
+
| `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. |
|
|
227
|
+
| `worker/sdk/` | The `puras` package — the CLI and the SDK skills import at runtime. Ships as its own wheel. |
|
|
228
|
+
| `examples/` | Runnable, offline-capable skillpacks. |
|
|
229
|
+
| `tests/` | The dependency-light import-isolation guard, a CLI smoke test, and the `puras serve` API tests. |
|
|
230
|
+
|
|
231
|
+
## Community & contributing
|
|
232
|
+
|
|
233
|
+
Questions, bugs, and skill ideas are welcome in
|
|
234
|
+
[Issues](../../issues) and [Discussions](../../discussions). PRs that improve the
|
|
235
|
+
runner, the docs, or the examples are appreciated.
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
[MIT](./LICENSE). The hosted platform's server-side code is separate and
|
|
240
|
+
commercial — this runner, the SDK, and the examples are MIT and yours to use,
|
|
241
|
+
modify, and self-run.
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# Puras — local skill runner
|
|
4
|
+
|
|
5
|
+
**Run [Puras](https://puras.co) AI skills on your own machine, on your own LLM key — no account.**
|
|
6
|
+
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](https://pypi.org/project/puras/)
|
|
9
|
+
[](https://www.python.org/)
|
|
10
|
+
|
|
11
|
+
[Docs](https://puras.co/docs) · [Cloud](https://puras.co) · [Examples](./examples) · [Build a skill](#build-your-own-skill)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
A **skill** is a small folder — a prompt, an input/output schema, optional Python
|
|
18
|
+
tools, and evals — that an agent runs end to end. This is the open-source runner
|
|
19
|
+
that executes one entirely **on your laptop**: no Postgres, no bucket, no platform
|
|
20
|
+
API, no sign-up. It's the *same* agent loop the hosted platform runs (one loop,
|
|
21
|
+
two environments), so a skill behaves identically locally and in prod.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install "puras[local]"
|
|
25
|
+
export ANTHROPIC_API_KEY=sk-ant-... # BYO key — you call the provider, you pay the bill
|
|
26
|
+
puras run --local greeter --dir ./examples/hello-world -i name=Ada
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- 🧑💻 **Local-first** — run and iterate on a skill before deploying anything.
|
|
30
|
+
- 🔌 **Local API** — `puras serve` exposes the hosted job API on `localhost`, so you build and test your app offline before you deploy.
|
|
31
|
+
- 🔑 **Bring your own key** — your provider, your bill, nothing billed by a platform.
|
|
32
|
+
- 🪶 **Dependency-light** — the offline path needs no DB/bucket/openai stack.
|
|
33
|
+
- 🔁 **Prod parity** — the same loop and contracts as [Puras Cloud](https://puras.co).
|
|
34
|
+
|
|
35
|
+
## Getting started
|
|
36
|
+
|
|
37
|
+
### Run it locally
|
|
38
|
+
|
|
39
|
+
The whole point of this repo — a skill's agent loop on your machine, on your key:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install "puras[local]" # the puras CLI + the offline runner
|
|
43
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
44
|
+
|
|
45
|
+
# the bundled "hello world" skillpack has two skills: greeter + formatter
|
|
46
|
+
puras run --local greeter --dir ./examples/hello-world -i name="the Puras team"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
From a checkout of this repo instead of PyPI:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install -e . # puras-runner (the runtime)
|
|
53
|
+
pip install -e worker/sdk # the puras CLI + SDK
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### …or use Puras Cloud (recommended for production)
|
|
57
|
+
|
|
58
|
+
The fastest way to run a skill with the **full tool surface** — media generation,
|
|
59
|
+
web search/fetch, shared memory, persistent storage, durable resume, and
|
|
60
|
+
eval suites at scale — is to [sign up free at puras.co](https://puras.co). No
|
|
61
|
+
infrastructure to manage, automatic scaling, and a one-line MCP connect for
|
|
62
|
+
Claude Code. The local runner here gives you the free, offline core; Cloud adds
|
|
63
|
+
the managed, hosted surface for when you ship. The [comparison below](#open-source-vs-cloud)
|
|
64
|
+
spells out exactly which is which.
|
|
65
|
+
|
|
66
|
+
## Run a skill
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
puras run --local <skill> --dir <skillpack> -i KEY=VALUE [-i KEY2=VALUE2 ...]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- `<skill>` — the skill to run; omit it when the bundle has exactly one.
|
|
73
|
+
- `--dir` — the skillpack bundle root (a folder of `<skill>/skill.yaml`). Defaults to `.`.
|
|
74
|
+
- `-i KEY=VALUE` — an input, repeatable; validated against the skill's `input_schema`.
|
|
75
|
+
- `--model claude/sonnet-4-6` — override the skill's model for this run.
|
|
76
|
+
- `--api-key sk-...` — your LLM key, if it isn't already in the environment.
|
|
77
|
+
|
|
78
|
+
Events stream to your console as the agent works; the final JSON output and a
|
|
79
|
+
token tally (informational — you paid your provider, not Puras) print at the end.
|
|
80
|
+
|
|
81
|
+
Programmatic use is the same loop:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from worker.local_run import run_local
|
|
85
|
+
|
|
86
|
+
res = run_local("./examples/hello-world", {"name": "Ada"}, skill="greeter")
|
|
87
|
+
print(res["output"])
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Run a skill's evals
|
|
91
|
+
|
|
92
|
+
Evals are to a skill what unit tests are to code. If a skill declares an `evals:`
|
|
93
|
+
block, run its suite locally and gate on it:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
puras eval --local content-repurposer --dir ./examples/content-studio --threshold 80
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`check` / `exact_match` / `schema` graders run free; a `rubric` (LLM-as-judge)
|
|
100
|
+
grader runs on your BYO key. `--threshold N` is a CI gate — non-zero exit if the
|
|
101
|
+
pass-rate is below `N`.
|
|
102
|
+
|
|
103
|
+
## Build your app against a local API
|
|
104
|
+
|
|
105
|
+
`puras run --local` answers *"does my skill work?"*. When you're building the
|
|
106
|
+
**app** that calls the skill, you want the other half: a local server that speaks
|
|
107
|
+
the same API your app will hit in production. That's `puras serve`:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
puras serve --dir ./examples/hello-world # → http://127.0.0.1:8787
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
It mirrors the hosted **job API** (`POST /v1/jobs`, `GET /v1/jobs/{id}`,
|
|
114
|
+
`…/events`, `…/spans`) backed by the offline runner — in-memory, zero extra
|
|
115
|
+
dependencies. Point any Puras SDK at it by changing one thing — the base URL —
|
|
116
|
+
and your app runs unchanged, offline, on your own key:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
import puras
|
|
120
|
+
|
|
121
|
+
# api_base is the only thing that differs between local and prod
|
|
122
|
+
client = puras.Client(api_key="local", api_base="http://127.0.0.1:8787", skillpack="local")
|
|
123
|
+
print(client.run("greeter", {"name": "Ada"}))
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { Puras } from "puras";
|
|
128
|
+
const puras = new Puras({ apiKey: "local", apiBase: "http://127.0.0.1:8787", skillpack: "local" });
|
|
129
|
+
console.log(await puras.run("greeter", { name: "Ada" }));
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
…or just curl it:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
curl -s "http://127.0.0.1:8787/v1/jobs?wait=true" \
|
|
136
|
+
-H "content-type: application/json" \
|
|
137
|
+
-d '{"skill": "greeter", "inputs": {"name": "Ada"}}'
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
When you ship, change the base URL to `https://api.puras.co` and `puras deploy` —
|
|
141
|
+
the **same app code** now runs against the managed platform. Auth is open
|
|
142
|
+
locally; `--require-key <token>` emulates API-key auth, and `--host` / `--port`
|
|
143
|
+
change where it binds. (The Python and React-Native SDKs poll, so they work
|
|
144
|
+
as-is; live SSE streaming is a Cloud feature.)
|
|
145
|
+
|
|
146
|
+
## Build your own skill
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
cp -r examples/skillpack-template my-skillpack
|
|
150
|
+
$EDITOR my-skillpack/my-skill/SKILL.md # the prompt
|
|
151
|
+
$EDITOR my-skillpack/my-skill/skill.yaml # schema, model, tools, evals
|
|
152
|
+
puras run --local --dir ./my-skillpack -i topic=otters
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
my-skillpack/
|
|
157
|
+
my-skill/
|
|
158
|
+
SKILL.md # the agent's instructions (system prompt)
|
|
159
|
+
skill.yaml # input/output schema + model + tools + evals
|
|
160
|
+
tools/... # optional deterministic Python tools the agent can call
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
When you're happy, the same bundle deploys to [Puras Cloud](https://puras.co)
|
|
164
|
+
unchanged. See the [docs](https://puras.co/docs) to deploy and call skills over
|
|
165
|
+
the API.
|
|
166
|
+
|
|
167
|
+
## Open-source vs Cloud
|
|
168
|
+
|
|
169
|
+
Puras is **open-core**: the runner — the agent loop and the local tool surface —
|
|
170
|
+
is MIT-licensed and runs fully offline, forever. The hosted platform at
|
|
171
|
+
[puras.co](https://puras.co) is how the project is sustainably funded, and it
|
|
172
|
+
*adds* the managed surface that can't exist on a single laptop. Premium isn't a
|
|
173
|
+
crippled core — it's the capabilities that need real infrastructure.
|
|
174
|
+
|
|
175
|
+
| | **Local runner** (this repo, MIT) | **Puras Cloud** (hosted) |
|
|
176
|
+
| ---------------------------- | ----------------------------------------- | ---------------------------------------------- |
|
|
177
|
+
| Setup & maintenance | `pip install`, you run it | Fully managed, nothing to install |
|
|
178
|
+
| LLM key & billing | Bring your own key, you pay the provider | Managed, usage-based, transparent pricing |
|
|
179
|
+
| Agent loop & local tools | ✓ text, `bash`, file tools, your Python tools, in-process subagents | ✓ same loop |
|
|
180
|
+
| Job API for your app | ✓ `puras serve` — the job API on localhost | ✓ api.puras.co — managed, scaled, durable |
|
|
181
|
+
| Evals (`check`/`schema`/`rubric`) | ✓ per run + offline suites | ✓ + suites at scale, CI gating, version diffs |
|
|
182
|
+
| Media (image/video/audio) | — | ✓ generation + persistence |
|
|
183
|
+
| Web search / fetch / browser | — | ✓ |
|
|
184
|
+
| Shared memory & storage | — | ✓ persistent, workspace-scoped |
|
|
185
|
+
| Durable resume | — | ✓ checkpointed, survives worker restarts |
|
|
186
|
+
| Budgets, tracing, dashboard | console events + a token tally | ✓ spend budgets, OTel spans, run timelines |
|
|
187
|
+
| Marketplace & sharing | — | ✓ |
|
|
188
|
+
| Support | [Issues](../../issues) & [Discussions](../../discussions) | priority / SLA |
|
|
189
|
+
|
|
190
|
+
The hosted-only tools (`worker/agent_runner.py:PLATFORM_ONLY_TOOLS`) are simply
|
|
191
|
+
not offered to the model offline, so a skill that needs them still runs — it just
|
|
192
|
+
won't see those tools locally. The included examples (`hello-world`,
|
|
193
|
+
`skillpack-template`, `content-studio`) use only the local surface and run
|
|
194
|
+
end-to-end offline.
|
|
195
|
+
|
|
196
|
+
## How it works
|
|
197
|
+
|
|
198
|
+
The runner runs the agent on a `LocalRunContext` with `platform_enabled=False`.
|
|
199
|
+
That `RunContext` seam is the whole trick: one agent loop, two environments. The
|
|
200
|
+
offline import path is kept **dependency-light** — no Postgres/bucket/openai at
|
|
201
|
+
import time — and that's enforced by
|
|
202
|
+
`tests/dry/test_local_import_isolation.py`.
|
|
203
|
+
|
|
204
|
+
| Path | What |
|
|
205
|
+
| --- | --- |
|
|
206
|
+
| `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. |
|
|
207
|
+
| `worker/sdk/` | The `puras` package — the CLI and the SDK skills import at runtime. Ships as its own wheel. |
|
|
208
|
+
| `examples/` | Runnable, offline-capable skillpacks. |
|
|
209
|
+
| `tests/` | The dependency-light import-isolation guard, a CLI smoke test, and the `puras serve` API tests. |
|
|
210
|
+
|
|
211
|
+
## Community & contributing
|
|
212
|
+
|
|
213
|
+
Questions, bugs, and skill ideas are welcome in
|
|
214
|
+
[Issues](../../issues) and [Discussions](../../discussions). PRs that improve the
|
|
215
|
+
runner, the docs, or the examples are appreciated.
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
[MIT](./LICENSE). The hosted platform's server-side code is separate and
|
|
220
|
+
commercial — this runner, the SDK, and the examples are MIT and yours to use,
|
|
221
|
+
modify, and self-run.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Publishable `puras-runner` distribution: the OPEN-SOURCE offline runner — the
|
|
2
|
+
# same `worker` runtime the hosted platform runs, made pip-installable so
|
|
3
|
+
# `puras run --local` works from a plain `pip install` with no repo checkout.
|
|
4
|
+
#
|
|
5
|
+
# The dependency list here is the DEPENDENCY-LIGHT offline surface (no Postgres,
|
|
6
|
+
# no bucket, no openai SDK): the hosted-only modules (db / queue / storage /
|
|
7
|
+
# memory / main) still live in the package but are never imported on the offline
|
|
8
|
+
# path (every call site is gated on `ctx.platform_enabled`), so they don't drag
|
|
9
|
+
# their heavy deps into a local install. The HOSTED worker image installs the
|
|
10
|
+
# full stack from `requirements.txt` instead — this wheel is for local runs.
|
|
11
|
+
#
|
|
12
|
+
# Build/publish: cd worker && uv build && uv publish
|
|
13
|
+
# Local install: pip install ./worker (gives `import worker` + the runner)
|
|
14
|
+
# Via the SDK: pip install puras[local] (pulls this as an extra)
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["hatchling"]
|
|
18
|
+
build-backend = "hatchling.build"
|
|
19
|
+
|
|
20
|
+
[project]
|
|
21
|
+
name = "puras-runner"
|
|
22
|
+
dynamic = ["version"]
|
|
23
|
+
description = "Puras offline runner — run a skill's agent loop locally on your own LLM key."
|
|
24
|
+
readme = "README.md"
|
|
25
|
+
requires-python = ">=3.10"
|
|
26
|
+
authors = [{ name = "Puras" }]
|
|
27
|
+
keywords = ["puras", "agents", "ai", "local", "runner", "offline"]
|
|
28
|
+
classifiers = [
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Environment :: Console",
|
|
31
|
+
"Intended Audience :: Developers",
|
|
32
|
+
]
|
|
33
|
+
# The offline import path (worker.local_run → agent loop) needs only these.
|
|
34
|
+
# Verified by tests/dry/test_local_import_isolation.py, which blocks the heavy
|
|
35
|
+
# stack (sqlalchemy / asyncpg / storage3 / posthog / openai) and imports the
|
|
36
|
+
# whole local graph.
|
|
37
|
+
dependencies = [
|
|
38
|
+
"anthropic>=0.42,<1",
|
|
39
|
+
"pydantic>=2.10,<3",
|
|
40
|
+
"pydantic-settings>=2.6,<3",
|
|
41
|
+
"jsonschema>=4.20,<5",
|
|
42
|
+
"structlog>=24.4",
|
|
43
|
+
"httpx>=0.27,<0.28",
|
|
44
|
+
"pyyaml>=6.0,<7",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.hatch.version]
|
|
48
|
+
path = "worker/__init__.py"
|
|
49
|
+
|
|
50
|
+
[tool.hatch.build.targets.wheel]
|
|
51
|
+
packages = ["worker"]
|
|
52
|
+
# The publishable `puras` SDK is rooted inside this tree (worker/sdk) and ships
|
|
53
|
+
# as its OWN wheel — keep it out of the runner wheel.
|
|
54
|
+
exclude = ["worker/sdk", "**/__pycache__", "**/*.pyc"]
|
|
55
|
+
|
|
56
|
+
[tool.hatch.build.targets.sdist]
|
|
57
|
+
include = ["worker", "pyproject.toml"]
|
|
58
|
+
exclude = ["worker/sdk", "**/__pycache__", "**/*.pyc"]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Puras runtime (the open-source runner).
|
|
2
|
+
|
|
3
|
+
Same package the hosted worker runs; also the dependency-light **offline runner**
|
|
4
|
+
behind `puras run --local` (see `worker.local_run`). The hosted-only modules
|
|
5
|
+
(db / queue / storage / memory) pull the platform stack, but they're never
|
|
6
|
+
imported on the offline path, so `pip install puras[local]` stays thin.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.1"
|