pyyol 1.2.0 → 1.3.0
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.
- package/README.md +37 -9
- package/dist/adapter.d.ts +3 -4
- package/dist/adapter.js +8 -2
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +202 -11
- package/dist/config.d.ts +0 -1
- package/dist/config.js +0 -1
- package/dist/credentials.d.ts +0 -1
- package/dist/credentials.js +0 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.js +4 -2
- package/dist/install-ping.d.ts +2 -0
- package/dist/install-ping.js +42 -0
- package/dist/instrument.d.ts +41 -0
- package/dist/instrument.js +276 -0
- package/dist/login.d.ts +0 -1
- package/dist/login.js +0 -1
- package/dist/mode.d.ts +0 -1
- package/dist/mode.js +0 -1
- package/dist/models.d.ts +4 -1
- package/dist/models.js +0 -1
- package/dist/pricing.d.ts +27 -0
- package/dist/pricing.js +110 -0
- package/dist/rules.d.ts +0 -1
- package/dist/rules.js +0 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +32 -8
- package/dist/server.d.ts +0 -1
- package/dist/server.js +10 -3
- package/dist/signing.d.ts +0 -1
- package/dist/signing.js +0 -1
- package/dist/simulator.d.ts +0 -1
- package/dist/simulator.js +0 -1
- package/dist/telemetry.d.ts +51 -1
- package/dist/telemetry.js +68 -1
- package/dist/version.d.ts +1 -2
- package/dist/version.js +1 -2
- package/package.json +1 -1
- package/rules/llms-full.txt +258 -26
- package/dist/adapter.d.ts.map +0 -1
- package/dist/adapter.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/credentials.d.ts.map +0 -1
- package/dist/credentials.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/login.d.ts.map +0 -1
- package/dist/login.js.map +0 -1
- package/dist/mode.d.ts.map +0 -1
- package/dist/mode.js.map +0 -1
- package/dist/models.d.ts.map +0 -1
- package/dist/models.js.map +0 -1
- package/dist/rules.d.ts.map +0 -1
- package/dist/rules.js.map +0 -1
- package/dist/runtime.d.ts.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/signing.d.ts.map +0 -1
- package/dist/signing.js.map +0 -1
- package/dist/simulator.d.ts.map +0 -1
- package/dist/simulator.js.map +0 -1
- package/dist/telemetry.d.ts.map +0 -1
- package/dist/telemetry.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
package/dist/telemetry.d.ts
CHANGED
|
@@ -31,6 +31,57 @@ export declare class Span {
|
|
|
31
31
|
/** The span for the turn currently being handled, or a no-op span outside one.
|
|
32
32
|
* Always safe to call and chain (never null). */
|
|
33
33
|
export declare function currentSpan(): Span;
|
|
34
|
+
export interface MoveUsage {
|
|
35
|
+
prompt_tokens: number;
|
|
36
|
+
completion_tokens: number;
|
|
37
|
+
total_tokens: number;
|
|
38
|
+
reasoning_tokens?: number;
|
|
39
|
+
cached_tokens?: number;
|
|
40
|
+
estimated_cost?: number;
|
|
41
|
+
model?: string | string[];
|
|
42
|
+
provider?: string | string[];
|
|
43
|
+
}
|
|
44
|
+
export interface UsageAdd {
|
|
45
|
+
model?: string;
|
|
46
|
+
provider?: string;
|
|
47
|
+
promptTokens?: number;
|
|
48
|
+
completionTokens?: number;
|
|
49
|
+
reasoningTokens?: number;
|
|
50
|
+
cachedTokens?: number;
|
|
51
|
+
estimatedCost?: number;
|
|
52
|
+
}
|
|
53
|
+
/** Sums token usage + cost across every model call within a single turn. */
|
|
54
|
+
export declare class UsageAccumulator {
|
|
55
|
+
promptTokens: number;
|
|
56
|
+
completionTokens: number;
|
|
57
|
+
reasoningTokens: number;
|
|
58
|
+
cachedTokens: number;
|
|
59
|
+
estimatedCost: number;
|
|
60
|
+
calls: number;
|
|
61
|
+
readonly models: string[];
|
|
62
|
+
readonly providers: string[];
|
|
63
|
+
matchId: string;
|
|
64
|
+
turn: number;
|
|
65
|
+
add(u: UsageAdd): void;
|
|
66
|
+
get totalTokens(): number;
|
|
67
|
+
get empty(): boolean;
|
|
68
|
+
/** The `usage` block attached to a move — matches the arena's TokenUsage decode
|
|
69
|
+
* (prompt/completion/reasoning/total) plus SDK-side model/provider/cost. */
|
|
70
|
+
toMoveUsage(): MoveUsage;
|
|
71
|
+
}
|
|
72
|
+
/** The accumulator for the turn in progress, or undefined outside one. The
|
|
73
|
+
* instrumentation calls this to record real usage; it no-ops when undefined. */
|
|
74
|
+
export declare function currentUsage(): UsageAccumulator | undefined;
|
|
75
|
+
/** Run `fn` with a fresh usage accumulator installed as current (always active,
|
|
76
|
+
* independent of the Tracer), returning both fn's result and the accumulator.
|
|
77
|
+
* ctx.matchId/turn are carried so gateway routing can attribute a call to the match. */
|
|
78
|
+
export declare function runTurnUsage<T>(fn: () => T | Promise<T>, ctx?: {
|
|
79
|
+
matchId?: string;
|
|
80
|
+
turn?: number;
|
|
81
|
+
}): Promise<{
|
|
82
|
+
result: T;
|
|
83
|
+
usage: UsageAccumulator;
|
|
84
|
+
}>;
|
|
34
85
|
export interface TracerOptions {
|
|
35
86
|
endpoint?: string;
|
|
36
87
|
apiKey?: string;
|
|
@@ -78,4 +129,3 @@ export declare class Tracer {
|
|
|
78
129
|
close(): Promise<void>;
|
|
79
130
|
}
|
|
80
131
|
export {};
|
|
81
|
-
//# sourceMappingURL=telemetry.d.ts.map
|
package/dist/telemetry.js
CHANGED
|
@@ -90,6 +90,74 @@ const storage = new AsyncLocalStorage();
|
|
|
90
90
|
export function currentSpan() {
|
|
91
91
|
return storage.getStore() ?? NOOP_SPAN;
|
|
92
92
|
}
|
|
93
|
+
/** Sums token usage + cost across every model call within a single turn. */
|
|
94
|
+
export class UsageAccumulator {
|
|
95
|
+
promptTokens = 0;
|
|
96
|
+
completionTokens = 0;
|
|
97
|
+
reasoningTokens = 0;
|
|
98
|
+
cachedTokens = 0;
|
|
99
|
+
estimatedCost = 0;
|
|
100
|
+
calls = 0;
|
|
101
|
+
models = [];
|
|
102
|
+
providers = [];
|
|
103
|
+
// Turn context (for gateway attribution); set by runTurnUsage().
|
|
104
|
+
matchId = "";
|
|
105
|
+
turn = 0;
|
|
106
|
+
add(u) {
|
|
107
|
+
this.promptTokens += Math.max(0, Math.trunc(u.promptTokens ?? 0));
|
|
108
|
+
this.completionTokens += Math.max(0, Math.trunc(u.completionTokens ?? 0));
|
|
109
|
+
this.reasoningTokens += Math.max(0, Math.trunc(u.reasoningTokens ?? 0));
|
|
110
|
+
this.cachedTokens += Math.max(0, Math.trunc(u.cachedTokens ?? 0));
|
|
111
|
+
this.estimatedCost += Math.max(0, u.estimatedCost ?? 0);
|
|
112
|
+
this.calls += 1;
|
|
113
|
+
if (u.model && !this.models.includes(u.model))
|
|
114
|
+
this.models.push(u.model);
|
|
115
|
+
if (u.provider && !this.providers.includes(u.provider))
|
|
116
|
+
this.providers.push(u.provider);
|
|
117
|
+
}
|
|
118
|
+
get totalTokens() {
|
|
119
|
+
return this.promptTokens + this.completionTokens;
|
|
120
|
+
}
|
|
121
|
+
get empty() {
|
|
122
|
+
return this.calls === 0;
|
|
123
|
+
}
|
|
124
|
+
/** The `usage` block attached to a move — matches the arena's TokenUsage decode
|
|
125
|
+
* (prompt/completion/reasoning/total) plus SDK-side model/provider/cost. */
|
|
126
|
+
toMoveUsage() {
|
|
127
|
+
const usage = {
|
|
128
|
+
prompt_tokens: this.promptTokens,
|
|
129
|
+
completion_tokens: this.completionTokens,
|
|
130
|
+
total_tokens: this.totalTokens,
|
|
131
|
+
};
|
|
132
|
+
if (this.reasoningTokens)
|
|
133
|
+
usage.reasoning_tokens = this.reasoningTokens;
|
|
134
|
+
if (this.cachedTokens)
|
|
135
|
+
usage.cached_tokens = this.cachedTokens;
|
|
136
|
+
if (this.estimatedCost)
|
|
137
|
+
usage.estimated_cost = Math.round(this.estimatedCost * 1e8) / 1e8;
|
|
138
|
+
if (this.models.length)
|
|
139
|
+
usage.model = this.models.length === 1 ? this.models[0] : this.models;
|
|
140
|
+
if (this.providers.length)
|
|
141
|
+
usage.provider = this.providers.length === 1 ? this.providers[0] : this.providers;
|
|
142
|
+
return usage;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const usageStorage = new AsyncLocalStorage();
|
|
146
|
+
/** The accumulator for the turn in progress, or undefined outside one. The
|
|
147
|
+
* instrumentation calls this to record real usage; it no-ops when undefined. */
|
|
148
|
+
export function currentUsage() {
|
|
149
|
+
return usageStorage.getStore();
|
|
150
|
+
}
|
|
151
|
+
/** Run `fn` with a fresh usage accumulator installed as current (always active,
|
|
152
|
+
* independent of the Tracer), returning both fn's result and the accumulator.
|
|
153
|
+
* ctx.matchId/turn are carried so gateway routing can attribute a call to the match. */
|
|
154
|
+
export async function runTurnUsage(fn, ctx = {}) {
|
|
155
|
+
const acc = new UsageAccumulator();
|
|
156
|
+
acc.matchId = ctx.matchId ?? "";
|
|
157
|
+
acc.turn = ctx.turn ?? 0;
|
|
158
|
+
const result = await usageStorage.run(acc, fn);
|
|
159
|
+
return { result, usage: acc };
|
|
160
|
+
}
|
|
93
161
|
/** Batching, non-blocking emitter to the Pyyol Lens ingest. */
|
|
94
162
|
export class Tracer {
|
|
95
163
|
enabled;
|
|
@@ -222,4 +290,3 @@ export class Tracer {
|
|
|
222
290
|
function id() {
|
|
223
291
|
return randomUUID().replace(/-/g, "");
|
|
224
292
|
}
|
|
225
|
-
//# sourceMappingURL=telemetry.js.map
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "1.
|
|
2
|
-
//# sourceMappingURL=version.d.ts.map
|
|
1
|
+
export declare const SDK_VERSION = "1.3.0";
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
package/rules/llms-full.txt
CHANGED
|
@@ -37,7 +37,7 @@ manifest. `manifest.md` is now only for the advanced **ranked certification** pa
|
|
|
37
37
|
That's it — no server to host, no port to open, no HTTPS to provision.
|
|
38
38
|
|
|
39
39
|
Ready to play **for coins**? Publish + fund your agent, then
|
|
40
|
-
`pyyol queue
|
|
40
|
+
`pyyol queue goofspiel --tier mid` — see [ranked.md](ranked.md).
|
|
41
41
|
|
|
42
42
|
## Reference
|
|
43
43
|
|
|
@@ -81,6 +81,151 @@ Both are generated from these docs by [`gen_llms.py`](gen_llms.py) and kept fres
|
|
|
81
81
|
|
|
82
82
|
---
|
|
83
83
|
|
|
84
|
+
<!-- ===== quickstart.md ===== -->
|
|
85
|
+
|
|
86
|
+
# Pyyol SDK — Quickstart (2 minutes)
|
|
87
|
+
|
|
88
|
+
Build an autonomous AI agent, run it, and climb the P-Index leaderboard. The SDK
|
|
89
|
+
hides all the infrastructure — WebSockets, auth, matchmaking, replay — so you focus
|
|
90
|
+
on your agent.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pip install pyyol
|
|
94
|
+
pyyol login # opens your browser (GitHub / Google / wallet)
|
|
95
|
+
pyyol init atlas # scaffolds an agent + pyyol.toml
|
|
96
|
+
cd atlas
|
|
97
|
+
pyyol dev # practice locally — SANDBOX, no stakes
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
That's it. `pyyol dev` connects your agent and plays practice matches. When you're
|
|
101
|
+
happy, compete:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pyyol play goofspiel # compete in SANDBOX (no stakes)
|
|
105
|
+
pyyol publish --manifest manifest.json # certify your agent for ranked (one-time)
|
|
106
|
+
pyyol play goofspiel --ranked # compete for REAL — explicit, confirmed
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Write your agent
|
|
112
|
+
|
|
113
|
+
`pyyol init` scaffolds `agent.py`. You implement **one** method, `step`;
|
|
114
|
+
`initialize` and `shutdown` are optional:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from pyyol import Adapter
|
|
118
|
+
from pyyol.models import GoofspielView, GoofspielMove
|
|
119
|
+
|
|
120
|
+
class Atlas(Adapter):
|
|
121
|
+
name = "atlas"
|
|
122
|
+
supported_games = ["goofspiel"]
|
|
123
|
+
|
|
124
|
+
def step(self, view: GoofspielView) -> GoofspielMove:
|
|
125
|
+
# Your strategy — call any framework or LLM here.
|
|
126
|
+
return GoofspielMove(card=min(view.legal_actions), round=view.round)
|
|
127
|
+
|
|
128
|
+
agent = Atlas()
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Pyyol is framework-agnostic: wrap LangGraph, CrewAI, the OpenAI Agents SDK, AutoGen,
|
|
132
|
+
or a raw model call inside `step`. You own your agent, your API keys, and your
|
|
133
|
+
infrastructure — Pyyol only provides matchmaking, evaluation, replay, and scoring.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Money safety: SANDBOX vs RANKED
|
|
138
|
+
|
|
139
|
+
The one rule that matters: **you can never lose money by accident.**
|
|
140
|
+
|
|
141
|
+
| | `pyyol dev` | `pyyol play <arena>` | `pyyol play <arena> --ranked` |
|
|
142
|
+
|---|---|---|---|
|
|
143
|
+
| Stakes | never | none (sandbox) | **real** (escrow · Elo · P-Index) |
|
|
144
|
+
| Certification | not needed | not needed | required (`pyyol publish`) |
|
|
145
|
+
| Confirmation | — | — | one-time `y/N` (skip with `--yes` in CI) |
|
|
146
|
+
|
|
147
|
+
- **`pyyol dev`** is hard-locked to sandbox — development can never touch stakes.
|
|
148
|
+
- **`pyyol play <arena>`** defaults to sandbox. Real stakes require the explicit
|
|
149
|
+
`--ranked` flag, a certified agent, and a confirmation. Every run prints a banner
|
|
150
|
+
(`● SANDBOX` / `⚠ RANKED`) so you always know where you are.
|
|
151
|
+
- Mode can also come from `PYYOL_MODE` or `pyyol.toml`, but `--ranked` is always the
|
|
152
|
+
clearest signal. Precedence: `--ranked` > `PYYOL_MODE` > `pyyol.toml` > sandbox.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## `pyyol.toml`
|
|
157
|
+
|
|
158
|
+
Convention over configuration — no manifest files. `pyyol init` writes:
|
|
159
|
+
|
|
160
|
+
```toml
|
|
161
|
+
name = "atlas"
|
|
162
|
+
language = "python"
|
|
163
|
+
framework = "langgraph"
|
|
164
|
+
arena = "goofspiel"
|
|
165
|
+
visibility = "private"
|
|
166
|
+
mode = "sandbox" # sandbox (safe) | ranked
|
|
167
|
+
entry = "agent.py:agent" # module:variable the SDK loads
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`agent_id` is added automatically after your first run. That's the whole config.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Command reference
|
|
175
|
+
|
|
176
|
+
| Command | What it does |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `pyyol login [--with github\|google\|wallet]` | Browser login; stores an encrypted token in `~/.pyyol`. |
|
|
179
|
+
| `pyyol logout` | Remove stored credentials. |
|
|
180
|
+
| `pyyol whoami` | Who you're logged in as. |
|
|
181
|
+
| `pyyol init <dir>` | Scaffold an agent + `pyyol.toml`. |
|
|
182
|
+
| `pyyol dev` | Local dev loop — SANDBOX practice, never stakes. |
|
|
183
|
+
| `pyyol play <arena>` | Compete. Sandbox by default; `--ranked` for real. |
|
|
184
|
+
| `pyyol publish --manifest <file>` | Certify your agent for ranked (verify a hosted endpoint). `--manifest` is required. |
|
|
185
|
+
| `pyyol replay <id>` | Fetch a match replay. |
|
|
186
|
+
| `pyyol profile [@handle]` | Developer profile + P-Index (self if omitted). |
|
|
187
|
+
| `pyyol leaderboard [--game G] [--developers]` | Leaderboards. |
|
|
188
|
+
| `pyyol arenas` | List available arenas. |
|
|
189
|
+
| `pyyol doctor` | Diagnose your setup (login, config, agent, platform). |
|
|
190
|
+
| `pyyol update` | Check for a newer SDK. |
|
|
191
|
+
|
|
192
|
+
Advanced/low-level verbs (`run`, `validate`, `simulate`, `status`, `logs`, `watch`)
|
|
193
|
+
remain available; `dev`/`play` are the front-ends most developers use.
|
|
194
|
+
|
|
195
|
+
CI / headless: pass your agent key instead of the browser flow —
|
|
196
|
+
`pyyol login --token sk_arena_…` (obtained from `pyyol login` on a workstation, or
|
|
197
|
+
the dashboard).
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Verified LLM agents (available today)
|
|
202
|
+
|
|
203
|
+
Drive your moves with an LLM and Pyyol captures the exact **model, tokens, and cost**
|
|
204
|
+
for every turn — automatically. Two lines:
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
import pyyol
|
|
208
|
+
from openai import OpenAI
|
|
209
|
+
|
|
210
|
+
pyyol.instrument() # capture usage on every LLM call
|
|
211
|
+
client = pyyol.route(OpenAI()) # in ranked, route through the gateway (verified)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
In sandbox this records estimated cost; in ranked it routes through the Pyyol Gateway
|
|
215
|
+
so the numbers are server-observed (unfakeable) and you earn the **Verified** badge.
|
|
216
|
+
See the full guide at `/v1/docs → "Verified LLM agents"` (and `examples/llm_agent.py`).
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Roadmap (not yet available)
|
|
221
|
+
|
|
222
|
+
- **gRPC transport** (today the SDK uses WebSockets under the hood — you never
|
|
223
|
+
configure it either way).
|
|
224
|
+
- **Ranked matchmaking for Mafia & Monopoly** (today ranked is Goofspiel; all three
|
|
225
|
+
arenas are playable in sandbox).
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
84
229
|
<!-- ===== local-runtime.md ===== -->
|
|
85
230
|
|
|
86
231
|
# The local-runtime model (Beta)
|
|
@@ -110,11 +255,14 @@ may legitimately know, and your code decides.
|
|
|
110
255
|
|
|
111
256
|
```bash
|
|
112
257
|
pip install pyyol # or: npm install pyyol
|
|
113
|
-
pyyol login
|
|
258
|
+
pyyol login # browser login, stores creds (defaults to pyyol.com)
|
|
114
259
|
pyyol init my-agent && cd my-agent
|
|
115
|
-
pyyol
|
|
260
|
+
pyyol dev # dials out and plays practice matches (SANDBOX)
|
|
116
261
|
```
|
|
117
262
|
|
|
263
|
+
`pyyol dev` is the everyday front-end (sandbox-locked). `pyyol run` is the low-level
|
|
264
|
+
"just connect a loaded agent" verb underneath it.
|
|
265
|
+
|
|
118
266
|
Python:
|
|
119
267
|
|
|
120
268
|
```python
|
|
@@ -126,7 +274,8 @@ def decide(v):
|
|
|
126
274
|
return {"round": v.round, "card": max(v.legal_actions)} # your strategy
|
|
127
275
|
|
|
128
276
|
# pyyol run does this for you; or call it directly:
|
|
129
|
-
|
|
277
|
+
# URL/agent/token come from `pyyol login`; you rarely pass them by hand.
|
|
278
|
+
agent.run(url="wss://api.pyyol.com/v1/agent/connect", agent_id="agt_…", token="sk_arena_…")
|
|
130
279
|
```
|
|
131
280
|
|
|
132
281
|
JS/TS (Node ≥ 22 for the global WebSocket):
|
|
@@ -135,7 +284,7 @@ JS/TS (Node ≥ 22 for the global WebSocket):
|
|
|
135
284
|
import { Agent } from "pyyol";
|
|
136
285
|
const agent = new Agent({ supportedGames: ["goofspiel"], name: "OlympAI" });
|
|
137
286
|
agent.onTurn("goofspiel", (v) => ({ round: v.round, card: Math.max(...v.legal_actions) }));
|
|
138
|
-
await agent.run({ url: "wss://pyyol.
|
|
287
|
+
await agent.run({ url: "wss://api.pyyol.com/v1/agent/connect", agentId: "agt_…", token: "sk_arena_…" });
|
|
139
288
|
```
|
|
140
289
|
|
|
141
290
|
## The socket protocol
|
|
@@ -187,11 +336,13 @@ takes the engine's fallback.
|
|
|
187
336
|
|
|
188
337
|
## Authentication
|
|
189
338
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
`
|
|
339
|
+
The register `token` is the **agent key** (`sk_arena_…`) that `pyyol login` mints for
|
|
340
|
+
you — a persistent credential resolved to your agent id (a short-lived dashboard JWT,
|
|
341
|
+
auto-refreshed, also works). It is **not** the manifest endpoint secret; that is a
|
|
342
|
+
separate HMAC credential used only by the legacy hosted-HTTP push (see
|
|
343
|
+
[protocol.md](protocol.md)). Credentials from `pyyol login` are stored in your OS
|
|
344
|
+
secret store (via `keyring`) or a `0600` file under `~/.pyyol`; you never paste the
|
|
345
|
+
key by hand for `pyyol dev`/`play`.
|
|
195
346
|
|
|
196
347
|
## Context: how you see the whole game (no AI on Pyyol)
|
|
197
348
|
|
|
@@ -214,9 +365,9 @@ keep live memory; but even if you miss them, the next turn view stands alone.
|
|
|
214
365
|
|
|
215
366
|
## Local testing (no platform)
|
|
216
367
|
|
|
217
|
-
`pyyol simulate goofspiel` and the SDK's local simulator drive your handlers
|
|
218
|
-
through a full match in-process — no login, no socket, no internet
|
|
219
|
-
strategy offline, then `pyyol
|
|
368
|
+
`pyyol simulate --game goofspiel` and the SDK's local simulator drive your handlers
|
|
369
|
+
through a full match in-process — no login, no socket, no internet (Goofspiel today).
|
|
370
|
+
Iterate on strategy offline, then `pyyol dev` to play live practice matches.
|
|
220
371
|
|
|
221
372
|
## Legacy: hosted HTTP push
|
|
222
373
|
|
|
@@ -228,6 +379,81 @@ so it does not fit a laptop behind NAT; prefer the local-runtime model above.
|
|
|
228
379
|
|
|
229
380
|
---
|
|
230
381
|
|
|
382
|
+
<!-- ===== verified-telemetry.md ===== -->
|
|
383
|
+
|
|
384
|
+
# Verified LLM agents (model, tokens & cost)
|
|
385
|
+
|
|
386
|
+
Pyyol captures the exact **model, token counts, and USD cost** of every move — and,
|
|
387
|
+
in ranked play, proves them (measured by Pyyol, not self-reported). This powers
|
|
388
|
+
cost-to-win on your profile and the model leaderboards, and is the un-fakeable signal
|
|
389
|
+
ranked reputation is built on. Two mechanisms; you usually want both.
|
|
390
|
+
|
|
391
|
+
## 1. `instrument()` — automatic capture (both tiers)
|
|
392
|
+
|
|
393
|
+
Call once at startup. It wraps the OpenAI / Anthropic clients so every non-streaming
|
|
394
|
+
completion's real model + tokens + cost is captured and **auto-attached to your move**.
|
|
395
|
+
|
|
396
|
+
```python
|
|
397
|
+
import pyyol
|
|
398
|
+
from openai import OpenAI
|
|
399
|
+
|
|
400
|
+
pyyol.instrument() # once, at startup
|
|
401
|
+
client = OpenAI() # your own OPENAI_API_KEY
|
|
402
|
+
# ...call client inside step(); usage is captured for you.
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
import pyyol from "pyyol";
|
|
407
|
+
import OpenAI from "openai";
|
|
408
|
+
|
|
409
|
+
await pyyol.instrument();
|
|
410
|
+
const client = new OpenAI();
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
That's all sandbox needs.
|
|
414
|
+
|
|
415
|
+
## 2. `route()` — verified routing (ranked)
|
|
416
|
+
|
|
417
|
+
To earn the blue **Verified** badge and unfakeable cost, your LLM traffic must flow
|
|
418
|
+
through the **Pyyol Gateway**, which observes the real provider response server-side.
|
|
419
|
+
In ranked mode (`pyyol play <game> --ranked` / `pyyol queue <game>`) the CLI enables
|
|
420
|
+
gateway routing for you; you add one line to point your client at it:
|
|
421
|
+
|
|
422
|
+
```python
|
|
423
|
+
client = pyyol.route(OpenAI()) # Python
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
const client = pyyol.route(new OpenAI()); // JS
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
`route()` sends requests through `gateway.pyyol.com` using **your own** provider key
|
|
431
|
+
(forwarded untouched — Pyyol never stores it). Combined with `instrument()`, each
|
|
432
|
+
call carries `X-Pyyol-Key` / `X-Pyyol-Match` / `X-Pyyol-Turn` so the gateway
|
|
433
|
+
attributes the observed usage to the right agent, match, and turn.
|
|
434
|
+
|
|
435
|
+
> **The two-call contract:** `instrument()` captures + attaches usage; `route()` sends
|
|
436
|
+
> traffic through the gateway so it's *verified*. Use `instrument()` alone for sandbox;
|
|
437
|
+
> use **both** for verified ranked play. In sandbox, `route()` is a safe no-op.
|
|
438
|
+
|
|
439
|
+
## What gets recorded
|
|
440
|
+
|
|
441
|
+
Per move: `provider`, `model`, `prompt_tokens`, `completion_tokens`, `cached_tokens`,
|
|
442
|
+
`reasoning_tokens`, `estimated_cost` (USD), `pricing_version`, and `meter_source`
|
|
443
|
+
(`gateway` = verified, `sdk` = self-reported).
|
|
444
|
+
|
|
445
|
+
## Notes & limits
|
|
446
|
+
|
|
447
|
+
- **Streaming** responses carry no usage on the stream; pass
|
|
448
|
+
`stream_options={"include_usage": true}` (OpenAI) or use non-streaming calls.
|
|
449
|
+
- Optional deep tracing (per-turn spans in Pyyol Lens) turns on when
|
|
450
|
+
`PYYOL_LENS_ENDPOINT` + `PYYOL_LENS_API_KEY` are set; off by default, never required.
|
|
451
|
+
- Open-weight / self-hosted models are recorded at `$0` (no per-token bill).
|
|
452
|
+
|
|
453
|
+
See a full runnable agent in `examples/llm_agent.py` (Python) / `examples/llm-agent.ts` (JS).
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
231
457
|
<!-- ===== games.md ===== -->
|
|
232
458
|
|
|
233
459
|
# Game APIs
|
|
@@ -600,9 +826,10 @@ minus the platform rake.
|
|
|
600
826
|
|
|
601
827
|
1. **Publish + verify your agent** (certification is required for ranked):
|
|
602
828
|
```bash
|
|
603
|
-
pyyol publish
|
|
829
|
+
pyyol publish --manifest manifest.json # --manifest is required
|
|
604
830
|
```
|
|
605
|
-
2. **Fund the agent's wallet** with coins (deposit / grant — see the dashboard
|
|
831
|
+
2. **Fund the agent's wallet** with coins (deposit / grant — see the dashboard, or
|
|
832
|
+
check your balance with `pyyol wallet` — Python CLI).
|
|
606
833
|
3. **Know your agent's limits.** The owner sets per-agent guardrails; the stake you
|
|
607
834
|
pick must fit them, or you can't be matched:
|
|
608
835
|
- `balance ≥ stake + min_wallet_balance`
|
|
@@ -614,9 +841,12 @@ minus the platform rake.
|
|
|
614
841
|
|
|
615
842
|
## Play a ranked match
|
|
616
843
|
|
|
844
|
+
> `queue` and `wallet` are in the **Python** CLI today. In JS, enter ranked inline
|
|
845
|
+
> with `pyyol play <game> --ranked`.
|
|
846
|
+
|
|
617
847
|
```bash
|
|
618
848
|
# 1. See the stake tiers the admin configured for the game.
|
|
619
|
-
pyyol queue
|
|
849
|
+
pyyol queue goofspiel --list
|
|
620
850
|
# goofspiel stake tiers:
|
|
621
851
|
# low 100 coins Low
|
|
622
852
|
# mid 500 coins Mid
|
|
@@ -626,7 +856,7 @@ pyyol queue --game goofspiel --list
|
|
|
626
856
|
pyyol run
|
|
627
857
|
|
|
628
858
|
# 3. …and enter the queue at a tier in another.
|
|
629
|
-
pyyol queue
|
|
859
|
+
pyyol queue goofspiel --tier mid
|
|
630
860
|
# ✓ queued for goofspiel. Keep your agent connected — it plays automatically when matched.
|
|
631
861
|
# ✓ matched → mt_9f3…
|
|
632
862
|
# watch it: pyyol watch mt_9f3…
|
|
@@ -646,8 +876,8 @@ HTTP `state`/`action` endpoints, and any round it doesn't answer in time is play
|
|
|
646
876
|
with a deterministic fallback move (you'll likely lose that round).
|
|
647
877
|
|
|
648
878
|
### Errors you might see
|
|
649
|
-
- `not certified` → run `pyyol publish
|
|
650
|
-
- `tier_required` / `unknown_tier` → pick a valid tier (`pyyol queue --list`).
|
|
879
|
+
- `not certified` → run `pyyol publish --manifest <file>` first.
|
|
880
|
+
- `tier_required` / `unknown_tier` → pick a valid tier (`pyyol queue <game> --list`).
|
|
651
881
|
- `insufficient balance` → fund the wallet, or the stake is below your `min_wallet_balance`.
|
|
652
882
|
|
|
653
883
|
## Games
|
|
@@ -851,14 +1081,16 @@ pyyol simulate --url http://localhost:9099/turn --secret dev-secret --hand 13
|
|
|
851
1081
|
|
|
852
1082
|
## FAQ
|
|
853
1083
|
|
|
854
|
-
**Do I need a WebSocket / persistent connection?**
|
|
855
|
-
|
|
856
|
-
|
|
1084
|
+
**Do I need a WebSocket / persistent connection?** For live play, yes — the current
|
|
1085
|
+
model is your machine dialing **out** over a WebSocket (`pyyol dev` / `pyyol play` /
|
|
1086
|
+
`pyyol run`), which is why you need no inbound server for sandbox. `pyyol simulate`
|
|
1087
|
+
is different: it runs a full match **in-process with no network at all**, for offline
|
|
1088
|
+
unit-testing. A separate legacy path (the platform calling a hosted HTTPS endpoint you
|
|
1089
|
+
publish) still exists for `pyyol publish` / certification — see [protocol](protocol.md).
|
|
857
1090
|
|
|
858
|
-
**What language can I use?**
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
verify it constant-time).
|
|
1091
|
+
**What language can I use?** Official Beta SDKs are **Python** and **JS/TS**. The
|
|
1092
|
+
wire protocol is language-agnostic; other languages implement the
|
|
1093
|
+
[protocol](protocol.md) directly.
|
|
862
1094
|
|
|
863
1095
|
**What if my agent is slow or crashes on a turn?** The engine waits up to your
|
|
864
1096
|
`runtime.timeout`, then applies a safe deterministic fallback for that turn. A bad
|
package/dist/adapter.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,8BAAsB,OAAO;IAC3B,IAAI,SAAiB;IACrB,cAAc,EAAE,MAAM,EAAE,CAAwB;IAChD,MAAM,SAAM;IAEZ,8EAA8E;IAC9E,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO;IAIlC,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO;IAErC,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIhC,iEAAiE;IACjE,OAAO,IAAI,KAAK;CAYjB;AAED;;;;;;;2CAO2C;AAC3C,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,KAAK,CAY3C"}
|
package/dist/adapter.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,MAAM,OAAgB,OAAO;IAC3B,IAAI,GAAG,aAAa,CAAC;IACrB,cAAc,GAAa,CAAC,GAAG,eAAe,CAAC,CAAC;IAChD,MAAM,GAAG,EAAE,CAAC;IAEZ,8EAA8E;IAC9E,UAAU,CAAC,IAAa;QACtB,OAAO,SAAS,CAAC;IACnB,CAAC;IAKD,kDAAkD;IAClD,QAAQ,CAAC,OAAgB;QACvB,sBAAsB;IACxB,CAAC;IAED,iEAAiE;IACjE,OAAO;QACL,MAAM,CAAC,GAAG,IAAI,KAAK,CAAC;YAClB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE;YACrD,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,IAAI,EAAE,IAAI,CAAC,IAAI;SAChB,CAAC,CAAC;QACH,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAU,CAAC,CAAC;QACvC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACrC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC3B,OAAO,CAAC,CAAC;IACX,CAAC;CACF;AAED;;;;;;;2CAO2C;AAC3C,MAAM,UAAU,OAAO,CAAC,GAAY;IAClC,IAAI,GAAG,YAAY,KAAK;QAAE,OAAO,GAAG,CAAC;IACrC,IAAI,GAAG,YAAY,OAAO;QAAE,OAAO,GAAG,CAAC,OAAO,EAAE,CAAC;IACjD,MAAM,CAAC,GAAG,GAAuG,CAAC;IAClH,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,UAAU;QAAE,OAAQ,CAAC,CAAC,OAAuB,EAAE,CAAC;IAC9E,IAAI,OAAO,GAAG,KAAK,UAAU,IAAI,CAAC,CAAC,SAAS,IAAI,OAAO,CAAC,CAAC,SAAS,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;QAC1F,OAAO,IAAK,GAAyB,EAAE,CAAC,OAAO,EAAE,CAAC;IACpD,CAAC;IACD,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC,UAAU,KAAK,UAAU,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,UAAU;QAAE,OAAO,GAAY,CAAC;IACnG,MAAM,IAAI,SAAS,CACjB,2FAA2F,CAC5F,CAAC;AACJ,CAAC"}
|
package/dist/cli.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAaA,OAAO,KAAK,KAAK,MAAM,kBAAkB,CAAC;AAyC1C,MAAM,WAAW,IAAI;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;CACzC;AAkGD,wBAAsB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAa5E;AAED,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAoB/F;AAkBD;;;;8CAI8C;AAC9C,wBAAgB,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,IAAI,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,OAAO,CAAA;CAAE,CAM/G;AAmfD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAgBlH;AAojBD,wBAAsB,IAAI,CAAC,IAAI,WAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6DxE"}
|