@percepteye/agent-flywheel 0.1.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/LICENSE +21 -0
- package/README.md +368 -0
- package/cordis.patch.yml +11 -0
- package/openclaw.plugin.json +134 -0
- package/package.json +67 -0
- package/schema/flywheel-1.json +432 -0
- package/src/capture.js +733 -0
- package/src/classify.js +115 -0
- package/src/config.js +249 -0
- package/src/describe.js +355 -0
- package/src/dsh-classify.js +110 -0
- package/src/dsh.js +130 -0
- package/src/errors.js +37 -0
- package/src/evidence.js +109 -0
- package/src/execution-identity.js +444 -0
- package/src/host.js +63 -0
- package/src/http.js +249 -0
- package/src/index.js +380 -0
- package/src/mode.js +152 -0
- package/src/model-calls.js +214 -0
- package/src/policy.js +934 -0
- package/src/record.js +83 -0
- package/src/rollout.js +884 -0
- package/src/scope.js +242 -0
- package/src/session.js +42 -0
- package/src/trajectory.js +148 -0
- package/src/transport.js +403 -0
- package/src/turns.js +437 -0
- package/src/unattended.js +251 -0
- package/src/wire.js +182 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://schemas.percepteye.ai/flywheel/1.json",
|
|
4
|
+
"title": "PerceptEye Flywheel contract, version 1",
|
|
5
|
+
"description": "The wire contract between a customer-run agent and the PerceptEye control plane. THIS FILE IS THE AUTHORITY. Server implementations pin against the published schema; the schema is never generated from a server type. The Python types in src/agent_flywheel/contract.py are hand-written to match this file, not generated from it, and the test suite asserts the two agree.",
|
|
6
|
+
"x-contract-version": "flywheel/1",
|
|
7
|
+
"$defs": {
|
|
8
|
+
"ToolCallRecord": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"description": "One tool call the agent executed. FLYWHEEL-AUTHORED.",
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"required": [
|
|
13
|
+
"name",
|
|
14
|
+
"outcome",
|
|
15
|
+
"status_code"
|
|
16
|
+
],
|
|
17
|
+
"properties": {
|
|
18
|
+
"name": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"minLength": 1
|
|
21
|
+
},
|
|
22
|
+
"arguments": {
|
|
23
|
+
"type": "object"
|
|
24
|
+
},
|
|
25
|
+
"outcome": {
|
|
26
|
+
"description": "REQUIRED, no default. 'unknown' is a first-class, legal value and is the honest answer whenever the framework did not expose a real verdict \u2014 for example a LangChain tool with handle_tool_error=True, which catches its own exception and returns a string that reads like a normal result. 'unknown' must NEVER be rounded up to 'ok'. Maps server-side to a tri-state: ok->true, failed->false, unknown->null, on which outcome-dependent verifiers correctly abstain.",
|
|
27
|
+
"enum": [
|
|
28
|
+
"ok",
|
|
29
|
+
"failed",
|
|
30
|
+
"unknown"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"status_code": {
|
|
34
|
+
"description": "REQUIRED KEY, nullable value. The HTTP status observed on the wire, or null. MUST be null when outcome is 'unknown': a status may confirm or contradict an observed outcome, it may never manufacture one. Sending outcome='unknown' alongside status_code=200 is the exact shape that scores a failed call as a landed mutation.",
|
|
35
|
+
"type": [
|
|
36
|
+
"integer",
|
|
37
|
+
"null"
|
|
38
|
+
],
|
|
39
|
+
"minimum": 100,
|
|
40
|
+
"maximum": 599
|
|
41
|
+
},
|
|
42
|
+
"output": {
|
|
43
|
+
"description": "Tool result. Scrubbed of machine-readable status when outcome is 'unknown'."
|
|
44
|
+
},
|
|
45
|
+
"error": {
|
|
46
|
+
"type": [
|
|
47
|
+
"string",
|
|
48
|
+
"null"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"error_class": {
|
|
52
|
+
"type": [
|
|
53
|
+
"string",
|
|
54
|
+
"null"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"latency_ms": {
|
|
58
|
+
"type": [
|
|
59
|
+
"number",
|
|
60
|
+
"null"
|
|
61
|
+
],
|
|
62
|
+
"minimum": 0
|
|
63
|
+
},
|
|
64
|
+
"agent_name": {
|
|
65
|
+
"type": [
|
|
66
|
+
"string",
|
|
67
|
+
"null"
|
|
68
|
+
],
|
|
69
|
+
"description": "Which agent in a multi-agent system made this call. null means unattributed, which is NOT a claim that one agent made everything."
|
|
70
|
+
},
|
|
71
|
+
"tool_call_id": {
|
|
72
|
+
"type": [
|
|
73
|
+
"string",
|
|
74
|
+
"null"
|
|
75
|
+
],
|
|
76
|
+
"description": "The id the MODEL assigned to this call (LangChain ToolMessage.tool_call_id, ADK function_call_id, OpenAI tool_calls[].id). The join key for attributing a call to the completion that emitted it: the same id reappears on the role='tool' message of the agent's NEXT request, so attribution needs no clocks and survives concurrent tools. null/absent means the framework exposed no id -- the call still counts, it just cannot be placed more precisely than 'somewhere in this rollout'."
|
|
77
|
+
},
|
|
78
|
+
"entity_ids": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"additionalProperties": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"minLength": 1
|
|
83
|
+
},
|
|
84
|
+
"propertyNames": {
|
|
85
|
+
"enum": [
|
|
86
|
+
"id",
|
|
87
|
+
"key",
|
|
88
|
+
"uuid",
|
|
89
|
+
"sid",
|
|
90
|
+
"gid"
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
"description": "Identity the write produced, filed under the field it came from. Present so the entity can be found again later without retaining the response body. High-entropy fields only: a low-entropy value a different entity shares by coincidence identifies the wrong resource. Absent or empty means no identity evidence, which is an abstention and never a failure."
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"allOf": [
|
|
97
|
+
{
|
|
98
|
+
"if": {
|
|
99
|
+
"properties": {
|
|
100
|
+
"outcome": {
|
|
101
|
+
"const": "unknown"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"required": [
|
|
105
|
+
"outcome"
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
"then": {
|
|
109
|
+
"properties": {
|
|
110
|
+
"status_code": {
|
|
111
|
+
"type": "null"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
"ProductionTurn": {
|
|
119
|
+
"type": "object",
|
|
120
|
+
"description": "One customer production turn captured by attach(). Turn-native evidence, not a training row. task_id is caller-authored correlation evidence only and grants no training eligibility.",
|
|
121
|
+
"additionalProperties": false,
|
|
122
|
+
"required": [
|
|
123
|
+
"turn_id",
|
|
124
|
+
"input_text",
|
|
125
|
+
"final_text",
|
|
126
|
+
"tool_calls",
|
|
127
|
+
"tool_calls_omitted_count"
|
|
128
|
+
],
|
|
129
|
+
"properties": {
|
|
130
|
+
"turn_id": {
|
|
131
|
+
"description": "Exact caller turn id. In addition to the shared production identifier grammar, it is one storage-safe path segment: slash, backslash, and the dot segments . and .. are forbidden.",
|
|
132
|
+
"type": "string",
|
|
133
|
+
"minLength": 1,
|
|
134
|
+
"maxLength": 255,
|
|
135
|
+
"pattern": "^(?!\\.{1,2}$)(?!\\s)(?!.*\\s$)(?!.*[\\\\/])[^\\u0000-\\u001F\\u007F-\\u009F]+$"
|
|
136
|
+
},
|
|
137
|
+
"task_id": {
|
|
138
|
+
"description": "Optional opaque task/case/work-item identifier supplied by the customer. It is an exact correlation candidate for control-plane validation, never a success label, hint, policy identity, or permission to train.",
|
|
139
|
+
"type": "string",
|
|
140
|
+
"minLength": 1,
|
|
141
|
+
"maxLength": 255,
|
|
142
|
+
"pattern": "^(?!\\s)(?!.*\\s$)[^\\u0000-\\u001F\\u007F-\\u009F]+$"
|
|
143
|
+
},
|
|
144
|
+
"conversation_id": {
|
|
145
|
+
"description": "Optional opaque correlation id using the shared production identifier grammar: no leading/trailing whitespace or C0/C1 control characters.",
|
|
146
|
+
"type": "string",
|
|
147
|
+
"minLength": 1,
|
|
148
|
+
"maxLength": 255,
|
|
149
|
+
"pattern": "^(?!\\s)(?!.*\\s$)[^\\u0000-\\u001F\\u007F-\\u009F]+$"
|
|
150
|
+
},
|
|
151
|
+
"turn_index": {
|
|
152
|
+
"type": "integer",
|
|
153
|
+
"minimum": 0
|
|
154
|
+
},
|
|
155
|
+
"input_text": {
|
|
156
|
+
"type": [
|
|
157
|
+
"string",
|
|
158
|
+
"null"
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
"final_text": {
|
|
162
|
+
"type": [
|
|
163
|
+
"string",
|
|
164
|
+
"null"
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
"tool_calls": {
|
|
168
|
+
"description": "null means not reported; [] means observed zero calls.",
|
|
169
|
+
"type": [
|
|
170
|
+
"array",
|
|
171
|
+
"null"
|
|
172
|
+
],
|
|
173
|
+
"items": {
|
|
174
|
+
"$ref": "#/$defs/ToolCallRecord"
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"tool_calls_omitted_count": {
|
|
178
|
+
"type": "integer",
|
|
179
|
+
"minimum": 0
|
|
180
|
+
},
|
|
181
|
+
"agent_fingerprint": {
|
|
182
|
+
"type": "object"
|
|
183
|
+
},
|
|
184
|
+
"occurred_at": {
|
|
185
|
+
"description": "Optional RFC 3339 time at which the customer agent produced the turn. Receipt and retention clocks remain control-plane authored.",
|
|
186
|
+
"type": "string",
|
|
187
|
+
"format": "date-time"
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
"ProductionTurnBatch": {
|
|
192
|
+
"type": "object",
|
|
193
|
+
"description": "The body sent to POST /turns.",
|
|
194
|
+
"additionalProperties": false,
|
|
195
|
+
"required": [
|
|
196
|
+
"agent_id",
|
|
197
|
+
"turns"
|
|
198
|
+
],
|
|
199
|
+
"properties": {
|
|
200
|
+
"agent_id": {
|
|
201
|
+
"description": "Opaque customer agent id using the shared production identifier grammar. Because it is also a URL path segment, slash, backslash, and the dot segments . and .. are forbidden; remaining transport characters are percent-encoded.",
|
|
202
|
+
"type": "string",
|
|
203
|
+
"minLength": 1,
|
|
204
|
+
"maxLength": 255,
|
|
205
|
+
"pattern": "^(?!\\.{1,2}$)(?!\\s)(?!.*\\s$)(?!.*[\\\\/])[^\\u0000-\\u001F\\u007F-\\u009F]+$"
|
|
206
|
+
},
|
|
207
|
+
"turns": {
|
|
208
|
+
"type": "array",
|
|
209
|
+
"minItems": 1,
|
|
210
|
+
"maxItems": 64,
|
|
211
|
+
"items": {
|
|
212
|
+
"$ref": "#/$defs/ProductionTurn"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
"Exclusion": {
|
|
218
|
+
"type": "object",
|
|
219
|
+
"description": "The flywheel's typed 'do not train on this' signal. Distinct from a low reward: a substrate fault is not a policy failure.",
|
|
220
|
+
"additionalProperties": false,
|
|
221
|
+
"required": [
|
|
222
|
+
"reason"
|
|
223
|
+
],
|
|
224
|
+
"properties": {
|
|
225
|
+
"reason": {
|
|
226
|
+
"enum": [
|
|
227
|
+
"flywheel_crash",
|
|
228
|
+
"substrate_unavailable",
|
|
229
|
+
"credential_expired",
|
|
230
|
+
"deadline_exceeded",
|
|
231
|
+
"entrypoint_error",
|
|
232
|
+
"agent_exit_nonzero",
|
|
233
|
+
"cancelled",
|
|
234
|
+
"capacity"
|
|
235
|
+
]
|
|
236
|
+
},
|
|
237
|
+
"detail": {
|
|
238
|
+
"type": [
|
|
239
|
+
"string",
|
|
240
|
+
"null"
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
"RolloutRequest": {
|
|
246
|
+
"type": "object",
|
|
247
|
+
"description": "PERCEPTEYE-AUTHORED. Handed to the agent for one rollout. Note there is deliberately NO 'environment' key: the only configuration channel into the agent is subprocess environment at spawn. A previous SDK wrote server-supplied values into the RUNNING process's os.environ, which was both dangerous and useless (the LLM client had already read its base_url at construction).",
|
|
248
|
+
"additionalProperties": false,
|
|
249
|
+
"required": [
|
|
250
|
+
"rollout_id",
|
|
251
|
+
"turn_input",
|
|
252
|
+
"llm_url",
|
|
253
|
+
"api_key",
|
|
254
|
+
"artifacts_dir",
|
|
255
|
+
"deadline_ts"
|
|
256
|
+
],
|
|
257
|
+
"properties": {
|
|
258
|
+
"rollout_id": {
|
|
259
|
+
"description": "Minted by the control plane BEFORE the agent runs anything. This is the attribution unit; it is never a per-process session id.",
|
|
260
|
+
"type": "string",
|
|
261
|
+
"minLength": 1
|
|
262
|
+
},
|
|
263
|
+
"turn_input": {
|
|
264
|
+
"type": "string"
|
|
265
|
+
},
|
|
266
|
+
"llm_url": {
|
|
267
|
+
"description": "Per-rollout, unguessable, single-use, expiring. A shared or process-global URL is not an acceptable fallback: it destroys per-rollout attribution and collides across concurrent rollouts.",
|
|
268
|
+
"type": "string",
|
|
269
|
+
"format": "uri"
|
|
270
|
+
},
|
|
271
|
+
"api_key": {
|
|
272
|
+
"type": "string",
|
|
273
|
+
"minLength": 1
|
|
274
|
+
},
|
|
275
|
+
"artifacts_dir": {
|
|
276
|
+
"type": "string"
|
|
277
|
+
},
|
|
278
|
+
"deadline_ts": {
|
|
279
|
+
"description": "Authoritative. The flywheel computes its timeout from this, never from a local environment variable.",
|
|
280
|
+
"type": "number"
|
|
281
|
+
},
|
|
282
|
+
"lease_token": {
|
|
283
|
+
"type": [
|
|
284
|
+
"string",
|
|
285
|
+
"null"
|
|
286
|
+
]
|
|
287
|
+
},
|
|
288
|
+
"lease_expires_ts": {
|
|
289
|
+
"description": "Absolute epoch seconds at which the lease the server GRANTED expires, as of this claim. Not the work deadline: deadline_ts is the rollout's budget and does not move. The flywheel reads this ONCE, at claim, and paces its heartbeats at a third of the remaining grant for the life of the rollout; it does not re-read a renewed expiry, because heartbeat responses are not parsed and RolloutRequest is frozen. Absent means the server does not report what it granted, and the client falls back to the lease it requested -- which is wrong whenever the server clamped that request down.",
|
|
290
|
+
"type": [
|
|
291
|
+
"number",
|
|
292
|
+
"null"
|
|
293
|
+
]
|
|
294
|
+
},
|
|
295
|
+
"metadata": {
|
|
296
|
+
"type": "object"
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
"RolloutOutput": {
|
|
301
|
+
"type": "object",
|
|
302
|
+
"description": "FLYWHEEL-AUTHORED. Every field a customer-run flywheel may assert, and the complete set: the SDK is semi-trusted, so anything PerceptEye authors during scoring or training is absent by construction here and rejected at intake if present. The properties below are the whole allowed vocabulary -- additionalProperties is false, so what is permitted is stated positively rather than by listing what is not.",
|
|
303
|
+
"additionalProperties": false,
|
|
304
|
+
"required": [
|
|
305
|
+
"final_text"
|
|
306
|
+
],
|
|
307
|
+
"properties": {
|
|
308
|
+
"final_text": {
|
|
309
|
+
"type": "string"
|
|
310
|
+
},
|
|
311
|
+
"tool_calls": {
|
|
312
|
+
"description": "null means NOT REPORTED. [] means OBSERVED ZERO CALLS -- a positive assertion. These are different and the wire keeps them different; the default is null so an agent that reports nothing never accidentally asserts zero. If any executed call cannot be classified, either emit it with outcome='unknown' or set this to null for the WHOLE rollout -- never a partial list, because a truncated list is indistinguishable from a short one and corrupts every set-reasoning verifier.",
|
|
313
|
+
"type": [
|
|
314
|
+
"array",
|
|
315
|
+
"null"
|
|
316
|
+
],
|
|
317
|
+
"items": {
|
|
318
|
+
"$ref": "#/$defs/ToolCallRecord"
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
"tool_calls_omitted_count": {
|
|
322
|
+
"description": "Executed calls deliberately not reported. Non-zero forbids training grade.",
|
|
323
|
+
"type": "integer",
|
|
324
|
+
"minimum": 0,
|
|
325
|
+
"default": 0
|
|
326
|
+
},
|
|
327
|
+
"llm_call_count": {
|
|
328
|
+
"description": "Completions the agent believes it issued. Reconciled against the gateway's own count; a mismatch means traffic escaped our wire and the rollout cannot be treated as on-policy.",
|
|
329
|
+
"type": [
|
|
330
|
+
"integer",
|
|
331
|
+
"null"
|
|
332
|
+
],
|
|
333
|
+
"minimum": 0
|
|
334
|
+
},
|
|
335
|
+
"artifacts": {
|
|
336
|
+
"description": "name -> artifact record. A string value is the file's sha256 digest (or a named skip marker such as 'skipped:too_large:N'). An object value additionally INLINES the body of a small UTF-8 file -- for coding/REPL flywheels the files are the deliverable, and a grader that can verify a digest but never read the file proves integrity while grading nothing. Inline bodies are capped per file and per report; binary or over-cap files keep the string form.",
|
|
337
|
+
"type": "object",
|
|
338
|
+
"additionalProperties": {
|
|
339
|
+
"anyOf": [
|
|
340
|
+
{
|
|
341
|
+
"type": "string"
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
"type": "object",
|
|
345
|
+
"additionalProperties": false,
|
|
346
|
+
"required": [
|
|
347
|
+
"sha256",
|
|
348
|
+
"size_bytes",
|
|
349
|
+
"content"
|
|
350
|
+
],
|
|
351
|
+
"properties": {
|
|
352
|
+
"sha256": {
|
|
353
|
+
"type": "string",
|
|
354
|
+
"minLength": 64,
|
|
355
|
+
"maxLength": 64
|
|
356
|
+
},
|
|
357
|
+
"size_bytes": {
|
|
358
|
+
"type": "integer",
|
|
359
|
+
"minimum": 0
|
|
360
|
+
},
|
|
361
|
+
"content": {
|
|
362
|
+
"type": "string"
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
]
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
"reward": {
|
|
370
|
+
"description": "ADVISORY ONLY. Recorded on the rollout row. PerceptEye's compiled suite remains the scored suite; this scalar never reaches the dataset emitters in v1.",
|
|
371
|
+
"type": [
|
|
372
|
+
"number",
|
|
373
|
+
"null"
|
|
374
|
+
]
|
|
375
|
+
},
|
|
376
|
+
"success": {
|
|
377
|
+
"type": [
|
|
378
|
+
"boolean",
|
|
379
|
+
"null"
|
|
380
|
+
]
|
|
381
|
+
},
|
|
382
|
+
"exclusion": {
|
|
383
|
+
"oneOf": [
|
|
384
|
+
{
|
|
385
|
+
"$ref": "#/$defs/Exclusion"
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"type": "null"
|
|
389
|
+
}
|
|
390
|
+
]
|
|
391
|
+
},
|
|
392
|
+
"events": {
|
|
393
|
+
"type": "array",
|
|
394
|
+
"items": {
|
|
395
|
+
"type": "object"
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
"timing": {
|
|
399
|
+
"type": "object"
|
|
400
|
+
},
|
|
401
|
+
"agent_fingerprint": {
|
|
402
|
+
"description": "ATTESTATION, not an authenticated identity. The customer-declared half of the scaffold identity (SDK version, flywheel version, declared memory/skills/subagent digests). The server-derived half -- rendered-prompt sha, tool-schema digest, sampling params, tokenizer -- is computed at the gateway from the observed wire and is never accepted from here. The two are recorded separately and never conflated.",
|
|
403
|
+
"type": [
|
|
404
|
+
"object",
|
|
405
|
+
"null"
|
|
406
|
+
]
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
"ClaimResponse": {
|
|
411
|
+
"type": "object",
|
|
412
|
+
"additionalProperties": false,
|
|
413
|
+
"required": [
|
|
414
|
+
"rollouts",
|
|
415
|
+
"poll_after_ms"
|
|
416
|
+
],
|
|
417
|
+
"properties": {
|
|
418
|
+
"rollouts": {
|
|
419
|
+
"type": "array",
|
|
420
|
+
"items": {
|
|
421
|
+
"$ref": "#/$defs/RolloutRequest"
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
"poll_after_ms": {
|
|
425
|
+
"description": "Server-directed backoff. An empty claim returns 200 with an empty list and this hint -- never a bare 204, which carries no backoff information and forces the client to invent its own.",
|
|
426
|
+
"type": "integer",
|
|
427
|
+
"minimum": 0
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|