@kody-ade/kody-engine 0.4.431 → 0.4.433
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/dist/bin/kody.js +142 -91
- package/dist/runtime-services/capability-scheduler/profile.json +61 -0
- package/dist/runtime-services/capability-tick/profile.json +86 -0
- package/dist/runtime-services/capability-tick/prompt.md +77 -0
- package/dist/runtime-services/capability-tick/prompts/locked.md +50 -0
- package/dist/runtime-services/capability-tick-scripted/profile.json +76 -0
- package/dist/runtime-services/dispatch-due-loops/profile.json +54 -0
- package/dist/runtime-services/goal-manager/profile.json +56 -0
- package/dist/runtime-services/goal-scheduler/managed_todo_state.py +160 -0
- package/dist/runtime-services/goal-scheduler/profile.json +51 -0
- package/dist/runtime-services/goal-scheduler/scheduler.sh +872 -0
- package/dist/runtime-services/task-jobs/profile.json +51 -0
- package/package.json +2 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"internal": true,
|
|
3
|
+
"role": "watch",
|
|
4
|
+
"kind": "oneshot",
|
|
5
|
+
"claudeCode": {
|
|
6
|
+
"model": "inherit",
|
|
7
|
+
"permissionMode": "default",
|
|
8
|
+
"maxTurns": null,
|
|
9
|
+
"maxThinkingTokens": null,
|
|
10
|
+
"systemPromptAppend": null,
|
|
11
|
+
"tools": [],
|
|
12
|
+
"hooks": [],
|
|
13
|
+
"skills": [],
|
|
14
|
+
"commands": [],
|
|
15
|
+
"subagents": [],
|
|
16
|
+
"plugins": [],
|
|
17
|
+
"mcpServers": []
|
|
18
|
+
},
|
|
19
|
+
"cliTools": [
|
|
20
|
+
{
|
|
21
|
+
"name": "gh",
|
|
22
|
+
"install": {
|
|
23
|
+
"required": true,
|
|
24
|
+
"checkCommand": "command -v gh"
|
|
25
|
+
},
|
|
26
|
+
"verify": "gh auth status",
|
|
27
|
+
"usage": "",
|
|
28
|
+
"allowedUses": [
|
|
29
|
+
"api",
|
|
30
|
+
"issue",
|
|
31
|
+
"pr"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"inputArtifacts": [],
|
|
36
|
+
"outputArtifacts": [],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"preflight": [
|
|
39
|
+
{
|
|
40
|
+
"script": "dispatchCapabilityFileTicks",
|
|
41
|
+
"with": {
|
|
42
|
+
"jobsDir": ".kody/capabilities",
|
|
43
|
+
"targetImplementation": "capability-tick",
|
|
44
|
+
"slugArg": "capability",
|
|
45
|
+
"requireExplicitCapability": true
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"postflight": []
|
|
50
|
+
},
|
|
51
|
+
"inputs": [
|
|
52
|
+
{
|
|
53
|
+
"name": "capability",
|
|
54
|
+
"flag": "--capability",
|
|
55
|
+
"type": "string",
|
|
56
|
+
"required": false,
|
|
57
|
+
"describe": "Optional capability slug for legacy explicit runs. Scheduled fan-out belongs to goals and loops."
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"name": "capability-scheduler"
|
|
61
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"internal": true,
|
|
3
|
+
"role": "primitive",
|
|
4
|
+
"kind": "oneshot",
|
|
5
|
+
"claudeCode": {
|
|
6
|
+
"model": "inherit",
|
|
7
|
+
"permissionMode": "default",
|
|
8
|
+
"maxTurns": 100,
|
|
9
|
+
"maxThinkingTokens": null,
|
|
10
|
+
"systemPromptAppend": null,
|
|
11
|
+
"enableSubmitTool": true,
|
|
12
|
+
"tools": [
|
|
13
|
+
"Bash",
|
|
14
|
+
"Read",
|
|
15
|
+
"mcp__kody-submit"
|
|
16
|
+
],
|
|
17
|
+
"hooks": [],
|
|
18
|
+
"skills": [],
|
|
19
|
+
"commands": [],
|
|
20
|
+
"subagents": [],
|
|
21
|
+
"plugins": [],
|
|
22
|
+
"mcpServers": []
|
|
23
|
+
},
|
|
24
|
+
"cliTools": [
|
|
25
|
+
{
|
|
26
|
+
"name": "gh",
|
|
27
|
+
"install": {
|
|
28
|
+
"required": true,
|
|
29
|
+
"checkCommand": "command -v gh"
|
|
30
|
+
},
|
|
31
|
+
"verify": "gh auth status",
|
|
32
|
+
"usage": "Use `gh` for all GitHub actions: `gh pr list ...` to enumerate candidate PRs, `gh pr comment <n> --body \"...\"` to issue a Kody command, `gh pr view <n> --json mergeable,statusCheckRollup,headRefOid` to inspect state, `gh api ...` for anything else. NEVER edit files in the working tree.",
|
|
33
|
+
"allowedUses": [
|
|
34
|
+
"pr",
|
|
35
|
+
"api",
|
|
36
|
+
"issue"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"inputArtifacts": [],
|
|
41
|
+
"outputArtifacts": [],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"preflight": [
|
|
44
|
+
{
|
|
45
|
+
"script": "loadJobFromFile",
|
|
46
|
+
"with": {
|
|
47
|
+
"jobsDir": ".kody/capabilities",
|
|
48
|
+
"slugArg": "capability"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"script": "composePrompt"
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"postflight": [
|
|
56
|
+
{
|
|
57
|
+
"script": "parseJobStateFromAgentResult",
|
|
58
|
+
"with": {
|
|
59
|
+
"fenceLabel": "kody-job-next-state"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"script": "writeJobStateFile"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"script": "appendCompanyActivity"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"inputs": [
|
|
71
|
+
{
|
|
72
|
+
"name": "capability",
|
|
73
|
+
"flag": "--capability",
|
|
74
|
+
"type": "string",
|
|
75
|
+
"required": true,
|
|
76
|
+
"describe": "Capability slug — folder name under .kody/capabilities/."
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "force",
|
|
80
|
+
"flag": "--force",
|
|
81
|
+
"type": "bool",
|
|
82
|
+
"describe": "When true, this is an operator-forced run. All body rules (allowed commands, restrictions, state schema) still apply. Used for manual triggers from the dashboard's 'Run now' button."
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"name": "capability-tick"
|
|
86
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{{capabilityReference}}
|
|
2
|
+
|
|
3
|
+
You are **{{agentTitle}}** (agent `{{agentSlug}}`), running through **kody capability-tick** — the coordinator for one file-based capability. You do **not** touch code, do **not** commit, and do **not** edit files. You coordinate by inspecting GitHub state and issuing Kody commands as PR comments.
|
|
4
|
+
|
|
5
|
+
## Who you are — agent identity (authoritative identity)
|
|
6
|
+
|
|
7
|
+
The capability below assigns you, agent **`{{agentSlug}}`**, as its executor. This agent identity defines *who* runs the capability: your authority, doctrine, voice, and hard limits. Where the agent identity's restrictions are stricter than the capability body, **the agent identity wins** — a capability can never grant you authority your agent identity withholds.
|
|
8
|
+
|
|
9
|
+
{{agentIdentity}}
|
|
10
|
+
|
|
11
|
+
## The capability
|
|
12
|
+
|
|
13
|
+
Slug **`{{capabilitySlug}}`** — *{{capabilityTitle}}*, assigned to agent **`{{agentSlug}}`**, running on implementation **`{{executableSlug}}`**. The capability body below is authoritative for *what* to do, allowed commands, and state schema. The owning goal or loop decides when this runs. It is human-edited — re-read it every tick. Execute it **as** the agent identity above.
|
|
14
|
+
|
|
15
|
+
**Addressing the operator.** When the capability body tells you to @-mention the operator (e.g. the first line of an inbox recommendation), the exact handle(s) to use are: {{mentions}}. Copy that string **verbatim** — never invent, abbreviate, guess, or retype a GitHub username. A wrong handle silently fails to route to the operator's inbox, so the recommendation is lost. If the line above is blank, the capability declared no operator; post without a mention.
|
|
16
|
+
|
|
17
|
+
### Capability body
|
|
18
|
+
|
|
19
|
+
{{jobIntent}}
|
|
20
|
+
|
|
21
|
+
## Current state
|
|
22
|
+
|
|
23
|
+
This is the state you wrote at the end of the previous tick (or `null` if this is the first tick):
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{{jobStateJson}}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`cursor` is *your* enum — pick whatever labels map cleanly to your capability's phases. `data` is where you stash anything you need on the next tick (per-PR attempt counters, last-seen SHAs, etc). `done: true` is how you signal that the capability is permanently over — for evergreen capabilities this should always remain `false`.
|
|
30
|
+
|
|
31
|
+
## What to do on this tick
|
|
32
|
+
|
|
33
|
+
`forceRun = {{args.force}}` — set to `true` when an operator clicked "Run now" on the dashboard. When `forceRun` is `true`, treat this as an operator-requested execution. All body rules — allowed commands, restrictions, state schema — still apply.
|
|
34
|
+
|
|
35
|
+
1. **Check `done`.** If the prior state has `done: true`, emit the same state back unchanged and exit without any action.
|
|
36
|
+
2. **Re-read the capability body.** It may have changed since the last tick.
|
|
37
|
+
3. **Execute exactly the work the body's `## Capability` section describes**, subject to its `## Allowed Commands` and `## Restrictions`. Use the `## State` section to interpret and update `data`.
|
|
38
|
+
4. **Optionally post a short narration** wherever the capability tells you to (typically a PR comment alongside the action). Keep it terse.
|
|
39
|
+
5. **Submit the new state** by calling the `submit_state` tool (see contract below). Do not include `version` or `rev` — the postflight script manages those.
|
|
40
|
+
|
|
41
|
+
## Output contract (MANDATORY, exactly once, at the end)
|
|
42
|
+
|
|
43
|
+
Call the **`submit_state`** tool exactly once, as the final step, with your next state:
|
|
44
|
+
|
|
45
|
+
- `cursor` — your next cursor (string, e.g. `"idle"`).
|
|
46
|
+
- `data` — your next `data` object. Carry forward prior `data` and mutate only what you acted on this tick.
|
|
47
|
+
- `done` — `true` only if the capability is permanently finished; evergreen capabilities stay `false`.
|
|
48
|
+
|
|
49
|
+
This is the ONLY way your decision is saved. If you don't call it, the tick fails and the state is NOT updated — on the next wake you'll see the same prior state and can retry.
|
|
50
|
+
|
|
51
|
+
> Backstop (legacy): if the `submit_state` tool is unavailable, end your reply with the same JSON in a single fenced block tagged `kody-job-next-state` (or the new `kody-capability-next-state` alias) instead:
|
|
52
|
+
>
|
|
53
|
+
> ````
|
|
54
|
+
> ```kody-job-next-state
|
|
55
|
+
> { "cursor": "<next>", "data": { ... }, "done": <true|false> }
|
|
56
|
+
> ```
|
|
57
|
+
> ````
|
|
58
|
+
|
|
59
|
+
## Rules
|
|
60
|
+
|
|
61
|
+
- Never edit, create, or delete files in the working tree.
|
|
62
|
+
- Never commit or push via `git`. The only permitted commit path is `gh api -X PUT` against the report file (see exception below).
|
|
63
|
+
- Only shell calls allowed: `gh`. Everything must go through it.
|
|
64
|
+
- Keep each tick focused: do one action per candidate per wake. The cron will call you again.
|
|
65
|
+
- If state says you're waiting on something, just check and re-emit — don't spawn a duplicate.
|
|
66
|
+
- Honour the capability body's `## Restrictions` over any inferred shortcut.
|
|
67
|
+
|
|
68
|
+
### Single permitted write: the capability's report file
|
|
69
|
+
|
|
70
|
+
A capability MAY (optionally — only if its body asks for it) write a single
|
|
71
|
+
markdown report file at the canonical path:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
<state.path>/reports/{{capabilitySlug}}.md
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Only that exact path in the configured Kody state repo. Resolve it from `kody.config.json` (`state.repo`, `state.path`) with defaults `<owner>/kody-state` and `<repo>`, then use `gh api -X PUT /repos/<state.repo>/contents/<state.path>/reports/{{capabilitySlug}}.md` (with base64 content + `sha` of the existing file when updating). All other writes — code files, other report paths, other slugs — remain forbidden. The dashboard's `/reports` page surfaces these files automatically; this is the canonical channel for a capability's diagnostic output when an issue comment isn't expressive enough.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{{capabilityReference}}
|
|
2
|
+
|
|
3
|
+
You are **{{agentTitle}}** (agent `{{agentSlug}}`), running capability **`{{capabilitySlug}}`** — *{{capabilityTitle}}* — in **locked-toolbox mode**.
|
|
4
|
+
|
|
5
|
+
You have NO shell. You cannot run `gh`, edit files, or post raw comments. The only actions you can take this tick are the typed tools listed below, plus `submit_state` at the end. The capability body tells you *when* to use each tool; the tools themselves do the work.
|
|
6
|
+
|
|
7
|
+
## Tools available this tick
|
|
8
|
+
|
|
9
|
+
{{capabilityToolsList}}
|
|
10
|
+
- `submit_state` (always — call exactly once at the end)
|
|
11
|
+
|
|
12
|
+
Anything not in that list does not exist for this tick. If the capability body asks for an action whose tool isn't listed, skip it and note the gap in your reasoning.
|
|
13
|
+
|
|
14
|
+
## Who you are — agent identity (authoritative identity)
|
|
15
|
+
|
|
16
|
+
{{agentIdentity}}
|
|
17
|
+
|
|
18
|
+
## The capability
|
|
19
|
+
|
|
20
|
+
Slug **`{{capabilitySlug}}`** — assigned to agent **`{{agentSlug}}`**, running on implementation **`{{executableSlug}}`**. The body is authoritative for *what* and *when*; re-read it every tick.
|
|
21
|
+
|
|
22
|
+
**Operator handle.** Where the capability refers to "the operator," the `recommend_to_operator` tool already prepends this string: `{{mentions}}`. Never type it yourself.
|
|
23
|
+
|
|
24
|
+
### Capability body
|
|
25
|
+
|
|
26
|
+
{{jobIntent}}
|
|
27
|
+
|
|
28
|
+
## Current state
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{{jobStateJson}}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`cursor` is your enum; `data` is your free-form bag (per-PR fingerprints, attempt counters, etc.); `done: true` ends an evergreen capability (don't set it unless the capability truly retires).
|
|
35
|
+
|
|
36
|
+
## Tick procedure
|
|
37
|
+
|
|
38
|
+
`forceRun = {{args.force}}` — when `true`, the operator clicked "Run now"; ignore any "skip if too recent" guard in the body.
|
|
39
|
+
|
|
40
|
+
1. **Check `done`.** If prior state has `done: true`, call `submit_state` with the same state and stop.
|
|
41
|
+
2. **Read the body's intent.** Decide what action(s) this tick needs.
|
|
42
|
+
3. **Use tools** — only the ones in the palette above. Each tool returns structured JSON; read it and decide.
|
|
43
|
+
4. **Persist with `submit_state`** as the LAST action. Carry prior `data` forward; mutate only what you acted on. This is the ONLY way the next tick sees your decisions.
|
|
44
|
+
|
|
45
|
+
## Rules
|
|
46
|
+
|
|
47
|
+
- One action per candidate per tick. The capability fires on its cadence; if there's more to do, the next tick will see the new state.
|
|
48
|
+
- Honour the dedup ledger in `data`. If you already acted on a candidate with the same fingerprint, skip it.
|
|
49
|
+
- Tools handle authorization and rate-limit considerations internally. Don't try to "be careful" by skipping work the body says to do — if a tool errors, surface it in your reasoning and move on.
|
|
50
|
+
- You cannot post raw `@kody` comments. That ban is structural — the toolbox doesn't contain that affordance.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"internal": true,
|
|
3
|
+
"role": "utility",
|
|
4
|
+
"kind": "oneshot",
|
|
5
|
+
"claudeCode": {
|
|
6
|
+
"model": "inherit",
|
|
7
|
+
"permissionMode": "default",
|
|
8
|
+
"maxTurns": null,
|
|
9
|
+
"maxThinkingTokens": null,
|
|
10
|
+
"systemPromptAppend": null,
|
|
11
|
+
"tools": [],
|
|
12
|
+
"hooks": [],
|
|
13
|
+
"skills": [],
|
|
14
|
+
"commands": [],
|
|
15
|
+
"subagents": [],
|
|
16
|
+
"plugins": [],
|
|
17
|
+
"mcpServers": []
|
|
18
|
+
},
|
|
19
|
+
"cliTools": [
|
|
20
|
+
{
|
|
21
|
+
"name": "gh",
|
|
22
|
+
"install": {
|
|
23
|
+
"required": true,
|
|
24
|
+
"checkCommand": "command -v gh"
|
|
25
|
+
},
|
|
26
|
+
"verify": "gh auth status",
|
|
27
|
+
"usage": "Available to the tickScript via PATH; this implementation shells out to `bash <tickScript>`. Scripts use `gh pr list`, `gh pr comment`, etc.",
|
|
28
|
+
"allowedUses": [
|
|
29
|
+
"pr",
|
|
30
|
+
"api",
|
|
31
|
+
"issue"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"inputArtifacts": [],
|
|
36
|
+
"outputArtifacts": [],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"preflight": [
|
|
39
|
+
{
|
|
40
|
+
"script": "runTickScript",
|
|
41
|
+
"with": {
|
|
42
|
+
"jobsDir": ".kody/capabilities",
|
|
43
|
+
"slugArg": "capability",
|
|
44
|
+
"fenceLabel": "kody-job-next-state"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"postflight": [
|
|
49
|
+
{
|
|
50
|
+
"script": "writeJobStateFile",
|
|
51
|
+
"with": {
|
|
52
|
+
"jobsDir": ".kody/capabilities"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"script": "appendCompanyActivity"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"inputs": [
|
|
61
|
+
{
|
|
62
|
+
"name": "capability",
|
|
63
|
+
"flag": "--capability",
|
|
64
|
+
"type": "string",
|
|
65
|
+
"required": true,
|
|
66
|
+
"describe": "Capability slug — folder name under .kody/capabilities/."
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "force",
|
|
70
|
+
"flag": "--force",
|
|
71
|
+
"type": "bool",
|
|
72
|
+
"describe": "Accepted for parity with `capability-tick`. Scripted ticks have no agent-side schedule guard to bypass. Forwarded to the script via env if it cares."
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"name": "capability-tick-scripted"
|
|
76
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"role": "primitive",
|
|
3
|
+
"kind": "oneshot",
|
|
4
|
+
"claudeCode": {
|
|
5
|
+
"model": "inherit",
|
|
6
|
+
"permissionMode": "default",
|
|
7
|
+
"maxTurns": 0,
|
|
8
|
+
"maxThinkingTokens": null,
|
|
9
|
+
"systemPromptAppend": null,
|
|
10
|
+
"cacheable": true,
|
|
11
|
+
"tools": [],
|
|
12
|
+
"hooks": [],
|
|
13
|
+
"skills": [],
|
|
14
|
+
"commands": [],
|
|
15
|
+
"subagents": [],
|
|
16
|
+
"plugins": [],
|
|
17
|
+
"mcpServers": []
|
|
18
|
+
},
|
|
19
|
+
"cliTools": [],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"preflight": [
|
|
22
|
+
{
|
|
23
|
+
"script": "dispatchAgencyLoops"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"script": "skipAgent"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"postflight": []
|
|
30
|
+
},
|
|
31
|
+
"capabilityKind": "act",
|
|
32
|
+
"readsFrom": [
|
|
33
|
+
"agency-model"
|
|
34
|
+
],
|
|
35
|
+
"writesTo": [
|
|
36
|
+
"agency-runs",
|
|
37
|
+
"agency-state"
|
|
38
|
+
],
|
|
39
|
+
"inputs": [
|
|
40
|
+
{
|
|
41
|
+
"name": "loop",
|
|
42
|
+
"flag": "--loop",
|
|
43
|
+
"type": "string",
|
|
44
|
+
"required": false,
|
|
45
|
+
"description": "Optional Loop id to force through the manual Trigger path."
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"outputContract": {
|
|
49
|
+
"actionTypes": [
|
|
50
|
+
"AGENT_NOT_RUN"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
"name": "dispatch-due-loops"
|
|
54
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"internal": true,
|
|
3
|
+
"role": "primitive",
|
|
4
|
+
"kind": "oneshot",
|
|
5
|
+
"claudeCode": {
|
|
6
|
+
"model": "inherit",
|
|
7
|
+
"permissionMode": "default",
|
|
8
|
+
"maxTurns": 0,
|
|
9
|
+
"maxThinkingTokens": null,
|
|
10
|
+
"systemPromptAppend": null,
|
|
11
|
+
"tools": [],
|
|
12
|
+
"hooks": [],
|
|
13
|
+
"skills": [],
|
|
14
|
+
"commands": [],
|
|
15
|
+
"subagents": [],
|
|
16
|
+
"plugins": [],
|
|
17
|
+
"mcpServers": []
|
|
18
|
+
},
|
|
19
|
+
"cliTools": [],
|
|
20
|
+
"inputArtifacts": [],
|
|
21
|
+
"outputArtifacts": [],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"preflight": [
|
|
24
|
+
{
|
|
25
|
+
"script": "loadGoalState"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"script": "advanceManagedGoal",
|
|
29
|
+
"runWhen": {
|
|
30
|
+
"data.goal.state": "active"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"script": "saveManagedGoalState"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"script": "skipAgent"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"postflight": [
|
|
41
|
+
{
|
|
42
|
+
"script": "commitGoalState"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"inputs": [
|
|
47
|
+
{
|
|
48
|
+
"name": "goal",
|
|
49
|
+
"flag": "--goal",
|
|
50
|
+
"type": "string",
|
|
51
|
+
"required": true,
|
|
52
|
+
"describe": "Goal id — directory name under <statePath>/goals/instances/ in the configured state repo."
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"name": "goal-manager"
|
|
56
|
+
}
|
|
@@ -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
|
+
}
|