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.
Files changed (47) hide show
  1. puras_runner-0.1.1/.gitignore +31 -0
  2. puras_runner-0.1.1/LICENSE +21 -0
  3. puras_runner-0.1.1/PKG-INFO +241 -0
  4. puras_runner-0.1.1/README.md +221 -0
  5. puras_runner-0.1.1/pyproject.toml +58 -0
  6. puras_runner-0.1.1/worker/__init__.py +9 -0
  7. puras_runner-0.1.1/worker/agent_runner.py +3685 -0
  8. puras_runner-0.1.1/worker/agent_tool_specs.py +1030 -0
  9. puras_runner-0.1.1/worker/analytics.py +77 -0
  10. puras_runner-0.1.1/worker/approvals.py +130 -0
  11. puras_runner-0.1.1/worker/attachments.py +332 -0
  12. puras_runner-0.1.1/worker/browser.py +210 -0
  13. puras_runner-0.1.1/worker/checkpoint.py +108 -0
  14. puras_runner-0.1.1/worker/config.py +305 -0
  15. puras_runner-0.1.1/worker/db.py +67 -0
  16. puras_runner-0.1.1/worker/deployment.py +162 -0
  17. puras_runner-0.1.1/worker/drive.py +103 -0
  18. puras_runner-0.1.1/worker/embeddings.py +103 -0
  19. puras_runner-0.1.1/worker/eval_local.py +199 -0
  20. puras_runner-0.1.1/worker/eval_runner.py +406 -0
  21. puras_runner-0.1.1/worker/event_ctx.py +20 -0
  22. puras_runner-0.1.1/worker/function_runner.py +138 -0
  23. puras_runner-0.1.1/worker/health.py +92 -0
  24. puras_runner-0.1.1/worker/llm_models.py +112 -0
  25. puras_runner-0.1.1/worker/local_run.py +210 -0
  26. puras_runner-0.1.1/worker/local_server.py +374 -0
  27. puras_runner-0.1.1/worker/main.py +846 -0
  28. puras_runner-0.1.1/worker/manifest.py +1125 -0
  29. puras_runner-0.1.1/worker/memory.py +337 -0
  30. puras_runner-0.1.1/worker/memory_store.py +571 -0
  31. puras_runner-0.1.1/worker/pricing.py +83 -0
  32. puras_runner-0.1.1/worker/proc_env.py +52 -0
  33. puras_runner-0.1.1/worker/proc_limits.py +67 -0
  34. puras_runner-0.1.1/worker/prompt_cache.py +234 -0
  35. puras_runner-0.1.1/worker/providers/__init__.py +58 -0
  36. puras_runner-0.1.1/worker/providers/anthropic_provider.py +178 -0
  37. puras_runner-0.1.1/worker/providers/base.py +77 -0
  38. puras_runner-0.1.1/worker/providers/openrouter_provider.py +237 -0
  39. puras_runner-0.1.1/worker/queue.py +575 -0
  40. puras_runner-0.1.1/worker/resources.py +77 -0
  41. puras_runner-0.1.1/worker/run_context.py +252 -0
  42. puras_runner-0.1.1/worker/schema_dialect.py +197 -0
  43. puras_runner-0.1.1/worker/secret_crypto.py +63 -0
  44. puras_runner-0.1.1/worker/skill_loader.py +328 -0
  45. puras_runner-0.1.1/worker/storage.py +463 -0
  46. puras_runner-0.1.1/worker/workdir.py +89 -0
  47. 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: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
28
+ [![PyPI](https://img.shields.io/pypi/v/puras.svg)](https://pypi.org/project/puras/)
29
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](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: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
8
+ [![PyPI](https://img.shields.io/pypi/v/puras.svg)](https://pypi.org/project/puras/)
9
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](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"