@kody-ade/kody-engine 0.4.430 → 0.4.432

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.
@@ -0,0 +1,160 @@
1
+ """Managed-goal todo state contract for goal-scheduler.
2
+
3
+ The scheduler owns timing and dispatch. This module owns the todo JSON shape
4
+ used for managed goal state.
5
+ """
6
+
7
+ import json
8
+
9
+
10
+ def is_managed_todo_text(text: str) -> bool:
11
+ data = parse_json_object(text)
12
+ return is_managed_todo_data(data) if data is not None else False
13
+
14
+
15
+ def is_managed_todo_data(data: dict) -> bool:
16
+ return (
17
+ data.get("managed") is True
18
+ or data.get("managed") == "true"
19
+ or data.get("managedModel") in ("agentGoal", "agentLoop")
20
+ )
21
+
22
+
23
+ def parse_json_object(text: str) -> dict | None:
24
+ try:
25
+ data = json.loads(text)
26
+ except Exception:
27
+ return None
28
+ return data if isinstance(data, dict) else None
29
+
30
+
31
+ def string_list(value: object) -> list[str]:
32
+ return [item for item in value if isinstance(item, str)] if isinstance(value, list) else []
33
+
34
+
35
+ def route_from_items(items: list[dict]) -> list[dict]:
36
+ route: list[dict] = []
37
+ for item in items:
38
+ meta = item.get("meta") if isinstance(item.get("meta"), dict) else {}
39
+ stage = meta.get("stage")
40
+ evidence = meta.get("evidence") or item.get("id")
41
+ capability = meta.get("capability")
42
+ if isinstance(stage, str) and isinstance(evidence, str) and isinstance(capability, str):
43
+ step = {"stage": stage, "evidence": evidence, "capability": capability}
44
+ if isinstance(meta.get("args"), dict):
45
+ step["args"] = meta["args"]
46
+ if meta.get("saveReport") is True:
47
+ step["saveReport"] = True
48
+ route.append(step)
49
+ return route
50
+
51
+
52
+ def parse_json_todo_goal_state(goal_id: str, data: dict) -> dict:
53
+ items = [item for item in data.get("items", []) if isinstance(item, dict)] if isinstance(data.get("items"), list) else []
54
+ raw_destination = data.get("destination") if isinstance(data.get("destination"), dict) else {}
55
+ route = data.get("route") if isinstance(data.get("route"), list) else route_from_items(items)
56
+ evidence = string_list(raw_destination.get("evidence")) or string_list(data.get("evidence"))
57
+ if not evidence:
58
+ evidence = [
59
+ str((item.get("meta") if isinstance(item.get("meta"), dict) else {}).get("evidence") or item.get("id"))
60
+ for item in items
61
+ if item.get("id")
62
+ ]
63
+ facts = data.get("facts") if isinstance(data.get("facts"), dict) else {}
64
+ facts = dict(facts)
65
+ for item in items:
66
+ meta = item.get("meta") if isinstance(item.get("meta"), dict) else {}
67
+ key = meta.get("evidence") or item.get("id")
68
+ if isinstance(key, str):
69
+ facts[key] = item.get("completed") is True
70
+ capabilities = string_list(data.get("capabilities"))
71
+ if not capabilities:
72
+ capabilities = [step["capability"] for step in route if isinstance(step.get("capability"), str)]
73
+ outcome = data.get("description") if isinstance(data.get("description"), str) else raw_destination.get("outcome")
74
+ parsed = dict(data)
75
+ parsed.update(
76
+ {
77
+ "id": goal_id,
78
+ "version": data.get("version", 1),
79
+ "state": data.get("state", "active"),
80
+ "type": data.get("type", "general"),
81
+ "destination": {**raw_destination, "outcome": outcome if isinstance(outcome, str) else "", "evidence": evidence},
82
+ "capabilities": capabilities,
83
+ "route": route,
84
+ "facts": facts,
85
+ "blockers": string_list(data.get("blockers")),
86
+ }
87
+ )
88
+ return parsed
89
+
90
+
91
+ def parse_todo_goal_state(goal_id: str, text: str) -> dict:
92
+ data = parse_json_object(text)
93
+ if data is None:
94
+ raise ValueError(f"goal {goal_id} todo state must be JSON")
95
+ return parse_json_todo_goal_state(goal_id, data)
96
+
97
+
98
+ def todo_items_from_state(data: dict, now: str) -> list[dict]:
99
+ destination = data.get("destination") if isinstance(data.get("destination"), dict) else {}
100
+ evidence = string_list(destination.get("evidence"))
101
+ route = data.get("route") if isinstance(data.get("route"), list) else []
102
+ facts = data.get("facts") if isinstance(data.get("facts"), dict) else {}
103
+ route_by_evidence = {
104
+ step.get("evidence"): step
105
+ for step in route
106
+ if isinstance(step, dict) and isinstance(step.get("evidence"), str)
107
+ }
108
+ if evidence:
109
+ items = []
110
+ for key in evidence:
111
+ step = route_by_evidence.get(key) if isinstance(route_by_evidence.get(key), dict) else {}
112
+ completed = facts.get(key) is True
113
+ items.append(
114
+ {
115
+ "id": key,
116
+ "title": step.get("stage") if isinstance(step.get("stage"), str) else key,
117
+ "body": "",
118
+ "assignee": None,
119
+ "completed": completed,
120
+ "createdAt": data.get("createdAt") if isinstance(data.get("createdAt"), str) else now,
121
+ "completedAt": data.get("updatedAt") if completed and isinstance(data.get("updatedAt"), str) else None,
122
+ "meta": {
123
+ "evidence": key,
124
+ **({"stage": step["stage"]} if isinstance(step.get("stage"), str) else {}),
125
+ **({"capability": step["capability"]} if isinstance(step.get("capability"), str) else {}),
126
+ **({"args": step["args"]} if isinstance(step.get("args"), dict) else {}),
127
+ **({"saveReport": True} if step.get("saveReport") is True else {}),
128
+ },
129
+ }
130
+ )
131
+ return items
132
+ return [
133
+ {
134
+ "id": capability,
135
+ "title": capability,
136
+ "body": "",
137
+ "assignee": None,
138
+ "completed": False,
139
+ "createdAt": data.get("createdAt") if isinstance(data.get("createdAt"), str) else now,
140
+ "completedAt": None,
141
+ "meta": {"capability": capability},
142
+ }
143
+ for capability in string_list(data.get("capabilities"))
144
+ ]
145
+
146
+
147
+ def serialize_todo_goal_state(goal_id: str, data: dict, now: str) -> str:
148
+ destination = data.get("destination") if isinstance(data.get("destination"), dict) else {}
149
+ outcome = destination.get("outcome") if isinstance(destination.get("outcome"), str) else ""
150
+ record = dict(data)
151
+ record["version"] = 1
152
+ record["id"] = goal_id
153
+ record["title"] = goal_id
154
+ record["description"] = outcome
155
+ record["createdAt"] = data.get("createdAt") if isinstance(data.get("createdAt"), str) else now
156
+ record["managed"] = True
157
+ record["managedModel"] = "agentLoop" if record.get("scheduleMode") == "agentLoop" or record.get("type") == "agentLoop" else "agentGoal"
158
+ record["evidence"] = string_list(destination.get("evidence"))
159
+ record["items"] = todo_items_from_state(data, now)
160
+ return json.dumps(record, indent=2) + "\n"
@@ -0,0 +1,51 @@
1
+ {
2
+ "internal": true,
3
+ "role": "watch",
4
+ "kind": "scheduled",
5
+ "schedule": "*/5 * * * *",
6
+ "claudeCode": {
7
+ "model": "inherit",
8
+ "permissionMode": "default",
9
+ "maxTurns": 0,
10
+ "maxThinkingTokens": null,
11
+ "systemPromptAppend": null,
12
+ "tools": [],
13
+ "hooks": [],
14
+ "skills": [],
15
+ "commands": [],
16
+ "subagents": [],
17
+ "plugins": [],
18
+ "mcpServers": []
19
+ },
20
+ "cliTools": [
21
+ {
22
+ "name": "python3",
23
+ "install": {
24
+ "required": true,
25
+ "checkCommand": "command -v python3"
26
+ },
27
+ "verify": "python3 --version",
28
+ "usage": "scheduler.sh parses each managed goal todo file",
29
+ "allowedUses": []
30
+ }
31
+ ],
32
+ "inputArtifacts": [],
33
+ "outputArtifacts": [],
34
+ "scripts": {
35
+ "preflight": [
36
+ {
37
+ "script": "dispatchAgencyLoops"
38
+ },
39
+ {
40
+ "shell": "scheduler.sh",
41
+ "timeoutSec": 1800
42
+ },
43
+ {
44
+ "script": "skipAgent"
45
+ }
46
+ ],
47
+ "postflight": []
48
+ },
49
+ "inputs": [],
50
+ "name": "goal-scheduler"
51
+ }