apsimo-hostworker 0.2.0__py3-none-any.whl
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.
- apsimo_hostworker/__init__.py +134 -0
- apsimo_hostworker/_private_io.py +267 -0
- apsimo_hostworker/admission.py +297 -0
- apsimo_hostworker/catalog.py +754 -0
- apsimo_hostworker/client.py +292 -0
- apsimo_hostworker/compat.py +53 -0
- apsimo_hostworker/conformance/__init__.py +52 -0
- apsimo_hostworker/conformance/__main__.py +29 -0
- apsimo_hostworker/conformance/harness.py +278 -0
- apsimo_hostworker/conformance/suite.py +1109 -0
- apsimo_hostworker/contract.py +402 -0
- apsimo_hostworker/gate.py +446 -0
- apsimo_hostworker/intent.py +221 -0
- apsimo_hostworker/sqlite_store.py +1815 -0
- apsimo_hostworker/store.py +446 -0
- apsimo_hostworker/worker.py +643 -0
- apsimo_hostworker-0.2.0.dist-info/METADATA +24 -0
- apsimo_hostworker-0.2.0.dist-info/RECORD +22 -0
- apsimo_hostworker-0.2.0.dist-info/WHEEL +5 -0
- apsimo_hostworker-0.2.0.dist-info/licenses/LICENSE +21 -0
- apsimo_hostworker-0.2.0.dist-info/top_level.txt +2 -0
- colony_hostworker/__init__.py +4 -0
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
"""Single source of truth for the governed Colony tool catalog.
|
|
2
|
+
|
|
3
|
+
Each governed tool carries exactly five things: its wire name, its model-visible
|
|
4
|
+
argument schema, its strict argument validator, its owner-facing approval-display
|
|
5
|
+
metadata, and whether a standing bounded grant may ever authorize it
|
|
6
|
+
(``non_grantable``).
|
|
7
|
+
|
|
8
|
+
``colony_autonomy_enable`` and ``colony_autonomy_disable`` are marked
|
|
9
|
+
``non_grantable`` by owner decision: autonomy posture must always be a
|
|
10
|
+
per-message owner approval and can never ride on a standing grant, however the
|
|
11
|
+
grant was issued. The gate core (:mod:`apsimo_hostworker.gate`) fails closed
|
|
12
|
+
on the grant path for any non-grantable — or unknown — tool regardless of host
|
|
13
|
+
configuration.
|
|
14
|
+
|
|
15
|
+
The validators here are behavior-identical ports of the two existing
|
|
16
|
+
independent validators (ColonyAI's endpoint ``_validate_args`` and the private
|
|
17
|
+
worker's intent ``_validate_args``); the repo-internal agreement test pins
|
|
18
|
+
that equivalence against ColonyAI's endpoint, which deliberately keeps its own
|
|
19
|
+
copy (see the design rule in :mod:`apsimo_hostworker.contract`).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import copy
|
|
25
|
+
import json
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from typing import Any, Callable, Mapping
|
|
28
|
+
|
|
29
|
+
from .contract import (
|
|
30
|
+
BOUNDED_JSON_INTEGER_MAX,
|
|
31
|
+
BOUNDED_JSON_KEY_MAX_CHARS,
|
|
32
|
+
BOUNDED_JSON_MAX_DEPTH,
|
|
33
|
+
BOUNDED_JSON_MAX_NODES,
|
|
34
|
+
BOUNDED_JSON_STRING_MAX_CHARS,
|
|
35
|
+
GovernedContractError,
|
|
36
|
+
IDENTIFIER_MAX_CHARS,
|
|
37
|
+
IDENTIFIER_RE,
|
|
38
|
+
RESEARCH_TOPIC_MAX_CHARS,
|
|
39
|
+
bounded_integer,
|
|
40
|
+
bounded_json_value,
|
|
41
|
+
bounded_number,
|
|
42
|
+
bounded_text,
|
|
43
|
+
enum_text,
|
|
44
|
+
exact_mapping,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
COMMITMENT_PRIORITY_DEFAULT = 60
|
|
49
|
+
COMMITMENT_DESCRIPTION_MAX_CHARS = 8000
|
|
50
|
+
COMMITMENT_DUE_AT_MAX_CHARS = 256
|
|
51
|
+
INSIGHT_CONTENT_MAX_CHARS = 16000
|
|
52
|
+
FREEFORM_REASON_MAX_CHARS = 8000
|
|
53
|
+
|
|
54
|
+
# ``bounded_text`` rejects NUL and lone UTF-16 surrogates for every text field,
|
|
55
|
+
# and rejects blank text unless ``allow_empty=True``. These zero-width,
|
|
56
|
+
# ECMAScript-compatible patterns project the same rules into JSON Schema while
|
|
57
|
+
# still permitting ordinary newlines. The explicit class is Python's
|
|
58
|
+
# ``str.strip`` whitespace set; ECMAScript's shorter ``\s`` set differs.
|
|
59
|
+
_MODEL_TEXT_PATTERN = r"^(?![\s\S]*[\u0000\uD800-\uDFFF])"
|
|
60
|
+
_MODEL_NONBLANK_TEXT_PATTERN = (
|
|
61
|
+
_MODEL_TEXT_PATTERN
|
|
62
|
+
+ r"(?=[\s\S]*[^\u0009-\u000D\u001C-\u0020\u0085\u00A0"
|
|
63
|
+
r"\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000])"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ToolCatalogError(GovernedContractError):
|
|
68
|
+
"""The tool is not part of the governed catalog."""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _preview(value: Any, maximum: int = 300) -> str:
|
|
72
|
+
text = " ".join(str(value).split())
|
|
73
|
+
if len(text) > maximum:
|
|
74
|
+
text = text[: maximum - 1] + "…"
|
|
75
|
+
return json.dumps(text, ensure_ascii=True)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _parameters(
|
|
79
|
+
properties: Mapping[str, Any],
|
|
80
|
+
required=(),
|
|
81
|
+
*,
|
|
82
|
+
definitions: Mapping[str, Any] | None = None,
|
|
83
|
+
) -> dict[str, Any]:
|
|
84
|
+
schema = {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": dict(properties),
|
|
87
|
+
"required": list(required),
|
|
88
|
+
"additionalProperties": False,
|
|
89
|
+
}
|
|
90
|
+
if definitions:
|
|
91
|
+
schema["$defs"] = dict(definitions)
|
|
92
|
+
return schema
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _text_model_schema(
|
|
96
|
+
maximum: int, *, allow_empty: bool = False,
|
|
97
|
+
) -> dict[str, Any]:
|
|
98
|
+
schema = {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"maxLength": maximum,
|
|
101
|
+
"pattern": (
|
|
102
|
+
_MODEL_TEXT_PATTERN if allow_empty else _MODEL_NONBLANK_TEXT_PATTERN
|
|
103
|
+
),
|
|
104
|
+
}
|
|
105
|
+
if not allow_empty:
|
|
106
|
+
schema["minLength"] = 1
|
|
107
|
+
return schema
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def identifier_model_schema() -> dict[str, Any]:
|
|
111
|
+
"""Return the exact model-visible governed identifier grammar."""
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
"type": "string",
|
|
115
|
+
"description": (
|
|
116
|
+
"Canonical identifier: starts with an ASCII letter or digit; "
|
|
117
|
+
"remaining characters are ASCII letters, digits, '.', '_', ':', "
|
|
118
|
+
"'/', or '-'."
|
|
119
|
+
),
|
|
120
|
+
"maxLength": IDENTIFIER_MAX_CHARS,
|
|
121
|
+
"pattern": IDENTIFIER_RE.pattern,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _details_definitions() -> dict[str, Any]:
|
|
126
|
+
"""Build a finite JSON Schema projection of the bounded details tree.
|
|
127
|
+
|
|
128
|
+
Standard JSON Schema has no aggregate descendant-node counter. The exact
|
|
129
|
+
whole-tree budget is therefore stated in the portable field description
|
|
130
|
+
and enforced by both strict runtime validators. Per-container limits here
|
|
131
|
+
enforce every locally possible overflow without rejecting valid branched
|
|
132
|
+
values merely to approximate that aggregate budget.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
definitions: dict[str, Any] = {}
|
|
136
|
+
key_schema = {
|
|
137
|
+
"type": "string",
|
|
138
|
+
"maxLength": BOUNDED_JSON_KEY_MAX_CHARS,
|
|
139
|
+
"pattern": IDENTIFIER_RE.pattern,
|
|
140
|
+
}
|
|
141
|
+
scalar = [
|
|
142
|
+
{"type": "null"},
|
|
143
|
+
{"type": "boolean"},
|
|
144
|
+
# JSON Schema's mathematical number model cannot distinguish a Python
|
|
145
|
+
# integer from an integral float. Bounding the numeric branch is the
|
|
146
|
+
# portable safe subset: every advertised number passes both validators.
|
|
147
|
+
{
|
|
148
|
+
"type": "number",
|
|
149
|
+
"minimum": -BOUNDED_JSON_INTEGER_MAX,
|
|
150
|
+
"maximum": BOUNDED_JSON_INTEGER_MAX,
|
|
151
|
+
},
|
|
152
|
+
_text_model_schema(
|
|
153
|
+
BOUNDED_JSON_STRING_MAX_CHARS, allow_empty=True,
|
|
154
|
+
),
|
|
155
|
+
]
|
|
156
|
+
for depth in range(BOUNDED_JSON_MAX_DEPTH, 0, -1):
|
|
157
|
+
if depth == BOUNDED_JSON_MAX_DEPTH:
|
|
158
|
+
containers = [
|
|
159
|
+
{"type": "array", "maxItems": 0},
|
|
160
|
+
{
|
|
161
|
+
"type": "object",
|
|
162
|
+
"propertyNames": key_schema,
|
|
163
|
+
"maxProperties": 0,
|
|
164
|
+
"additionalProperties": False,
|
|
165
|
+
},
|
|
166
|
+
]
|
|
167
|
+
else:
|
|
168
|
+
child = {"$ref": f"#/$defs/detailsValue{depth + 1}"}
|
|
169
|
+
# The root and this container's ancestors have already consumed
|
|
170
|
+
# ``depth + 1`` nodes. This is the largest local collection that
|
|
171
|
+
# can still fit the runtime's whole-tree budget.
|
|
172
|
+
container_maximum = BOUNDED_JSON_MAX_NODES - depth - 1
|
|
173
|
+
containers = [
|
|
174
|
+
{
|
|
175
|
+
"type": "array",
|
|
176
|
+
"maxItems": container_maximum,
|
|
177
|
+
"items": child,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"type": "object",
|
|
181
|
+
"propertyNames": key_schema,
|
|
182
|
+
"maxProperties": container_maximum,
|
|
183
|
+
"additionalProperties": child,
|
|
184
|
+
},
|
|
185
|
+
]
|
|
186
|
+
definitions[f"detailsValue{depth}"] = {"anyOf": scalar + containers}
|
|
187
|
+
return definitions
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
_DETAILS_DEFINITIONS = _details_definitions()
|
|
191
|
+
_DETAILS_DESCRIPTION = (
|
|
192
|
+
f"Bounded JSON object: at most {BOUNDED_JSON_MAX_NODES} total values "
|
|
193
|
+
f"including this object; maximum nesting depth {BOUNDED_JSON_MAX_DEPTH}; "
|
|
194
|
+
f"strings at most {BOUNDED_JSON_STRING_MAX_CHARS} characters; object keys "
|
|
195
|
+
f"at most {BOUNDED_JSON_KEY_MAX_CHARS} characters, starting with an ASCII "
|
|
196
|
+
"letter or digit and then using only ASCII letters, digits, '.', '_', ':', "
|
|
197
|
+
"'/', or '-'. Numeric values must be finite and within the signed 63-bit "
|
|
198
|
+
"range."
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def details_model_schema() -> dict[str, Any]:
|
|
203
|
+
"""Return the portable advertised bounded-details root schema."""
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
"type": "object",
|
|
207
|
+
"description": _DETAILS_DESCRIPTION,
|
|
208
|
+
"propertyNames": {
|
|
209
|
+
"type": "string",
|
|
210
|
+
"maxLength": BOUNDED_JSON_KEY_MAX_CHARS,
|
|
211
|
+
"pattern": IDENTIFIER_RE.pattern,
|
|
212
|
+
},
|
|
213
|
+
"maxProperties": BOUNDED_JSON_MAX_NODES - 1,
|
|
214
|
+
"additionalProperties": {"$ref": "#/$defs/detailsValue1"},
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# ---------------------------------------------------------------------------
|
|
219
|
+
# Argument validators (one per tool, exact bounded contracts)
|
|
220
|
+
# ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _args_autonomy(raw: Any) -> dict[str, Any]:
|
|
224
|
+
return exact_mapping(raw, "args", allowed=frozenset())
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _args_create_commitment(raw: Any) -> dict[str, Any]:
|
|
228
|
+
args = exact_mapping(
|
|
229
|
+
raw,
|
|
230
|
+
"args",
|
|
231
|
+
allowed={"description", "due_at", "priority"},
|
|
232
|
+
required={"description"},
|
|
233
|
+
)
|
|
234
|
+
bounded_text(
|
|
235
|
+
args["description"], "description", COMMITMENT_DESCRIPTION_MAX_CHARS,
|
|
236
|
+
)
|
|
237
|
+
if "due_at" in args:
|
|
238
|
+
bounded_text(
|
|
239
|
+
args["due_at"], "due_at", COMMITMENT_DUE_AT_MAX_CHARS,
|
|
240
|
+
allow_empty=True,
|
|
241
|
+
)
|
|
242
|
+
if "priority" in args:
|
|
243
|
+
bounded_integer(args["priority"], "priority", 0, 100)
|
|
244
|
+
return args
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _args_initiative_feedback(raw: Any) -> dict[str, Any]:
|
|
248
|
+
args = exact_mapping(
|
|
249
|
+
raw,
|
|
250
|
+
"args",
|
|
251
|
+
allowed={"initiative_id", "action", "details"},
|
|
252
|
+
required={"initiative_id", "action"},
|
|
253
|
+
)
|
|
254
|
+
bounded_text(
|
|
255
|
+
args["initiative_id"], "initiative_id", IDENTIFIER_MAX_CHARS,
|
|
256
|
+
identifier=True,
|
|
257
|
+
)
|
|
258
|
+
enum_text(
|
|
259
|
+
args["action"],
|
|
260
|
+
"action",
|
|
261
|
+
frozenset({"acknowledged", "actioned", "dismissed", "snoozed"}),
|
|
262
|
+
)
|
|
263
|
+
if "details" in args:
|
|
264
|
+
if not isinstance(args["details"], Mapping):
|
|
265
|
+
raise GovernedContractError("details must be an object")
|
|
266
|
+
args["details"] = bounded_json_value(args["details"], "details")
|
|
267
|
+
return args
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _args_record_insight(raw: Any) -> dict[str, Any]:
|
|
271
|
+
args = exact_mapping(
|
|
272
|
+
raw,
|
|
273
|
+
"args",
|
|
274
|
+
allowed={"confidence", "content", "insight_type"},
|
|
275
|
+
required={"content", "insight_type"},
|
|
276
|
+
)
|
|
277
|
+
bounded_text(args["content"], "content", INSIGHT_CONTENT_MAX_CHARS)
|
|
278
|
+
enum_text(
|
|
279
|
+
args["insight_type"],
|
|
280
|
+
"insight_type",
|
|
281
|
+
frozenset(
|
|
282
|
+
{
|
|
283
|
+
"preference",
|
|
284
|
+
"connection",
|
|
285
|
+
"fact",
|
|
286
|
+
"goal_hint",
|
|
287
|
+
"relationship_update",
|
|
288
|
+
}
|
|
289
|
+
),
|
|
290
|
+
)
|
|
291
|
+
if "confidence" in args:
|
|
292
|
+
bounded_number(args["confidence"], "confidence", 0.0, 1.0)
|
|
293
|
+
return args
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _args_research(raw: Any) -> dict[str, Any]:
|
|
297
|
+
args = exact_mapping(
|
|
298
|
+
raw, "args", allowed={"depth", "topic"}, required={"topic"},
|
|
299
|
+
)
|
|
300
|
+
bounded_text(args["topic"], "topic", RESEARCH_TOPIC_MAX_CHARS)
|
|
301
|
+
if "depth" in args:
|
|
302
|
+
enum_text(args["depth"], "depth", frozenset({"quick", "standard", "deep"}))
|
|
303
|
+
return args
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _args_resolve_commitment(raw: Any) -> dict[str, Any]:
|
|
307
|
+
args = exact_mapping(
|
|
308
|
+
raw,
|
|
309
|
+
"args",
|
|
310
|
+
allowed={"commitment_id", "outcome", "reason"},
|
|
311
|
+
required={"commitment_id"},
|
|
312
|
+
)
|
|
313
|
+
bounded_text(
|
|
314
|
+
args["commitment_id"], "commitment_id", IDENTIFIER_MAX_CHARS,
|
|
315
|
+
identifier=True,
|
|
316
|
+
)
|
|
317
|
+
if "outcome" in args:
|
|
318
|
+
enum_text(
|
|
319
|
+
args["outcome"],
|
|
320
|
+
"outcome",
|
|
321
|
+
frozenset({"done", "invalid", "duplicate", "wont_do", "obsolete"}),
|
|
322
|
+
)
|
|
323
|
+
if "reason" in args:
|
|
324
|
+
bounded_text(
|
|
325
|
+
args["reason"], "reason", FREEFORM_REASON_MAX_CHARS,
|
|
326
|
+
allow_empty=True,
|
|
327
|
+
)
|
|
328
|
+
return args
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _args_task_complete(raw: Any) -> dict[str, Any]:
|
|
332
|
+
args = exact_mapping(raw, "args", allowed={"task_id"}, required={"task_id"})
|
|
333
|
+
bounded_text(
|
|
334
|
+
args["task_id"], "task_id", IDENTIFIER_MAX_CHARS, identifier=True,
|
|
335
|
+
)
|
|
336
|
+
return args
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _args_task_dismiss(raw: Any) -> dict[str, Any]:
|
|
340
|
+
args = exact_mapping(
|
|
341
|
+
raw, "args", allowed={"reason", "task_id"}, required={"task_id"},
|
|
342
|
+
)
|
|
343
|
+
bounded_text(
|
|
344
|
+
args["task_id"], "task_id", IDENTIFIER_MAX_CHARS, identifier=True,
|
|
345
|
+
)
|
|
346
|
+
if "reason" in args:
|
|
347
|
+
enum_text(
|
|
348
|
+
args["reason"],
|
|
349
|
+
"reason",
|
|
350
|
+
frozenset({"stale", "completed", "abandoned", "not_applicable"}),
|
|
351
|
+
)
|
|
352
|
+
return args
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _args_task_snooze(raw: Any) -> dict[str, Any]:
|
|
356
|
+
args = exact_mapping(
|
|
357
|
+
raw,
|
|
358
|
+
"args",
|
|
359
|
+
allowed={"hours", "reason", "task_id"},
|
|
360
|
+
required={"task_id"},
|
|
361
|
+
)
|
|
362
|
+
bounded_text(
|
|
363
|
+
args["task_id"], "task_id", IDENTIFIER_MAX_CHARS, identifier=True,
|
|
364
|
+
)
|
|
365
|
+
if "hours" in args:
|
|
366
|
+
bounded_integer(args["hours"], "hours", 1, 168)
|
|
367
|
+
if "reason" in args:
|
|
368
|
+
bounded_text(
|
|
369
|
+
args["reason"], "reason", FREEFORM_REASON_MAX_CHARS,
|
|
370
|
+
allow_empty=True,
|
|
371
|
+
)
|
|
372
|
+
return args
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
# ---------------------------------------------------------------------------
|
|
376
|
+
# Approval-display metadata (owner-facing summary/target/risk)
|
|
377
|
+
# ---------------------------------------------------------------------------
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _display_autonomy_disable(_args: Mapping[str, Any]) -> dict[str, str]:
|
|
381
|
+
return {
|
|
382
|
+
"summary": "Disable Colony autonomous scheduling",
|
|
383
|
+
"target": "Colony autonomy scheduler",
|
|
384
|
+
"risk": "Autonomous work will stop until it is explicitly enabled again",
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _display_autonomy_enable(_args: Mapping[str, Any]) -> dict[str, str]:
|
|
389
|
+
return {
|
|
390
|
+
"summary": "Enable Colony autonomous scheduling",
|
|
391
|
+
"target": "Colony autonomy scheduler",
|
|
392
|
+
"risk": (
|
|
393
|
+
"Colony may begin bounded autonomous work under its configured policies"
|
|
394
|
+
),
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _display_create_commitment(args: Mapping[str, Any]) -> dict[str, str]:
|
|
399
|
+
return {
|
|
400
|
+
"summary": "Create Colony commitment %s" % _preview(args["description"]),
|
|
401
|
+
"target": "Private Colony commitment ledger",
|
|
402
|
+
"risk": (
|
|
403
|
+
"Adds one durable personal commitment; "
|
|
404
|
+
"no external communication is sent"
|
|
405
|
+
),
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _display_initiative_feedback(args: Mapping[str, Any]) -> dict[str, str]:
|
|
410
|
+
return {
|
|
411
|
+
"summary": "Mark initiative %s as %s"
|
|
412
|
+
% (_preview(args["initiative_id"]), args["action"]),
|
|
413
|
+
"target": "Private Colony initiative ledger",
|
|
414
|
+
"risk": "Changes Colony's internal initiative state and learning evidence",
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _display_record_insight(args: Mapping[str, Any]) -> dict[str, str]:
|
|
419
|
+
return {
|
|
420
|
+
"summary": "Record %s insight %s"
|
|
421
|
+
% (args["insight_type"], _preview(args["content"])),
|
|
422
|
+
"target": "Private Colony memory and learning store",
|
|
423
|
+
"risk": (
|
|
424
|
+
"Persists model-derived personal context that can influence "
|
|
425
|
+
"future reasoning"
|
|
426
|
+
),
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _display_research(args: Mapping[str, Any]) -> dict[str, str]:
|
|
431
|
+
return {
|
|
432
|
+
"summary": "Start %s Colony research on %s"
|
|
433
|
+
% (args.get("depth", "quick"), _preview(args["topic"])),
|
|
434
|
+
"target": "Colony research queue",
|
|
435
|
+
"risk": (
|
|
436
|
+
"May consume configured research resources; "
|
|
437
|
+
"external publishing is not authorized"
|
|
438
|
+
),
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _display_resolve_commitment(args: Mapping[str, Any]) -> dict[str, str]:
|
|
443
|
+
return {
|
|
444
|
+
"summary": "Resolve commitment %s as %s"
|
|
445
|
+
% (_preview(args["commitment_id"]), args.get("outcome", "done")),
|
|
446
|
+
"target": "Private Colony commitment ledger",
|
|
447
|
+
"risk": "Changes one durable commitment's terminal state",
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _display_task_complete(args: Mapping[str, Any]) -> dict[str, str]:
|
|
452
|
+
return {
|
|
453
|
+
"summary": "Complete Colony task %s" % _preview(args["task_id"]),
|
|
454
|
+
"target": "Private Colony task or initiative ledger",
|
|
455
|
+
"risk": "Marks one internal task or initiative complete",
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _display_task_dismiss(args: Mapping[str, Any]) -> dict[str, str]:
|
|
460
|
+
return {
|
|
461
|
+
"summary": "Dismiss Colony task %s as %s"
|
|
462
|
+
% (_preview(args["task_id"]), args.get("reason", "stale")),
|
|
463
|
+
"target": "Private Colony task or initiative ledger",
|
|
464
|
+
"risk": "Dismisses one internal task or initiative from active work",
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def _display_task_snooze(args: Mapping[str, Any]) -> dict[str, str]:
|
|
469
|
+
return {
|
|
470
|
+
"summary": "Snooze Colony task %s for %d hours"
|
|
471
|
+
% (_preview(args["task_id"]), args.get("hours", 24)),
|
|
472
|
+
"target": "Private Colony task or initiative ledger",
|
|
473
|
+
"risk": (
|
|
474
|
+
"Defers one internal task or initiative until the bounded "
|
|
475
|
+
"snooze expires"
|
|
476
|
+
),
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
# ---------------------------------------------------------------------------
|
|
481
|
+
# The catalog
|
|
482
|
+
# ---------------------------------------------------------------------------
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
@dataclass(frozen=True, slots=True)
|
|
486
|
+
class ToolSpec:
|
|
487
|
+
"""One governed tool and its complete model/validation contract."""
|
|
488
|
+
|
|
489
|
+
name: str
|
|
490
|
+
model_description: str
|
|
491
|
+
model_parameters: Mapping[str, Any]
|
|
492
|
+
validate_args: Callable[[Any], dict[str, Any]]
|
|
493
|
+
approval_display: Callable[[Mapping[str, Any]], dict[str, str]]
|
|
494
|
+
# True → this tool must always require a per-message owner approval and
|
|
495
|
+
# can never be authorized by a standing bounded grant (owner decision).
|
|
496
|
+
non_grantable: bool = False
|
|
497
|
+
|
|
498
|
+
@property
|
|
499
|
+
def model_schema(self) -> dict[str, Any]:
|
|
500
|
+
"""Return an isolated copy safe for a model-facing plugin catalog."""
|
|
501
|
+
|
|
502
|
+
return {
|
|
503
|
+
"name": self.name,
|
|
504
|
+
"description": self.model_description,
|
|
505
|
+
"parameters": copy.deepcopy(self.model_parameters),
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
TOOL_CATALOG: dict[str, ToolSpec] = {
|
|
510
|
+
spec.name: spec
|
|
511
|
+
for spec in (
|
|
512
|
+
ToolSpec(
|
|
513
|
+
name="colony_autonomy_disable",
|
|
514
|
+
model_description=(
|
|
515
|
+
"Submit a governed intent to disable autonomous work scheduling."
|
|
516
|
+
),
|
|
517
|
+
model_parameters=_parameters({}),
|
|
518
|
+
validate_args=_args_autonomy,
|
|
519
|
+
approval_display=_display_autonomy_disable,
|
|
520
|
+
non_grantable=True,
|
|
521
|
+
),
|
|
522
|
+
ToolSpec(
|
|
523
|
+
name="colony_autonomy_enable",
|
|
524
|
+
model_description=(
|
|
525
|
+
"Submit a governed intent to enable autonomous work scheduling."
|
|
526
|
+
),
|
|
527
|
+
model_parameters=_parameters({}),
|
|
528
|
+
validate_args=_args_autonomy,
|
|
529
|
+
approval_display=_display_autonomy_enable,
|
|
530
|
+
non_grantable=True,
|
|
531
|
+
),
|
|
532
|
+
ToolSpec(
|
|
533
|
+
name="colony_create_commitment",
|
|
534
|
+
model_description=(
|
|
535
|
+
"Submit a governed intent to create a commitment for this "
|
|
536
|
+
"participant."
|
|
537
|
+
),
|
|
538
|
+
model_parameters=_parameters(
|
|
539
|
+
{
|
|
540
|
+
"description": _text_model_schema(
|
|
541
|
+
COMMITMENT_DESCRIPTION_MAX_CHARS,
|
|
542
|
+
),
|
|
543
|
+
"due_at": _text_model_schema(
|
|
544
|
+
COMMITMENT_DUE_AT_MAX_CHARS, allow_empty=True,
|
|
545
|
+
),
|
|
546
|
+
"priority": {
|
|
547
|
+
"type": "integer",
|
|
548
|
+
"minimum": 0,
|
|
549
|
+
"maximum": 100,
|
|
550
|
+
"default": COMMITMENT_PRIORITY_DEFAULT,
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
("description",),
|
|
554
|
+
),
|
|
555
|
+
validate_args=_args_create_commitment,
|
|
556
|
+
approval_display=_display_create_commitment,
|
|
557
|
+
),
|
|
558
|
+
ToolSpec(
|
|
559
|
+
name="colony_initiative_feedback",
|
|
560
|
+
model_description=(
|
|
561
|
+
"Submit a governed intent describing an initiative outcome."
|
|
562
|
+
),
|
|
563
|
+
model_parameters=_parameters(
|
|
564
|
+
{
|
|
565
|
+
"action": {
|
|
566
|
+
"type": "string",
|
|
567
|
+
"enum": [
|
|
568
|
+
"acknowledged", "actioned", "dismissed", "snoozed",
|
|
569
|
+
],
|
|
570
|
+
},
|
|
571
|
+
"details": details_model_schema(),
|
|
572
|
+
"initiative_id": identifier_model_schema(),
|
|
573
|
+
},
|
|
574
|
+
("initiative_id", "action"),
|
|
575
|
+
definitions=_DETAILS_DEFINITIONS,
|
|
576
|
+
),
|
|
577
|
+
validate_args=_args_initiative_feedback,
|
|
578
|
+
approval_display=_display_initiative_feedback,
|
|
579
|
+
),
|
|
580
|
+
ToolSpec(
|
|
581
|
+
name="colony_record_insight",
|
|
582
|
+
model_description=(
|
|
583
|
+
"Submit a governed intent to record a conversational insight."
|
|
584
|
+
),
|
|
585
|
+
model_parameters=_parameters(
|
|
586
|
+
{
|
|
587
|
+
"confidence": {
|
|
588
|
+
"type": "number",
|
|
589
|
+
"minimum": 0,
|
|
590
|
+
"maximum": 1,
|
|
591
|
+
"default": 0.7,
|
|
592
|
+
},
|
|
593
|
+
"content": _text_model_schema(INSIGHT_CONTENT_MAX_CHARS),
|
|
594
|
+
"insight_type": {
|
|
595
|
+
"type": "string",
|
|
596
|
+
"enum": [
|
|
597
|
+
"preference", "connection", "fact", "goal_hint",
|
|
598
|
+
"relationship_update",
|
|
599
|
+
],
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
("insight_type", "content"),
|
|
603
|
+
),
|
|
604
|
+
validate_args=_args_record_insight,
|
|
605
|
+
approval_display=_display_record_insight,
|
|
606
|
+
),
|
|
607
|
+
ToolSpec(
|
|
608
|
+
name="colony_research",
|
|
609
|
+
model_description=(
|
|
610
|
+
"Submit a governed intent to queue durable Colony research."
|
|
611
|
+
),
|
|
612
|
+
model_parameters=_parameters(
|
|
613
|
+
{
|
|
614
|
+
"depth": {
|
|
615
|
+
"type": "string",
|
|
616
|
+
"enum": ["quick", "standard", "deep"],
|
|
617
|
+
"default": "quick",
|
|
618
|
+
},
|
|
619
|
+
"topic": _text_model_schema(RESEARCH_TOPIC_MAX_CHARS),
|
|
620
|
+
},
|
|
621
|
+
("topic",),
|
|
622
|
+
),
|
|
623
|
+
validate_args=_args_research,
|
|
624
|
+
approval_display=_display_research,
|
|
625
|
+
),
|
|
626
|
+
ToolSpec(
|
|
627
|
+
name="colony_resolve_commitment",
|
|
628
|
+
model_description=(
|
|
629
|
+
"Submit a governed intent to resolve a commitment."
|
|
630
|
+
),
|
|
631
|
+
model_parameters=_parameters(
|
|
632
|
+
{
|
|
633
|
+
"commitment_id": identifier_model_schema(),
|
|
634
|
+
"outcome": {
|
|
635
|
+
"type": "string",
|
|
636
|
+
"enum": [
|
|
637
|
+
"done", "invalid", "duplicate", "wont_do", "obsolete",
|
|
638
|
+
],
|
|
639
|
+
"default": "done",
|
|
640
|
+
},
|
|
641
|
+
"reason": _text_model_schema(
|
|
642
|
+
FREEFORM_REASON_MAX_CHARS, allow_empty=True,
|
|
643
|
+
),
|
|
644
|
+
},
|
|
645
|
+
("commitment_id",),
|
|
646
|
+
),
|
|
647
|
+
validate_args=_args_resolve_commitment,
|
|
648
|
+
approval_display=_display_resolve_commitment,
|
|
649
|
+
),
|
|
650
|
+
ToolSpec(
|
|
651
|
+
name="colony_task_complete",
|
|
652
|
+
model_description=(
|
|
653
|
+
"Submit a governed intent to complete a task or initiative."
|
|
654
|
+
),
|
|
655
|
+
model_parameters=_parameters(
|
|
656
|
+
{"task_id": identifier_model_schema()}, ("task_id",),
|
|
657
|
+
),
|
|
658
|
+
validate_args=_args_task_complete,
|
|
659
|
+
approval_display=_display_task_complete,
|
|
660
|
+
),
|
|
661
|
+
ToolSpec(
|
|
662
|
+
name="colony_task_dismiss",
|
|
663
|
+
model_description=(
|
|
664
|
+
"Submit a governed intent to dismiss a task or initiative."
|
|
665
|
+
),
|
|
666
|
+
model_parameters=_parameters(
|
|
667
|
+
{
|
|
668
|
+
"reason": {
|
|
669
|
+
"type": "string",
|
|
670
|
+
"enum": [
|
|
671
|
+
"stale", "completed", "abandoned", "not_applicable",
|
|
672
|
+
],
|
|
673
|
+
"default": "stale",
|
|
674
|
+
},
|
|
675
|
+
"task_id": identifier_model_schema(),
|
|
676
|
+
},
|
|
677
|
+
("task_id",),
|
|
678
|
+
),
|
|
679
|
+
validate_args=_args_task_dismiss,
|
|
680
|
+
approval_display=_display_task_dismiss,
|
|
681
|
+
),
|
|
682
|
+
ToolSpec(
|
|
683
|
+
name="colony_task_snooze",
|
|
684
|
+
model_description=(
|
|
685
|
+
"Submit a governed intent to snooze a task or initiative."
|
|
686
|
+
),
|
|
687
|
+
model_parameters=_parameters(
|
|
688
|
+
{
|
|
689
|
+
"hours": {
|
|
690
|
+
"type": "integer",
|
|
691
|
+
"minimum": 1,
|
|
692
|
+
"maximum": 168,
|
|
693
|
+
"default": 24,
|
|
694
|
+
},
|
|
695
|
+
"reason": {
|
|
696
|
+
**_text_model_schema(
|
|
697
|
+
FREEFORM_REASON_MAX_CHARS, allow_empty=True,
|
|
698
|
+
),
|
|
699
|
+
"default": "",
|
|
700
|
+
},
|
|
701
|
+
"task_id": identifier_model_schema(),
|
|
702
|
+
},
|
|
703
|
+
("task_id",),
|
|
704
|
+
),
|
|
705
|
+
validate_args=_args_task_snooze,
|
|
706
|
+
approval_display=_display_task_snooze,
|
|
707
|
+
),
|
|
708
|
+
)
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
ACTION_TOOL_NAMES = frozenset(TOOL_CATALOG)
|
|
712
|
+
NON_GRANTABLE_TOOL_NAMES = frozenset(
|
|
713
|
+
name for name, spec in TOOL_CATALOG.items() if spec.non_grantable
|
|
714
|
+
)
|
|
715
|
+
GRANT_AUTHORIZABLE_TOOL_NAMES = ACTION_TOOL_NAMES - NON_GRANTABLE_TOOL_NAMES
|
|
716
|
+
ACTION_MODEL_TOOL_SCHEMAS = tuple(
|
|
717
|
+
TOOL_CATALOG[name].model_schema for name in sorted(ACTION_TOOL_NAMES)
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def validate_tool_args(tool_name: Any, raw: Any) -> dict[str, Any]:
|
|
722
|
+
"""Validate one tool call against the catalog's exact bounded contract."""
|
|
723
|
+
|
|
724
|
+
spec = TOOL_CATALOG.get(tool_name) if isinstance(tool_name, str) else None
|
|
725
|
+
if spec is None:
|
|
726
|
+
raise ToolCatalogError("tool is not a governed Colony action")
|
|
727
|
+
return spec.validate_args(raw)
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
__all__ = (
|
|
731
|
+
"ACTION_MODEL_TOOL_SCHEMAS",
|
|
732
|
+
"ACTION_TOOL_NAMES",
|
|
733
|
+
"BOUNDED_JSON_INTEGER_MAX",
|
|
734
|
+
"BOUNDED_JSON_KEY_MAX_CHARS",
|
|
735
|
+
"BOUNDED_JSON_MAX_DEPTH",
|
|
736
|
+
"BOUNDED_JSON_MAX_NODES",
|
|
737
|
+
"BOUNDED_JSON_STRING_MAX_CHARS",
|
|
738
|
+
"COMMITMENT_DESCRIPTION_MAX_CHARS",
|
|
739
|
+
"COMMITMENT_DUE_AT_MAX_CHARS",
|
|
740
|
+
"COMMITMENT_PRIORITY_DEFAULT",
|
|
741
|
+
"FREEFORM_REASON_MAX_CHARS",
|
|
742
|
+
"GRANT_AUTHORIZABLE_TOOL_NAMES",
|
|
743
|
+
"IDENTIFIER_MAX_CHARS",
|
|
744
|
+
"IDENTIFIER_RE",
|
|
745
|
+
"INSIGHT_CONTENT_MAX_CHARS",
|
|
746
|
+
"NON_GRANTABLE_TOOL_NAMES",
|
|
747
|
+
"RESEARCH_TOPIC_MAX_CHARS",
|
|
748
|
+
"TOOL_CATALOG",
|
|
749
|
+
"ToolCatalogError",
|
|
750
|
+
"ToolSpec",
|
|
751
|
+
"details_model_schema",
|
|
752
|
+
"identifier_model_schema",
|
|
753
|
+
"validate_tool_args",
|
|
754
|
+
)
|