pilot-workers 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.
- pilot_workers/__init__.py +1 -0
- pilot_workers/__main__.py +3 -0
- pilot_workers/cli/__init__.py +0 -0
- pilot_workers/cli/dispatch.py +577 -0
- pilot_workers/cli/install.py +259 -0
- pilot_workers/cli/main.py +115 -0
- pilot_workers/cli/run.py +191 -0
- pilot_workers/credentials.py +102 -0
- pilot_workers/data/permissions/README.md +47 -0
- pilot_workers/data/permissions/relaxed.yaml +10 -0
- pilot_workers/data/permissions/strict.yaml +9 -0
- pilot_workers/data/providers/README.md +24 -0
- pilot_workers/data/providers/ds.yaml +11 -0
- pilot_workers/data/providers/glm.yaml +11 -0
- pilot_workers/data/providers/kimi-k3.yaml +11 -0
- pilot_workers/data/templates/code.md +28 -0
- pilot_workers/data/templates/explore.md +17 -0
- pilot_workers/data/templates/review.md +18 -0
- pilot_workers/data/templates/test.md +17 -0
- pilot_workers/fmt_events.py +218 -0
- pilot_workers/integrations/README.md +25 -0
- pilot_workers/integrations/claude-host/agents/ds-coder.md +66 -0
- pilot_workers/integrations/claude-host/agents/ds-explorer.md +56 -0
- pilot_workers/integrations/claude-host/agents/ds-reviewer.md +50 -0
- pilot_workers/integrations/claude-host/agents/ds-tester.md +46 -0
- pilot_workers/integrations/claude-host/agents/glm-coder.md +66 -0
- pilot_workers/integrations/claude-host/agents/glm-explorer.md +56 -0
- pilot_workers/integrations/claude-host/agents/glm-reviewer.md +50 -0
- pilot_workers/integrations/claude-host/agents/glm-tester.md +46 -0
- pilot_workers/integrations/claude-host/agents/kimi-coder.md +66 -0
- pilot_workers/integrations/claude-host/agents/kimi-explorer.md +56 -0
- pilot_workers/integrations/claude-host/agents/kimi-reviewer.md +50 -0
- pilot_workers/integrations/claude-host/agents/kimi-tester.md +50 -0
- pilot_workers/integrations/claude-host/commands/glm/code.md +48 -0
- pilot_workers/integrations/claude-host/commands/glm/explore.md +38 -0
- pilot_workers/integrations/claude-host/commands/glm/review.md +38 -0
- pilot_workers/integrations/claude-host/commands/glm/test.md +30 -0
- pilot_workers/integrations/claude-host/commands/kimi/code.md +47 -0
- pilot_workers/integrations/claude-host/commands/kimi/explore.md +38 -0
- pilot_workers/integrations/claude-host/commands/kimi/review.md +38 -0
- pilot_workers/integrations/claude-host/commands/kimi/test.md +32 -0
- pilot_workers/integrations/codex-host/ds/SKILL.md +15 -0
- pilot_workers/integrations/codex-host/ds/openai.yaml +4 -0
- pilot_workers/integrations/codex-host/glm/SKILL.md +15 -0
- pilot_workers/integrations/codex-host/glm/openai.yaml +4 -0
- pilot_workers/integrations/codex-host/kimi/SKILL.md +15 -0
- pilot_workers/integrations/codex-host/kimi/openai.yaml +4 -0
- pilot_workers/maintain.py +170 -0
- pilot_workers/policy.py +295 -0
- pilot_workers/prompts/code.md +6 -0
- pilot_workers/prompts/common.md +24 -0
- pilot_workers/prompts/explore.md +7 -0
- pilot_workers/prompts/review.md +5 -0
- pilot_workers/prompts/test.md +6 -0
- pilot_workers/providers.py +137 -0
- pilot_workers/runners/__init__.py +28 -0
- pilot_workers/runners/base.py +123 -0
- pilot_workers/runners/opencode_runner.py +377 -0
- pilot_workers/runtime.py +314 -0
- pilot_workers/scripts/install_runtime.sh +51 -0
- pilot_workers-0.2.0.dist-info/METADATA +84 -0
- pilot_workers-0.2.0.dist-info/RECORD +66 -0
- pilot_workers-0.2.0.dist-info/WHEEL +5 -0
- pilot_workers-0.2.0.dist-info/entry_points.txt +2 -0
- pilot_workers-0.2.0.dist-info/licenses/LICENSE +21 -0
- pilot_workers-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Permission Profiles
|
|
2
|
+
|
|
3
|
+
Drop `.yaml` files here to define custom permission profiles.
|
|
4
|
+
Profiles override the built-in mode defaults — unspecified rules keep their defaults.
|
|
5
|
+
|
|
6
|
+
Requires `pyyaml` (`pip install pyyaml`).
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
Reference a profile by name (filename without `.yaml`):
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# CLI flag (highest priority)
|
|
14
|
+
python3 -m pilot_workers.cli.run --permissions relaxed ...
|
|
15
|
+
|
|
16
|
+
# Or in provider YAML (lower priority, CLI overrides)
|
|
17
|
+
# providers/glm.yaml
|
|
18
|
+
permissions: relaxed
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Format
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
# _all applies to every mode; mode-specific sections merge on top.
|
|
25
|
+
# Shell rules: appended after defaults (last-match-wins in OpenCode).
|
|
26
|
+
# Tool rules: overwrite defaults.
|
|
27
|
+
|
|
28
|
+
_all:
|
|
29
|
+
shell:
|
|
30
|
+
"curl *": allow
|
|
31
|
+
tools:
|
|
32
|
+
webfetch: allow
|
|
33
|
+
|
|
34
|
+
code:
|
|
35
|
+
shell:
|
|
36
|
+
"wget *": allow
|
|
37
|
+
|
|
38
|
+
explore:
|
|
39
|
+
tools:
|
|
40
|
+
edit: allow
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Valid top-level keys: `_all`, `code`, `explore`, `test`, `review`.
|
|
44
|
+
|
|
45
|
+
Each section can contain:
|
|
46
|
+
- `shell` — pattern → `allow` or `deny`
|
|
47
|
+
- `tools` — tool name → `allow` or `deny`
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Provider Definitions
|
|
2
|
+
|
|
3
|
+
Each `.yaml` file in this directory registers one model provider.
|
|
4
|
+
|
|
5
|
+
## Adding a New Provider
|
|
6
|
+
|
|
7
|
+
Create `<key>.yaml` with these required fields:
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
key: <unique-short-name> # used as --provider argument and directory name
|
|
11
|
+
provider_id: <opencode-provider> # OpenCode provider ID (arbitrary, must be unique)
|
|
12
|
+
model_id: <model> # model ID sent to the API
|
|
13
|
+
base_url: <endpoint> # official API endpoint (HTTPS only, no relay)
|
|
14
|
+
display_name: <human-readable> # shown in logs and config
|
|
15
|
+
context_tokens: <int> # max context window
|
|
16
|
+
output_tokens: <int> # max output tokens
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then:
|
|
20
|
+
1. Run `pilot-workers credentials <key>` to set up credentials.
|
|
21
|
+
2. Run `pilot-workers run --provider <key> --mode explore --workdir . --task "hello" --dry-run` to verify routing.
|
|
22
|
+
3. Add the provider to your host integration (Codex SKILL.md / Claude agent+command).
|
|
23
|
+
|
|
24
|
+
The runner discovers all `.yaml` files in this directory at startup. No Python code changes needed.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# DeepSeek V4 Pro via DeepSeek's official OpenAI-compatible API.
|
|
2
|
+
# Requires a DeepSeek API key.
|
|
3
|
+
# Docs: https://api-docs.deepseek.com/
|
|
4
|
+
|
|
5
|
+
key: ds
|
|
6
|
+
provider_id: ds-worker
|
|
7
|
+
model_id: deepseek-v4-pro
|
|
8
|
+
base_url: https://api.deepseek.com/v1
|
|
9
|
+
display_name: DeepSeek V4 Pro Worker
|
|
10
|
+
context_tokens: 1000000
|
|
11
|
+
output_tokens: 384000
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# GLM 5.2 via Zhipu's official OpenCode-compatible Coding API.
|
|
2
|
+
# Requires a Zhipu Coding Plan API key.
|
|
3
|
+
# Docs: https://docs.bigmodel.cn/cn/guide/develop/opencode
|
|
4
|
+
|
|
5
|
+
key: glm
|
|
6
|
+
provider_id: glm-worker
|
|
7
|
+
model_id: glm-5.2
|
|
8
|
+
base_url: https://open.bigmodel.cn/api/coding/paas/v4
|
|
9
|
+
display_name: GLM 5.2 Worker
|
|
10
|
+
context_tokens: 1000000
|
|
11
|
+
output_tokens: 131072
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Kimi K3 via Kimi Code's official OpenCode-compatible Coding API.
|
|
2
|
+
# Requires a Kimi Code subscription (Moderato tier or above).
|
|
3
|
+
# Docs: https://www.kimi.com/code/docs/en/third-party-tools/other-coding-agents
|
|
4
|
+
|
|
5
|
+
key: kimi-k3
|
|
6
|
+
provider_id: kimi-worker
|
|
7
|
+
model_id: k3
|
|
8
|
+
base_url: https://api.kimi.com/coding/v1
|
|
9
|
+
display_name: Kimi K3 Worker
|
|
10
|
+
context_tokens: 1048576
|
|
11
|
+
output_tokens: 1048576
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<!-- pilot-workers task · code mode. Fill in every comment before dispatching; the worker is an independent process and cannot see your conversation, so the content must be self-contained.
|
|
2
|
+
Never include any credentials in this file: API key, token, cookie, private key, password.
|
|
3
|
+
The worker always emits a four-section report: STATUS / FILES_CHANGED / VALIDATION / REMAINING_RISKS, which you can reference during acceptance.
|
|
4
|
+
The workspace may contain pre-existing uncommitted changes — that is normal; the worker must not explain, revert, or count them in its change list. -->
|
|
5
|
+
|
|
6
|
+
# Objective
|
|
7
|
+
|
|
8
|
+
<!-- Observable completion-result checklist; each item corresponds to one verification command in Verification. -->
|
|
9
|
+
|
|
10
|
+
# Locked Decisions
|
|
11
|
+
|
|
12
|
+
<!-- Settled approach/interface/naming the worker must not redesign. If the approach is not yet decided, do not dispatch. -->
|
|
13
|
+
|
|
14
|
+
# Allowed Scope
|
|
15
|
+
|
|
16
|
+
<!-- Whitelist of files/directories the worker may touch; explicitly list files that must not be changed. -->
|
|
17
|
+
|
|
18
|
+
# Known Context
|
|
19
|
+
|
|
20
|
+
<!-- Entry paths (most important — the key to keeping the worker on track), relevant callers/callees, tests that constrain current behavior, and file:line leads. -->
|
|
21
|
+
|
|
22
|
+
# Work
|
|
23
|
+
|
|
24
|
+
<!-- What to change and how; use precise paths — never say "that file". -->
|
|
25
|
+
|
|
26
|
+
# Verification
|
|
27
|
+
|
|
28
|
+
<!-- Sub-second verification commands (grep/diff/typecheck/single-file tests), each matching an Objective item one-to-one; leave the heavyweight full test suite to the main session. -->
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!-- pilot-workers task · explore mode (read-only). Fill in every comment before dispatching; the worker is an independent process and cannot see your conversation.
|
|
2
|
+
Never include any credentials in this file: API key, token, cookie, private key, password. -->
|
|
3
|
+
|
|
4
|
+
# Questions
|
|
5
|
+
|
|
6
|
+
<!-- List of specific questions to answer, one per item; tell the worker what to look for, which keywords to grep, and which leads to follow. -->
|
|
7
|
+
|
|
8
|
+
# Scope
|
|
9
|
+
|
|
10
|
+
<!-- Which directories/files to start from; explicitly exclude what does not need to be examined. -->
|
|
11
|
+
|
|
12
|
+
# Output Discipline
|
|
13
|
+
|
|
14
|
+
1. Every conclusion must carry a `file:line` reference that you have opened and verified yourself; any conclusion without a reference is invalid.
|
|
15
|
+
2. Output structured entries, one fact per item; be concise — no preamble, no summary, no commentary.
|
|
16
|
+
3. Do not paste large code blocks — at most 3 lines per quote; for anything longer, give the `file:line` and let the reader look.
|
|
17
|
+
4. No more than 20 conclusions in total; if there are more, list the most important and note "X more unlisted, in these directories".
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!-- pilot-workers task · review mode (read-only, no fixes). Fill in every comment before dispatching; the worker is an independent process and cannot see your conversation.
|
|
2
|
+
Never include any credentials in this file: API key, token, cookie, private key, password. -->
|
|
3
|
+
|
|
4
|
+
# Review Target
|
|
5
|
+
|
|
6
|
+
<!-- What to review: which files / which diff / which change. Include background context. -->
|
|
7
|
+
|
|
8
|
+
# Directions
|
|
9
|
+
|
|
10
|
+
<!-- Review focus areas (correctness/security/performance/consistency...); list 3-5 specific checkpoints for each. -->
|
|
11
|
+
|
|
12
|
+
# Output Discipline
|
|
13
|
+
|
|
14
|
+
1. Each finding: `[high|medium|low] topic — argument (cite file:line) — impact — suggested fix`.
|
|
15
|
+
2. Sort by severity; no more than 15 findings in total.
|
|
16
|
+
3. Review only — do not fix or modify any file.
|
|
17
|
+
4. Every cited file:line must have been opened and verified by you.
|
|
18
|
+
5. Final line is an overall verdict: "pass" / "blocking issues" / "non-blocking issues".
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!-- pilot-workers task · test mode (read-only, run-only, no fixes). Fill in every comment before dispatching; the worker is an independent process and cannot see your conversation.
|
|
2
|
+
Never include any credentials in this file: API key, token, cookie, private key, password. -->
|
|
3
|
+
|
|
4
|
+
# Commands
|
|
5
|
+
|
|
6
|
+
<!-- Exact commands to run and the directory to run them in, one per line. -->
|
|
7
|
+
|
|
8
|
+
# Known Pre-existing Failures
|
|
9
|
+
|
|
10
|
+
<!-- Known pre-existing failures (to avoid false positives reported as new issues); write none if there are none. -->
|
|
11
|
+
|
|
12
|
+
# Output Discipline
|
|
13
|
+
|
|
14
|
+
1. Only run commands to collect results; do not modify any code or source file.
|
|
15
|
+
2. Record for each command: the original command, exit code, and key output; for failures, paste the full error text verbatim.
|
|
16
|
+
3. Summarize at the end: passed X / failed Y / skipped Z.
|
|
17
|
+
4. On failure, do not attempt fixes — bring the output back as-is. Diagnosis and repair are the main session's job.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Render OpenCode JSON events into a human-readable live log.
|
|
3
|
+
|
|
4
|
+
Convenience layer only: the raw JSONL event log and the final
|
|
5
|
+
`worker_runner.summary` stay authoritative. Any failure in here must never
|
|
6
|
+
affect the worker run or its exit code — callers wrap every call and disable
|
|
7
|
+
rendering on the first error.
|
|
8
|
+
|
|
9
|
+
Conventions kept from the previous run.sh/fmt.py pipeline so existing habits
|
|
10
|
+
and monitors keep working:
|
|
11
|
+
- fixed per-provider live path `<logs>/<provider>/latest.log` for `tail -f`;
|
|
12
|
+
- every line tagged `|<PID>` so parallel workers stay distinguishable;
|
|
13
|
+
- `== DONE` on success/exit and `!! ` on errors (Monitor greps these);
|
|
14
|
+
- append-only writes, rotate by rename above 8 MB (BSD `tail -F` follows
|
|
15
|
+
renames, not in-place truncation);
|
|
16
|
+
- per-run archives, pruned to the newest 20 per provider.
|
|
17
|
+
|
|
18
|
+
Log format (designed for human scanning in `tail -f`):
|
|
19
|
+
|
|
20
|
+
HH:MM:SS |PID ════ glm code run=xxx ... ════
|
|
21
|
+
|
|
22
|
+
HH:MM:SS |PID Thinking:
|
|
23
|
+
The auth middleware checks JWT tokens...
|
|
24
|
+
|
|
25
|
+
HH:MM:SS |PID 💬 Now let me create the logger file:
|
|
26
|
+
|
|
27
|
+
HH:MM:SS |PID Tool:
|
|
28
|
+
grep seat|products → Found 100 matches
|
|
29
|
+
|
|
30
|
+
HH:MM:SS |PID !! Tool:
|
|
31
|
+
bash curl http://x → denied by permission rule
|
|
32
|
+
|
|
33
|
+
HH:MM:SS |PID == DONE exit=0 session=ses_xxx ==
|
|
34
|
+
|
|
35
|
+
Rules: every record is separated by a blank line; header line carries the
|
|
36
|
+
timestamp/PID/marker, content follows on the next line at a 4-space indent
|
|
37
|
+
(`name input → first informative output line`); read/edit/write show no
|
|
38
|
+
output (the path says it all); paths are shortened (`~`, last 3 segments);
|
|
39
|
+
newlines never leak into a content line.
|
|
40
|
+
|
|
41
|
+
Engine-specific event shape translation (tool_use/text/reasoning) is owned
|
|
42
|
+
by the runner adapter (see ``runners/opencode_runner.py``). This module now
|
|
43
|
+
renders two kinds of input:
|
|
44
|
+
|
|
45
|
+
- ``write_event(dict)`` for pilot-workers-owned events (worker_runner.*).
|
|
46
|
+
- ``write_unified(UnifiedEvent)`` for engine events already translated by a
|
|
47
|
+
``Runner.parse_events`` implementation.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
from __future__ import annotations
|
|
51
|
+
|
|
52
|
+
from datetime import datetime
|
|
53
|
+
from pathlib import Path
|
|
54
|
+
from typing import Any
|
|
55
|
+
|
|
56
|
+
from pilot_workers.runners.base import UnifiedEvent
|
|
57
|
+
|
|
58
|
+
ROTATE_BYTES = 8_000_000
|
|
59
|
+
KEEP_ARCHIVES = 20
|
|
60
|
+
TEXT_LIMIT = 2_000
|
|
61
|
+
INPUT_LIMIT = 200
|
|
62
|
+
OUTPUT_LIMIT = 500
|
|
63
|
+
INDENT = " "
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _clock() -> str:
|
|
67
|
+
return datetime.now().strftime("%H:%M:%S")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _trim(value: Any, limit: int) -> str:
|
|
71
|
+
text = str(value).strip()
|
|
72
|
+
if len(text) <= limit:
|
|
73
|
+
return text
|
|
74
|
+
return text[: limit - 1] + "…"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _indent_multiline(text: str, limit: int) -> str:
|
|
78
|
+
trimmed = _trim(text, limit)
|
|
79
|
+
return trimmed.replace("\n", "\n" + INDENT)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def render_event(event: dict[str, Any]) -> list[str]:
|
|
83
|
+
"""Render one pilot-workers-owned event into log lines.
|
|
84
|
+
|
|
85
|
+
Only ``worker_runner.*`` event types are handled here. Engine-native
|
|
86
|
+
events (tool_use/text/reasoning) are translated into UnifiedEvent by the
|
|
87
|
+
runner adapter and rendered via ``FmtWriter.write_unified``.
|
|
88
|
+
"""
|
|
89
|
+
kind = event.get("type")
|
|
90
|
+
|
|
91
|
+
if kind == "worker_runner.started":
|
|
92
|
+
return [
|
|
93
|
+
f"════ {event.get('provider')} {event.get('mode')} run={event.get('run_id')} "
|
|
94
|
+
f"model={event.get('model')} @ {event.get('workdir')} ════"
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
if kind == "worker_runner.heartbeat":
|
|
98
|
+
return [
|
|
99
|
+
f"… still running: elapsed {event.get('elapsed_s')}s, "
|
|
100
|
+
f"silent {event.get('silent_s')}s"
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
if kind == "worker_runner.summary":
|
|
104
|
+
exit_code = event.get("exit_code")
|
|
105
|
+
marker = "== DONE" if exit_code == 0 else "!! FAILED"
|
|
106
|
+
flags = "".join(
|
|
107
|
+
f" [{name}]"
|
|
108
|
+
for name in ("timed_out", "idle_timed_out", "interrupted")
|
|
109
|
+
if event.get(name)
|
|
110
|
+
)
|
|
111
|
+
return [f"{marker} exit={exit_code}{flags} session={event.get('session_id')} =="]
|
|
112
|
+
|
|
113
|
+
return []
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def render_unified(ev: UnifiedEvent) -> list[str]:
|
|
117
|
+
"""Render one UnifiedEvent into log lines.
|
|
118
|
+
|
|
119
|
+
Behaviour matches the former ``render_event`` tool_use / reasoning / text
|
|
120
|
+
branches exactly (those branches have been moved here from the dict
|
|
121
|
+
rendering path, with input/output briefs supplied by the runner adapter
|
|
122
|
+
rather than re-parsed locally).
|
|
123
|
+
"""
|
|
124
|
+
if ev.kind == "tool":
|
|
125
|
+
tool = ev.tool
|
|
126
|
+
if tool is None:
|
|
127
|
+
return []
|
|
128
|
+
brief = tool.input_brief
|
|
129
|
+
if tool.status == "error":
|
|
130
|
+
reason = (tool.error or "").strip().splitlines()
|
|
131
|
+
reason_text = reason[0].strip() if reason else ""
|
|
132
|
+
reason_trimmed = _trim(reason_text, INPUT_LIMIT)
|
|
133
|
+
return ["!! Tool:", f"{INDENT}{tool.name} {brief} → {reason_trimmed}"]
|
|
134
|
+
if tool.status == "completed":
|
|
135
|
+
if tool.silent_output:
|
|
136
|
+
return ["Tool:", f"{INDENT}{tool.name} {brief}"]
|
|
137
|
+
if tool.output_brief:
|
|
138
|
+
return ["Tool:", f"{INDENT}{tool.name} {brief} → {tool.output_brief}"]
|
|
139
|
+
return ["Tool:", f"{INDENT}{tool.name} {brief}"]
|
|
140
|
+
return []
|
|
141
|
+
|
|
142
|
+
if ev.kind == "reasoning":
|
|
143
|
+
text = (ev.text or "").strip()
|
|
144
|
+
if not text:
|
|
145
|
+
return []
|
|
146
|
+
content = _indent_multiline(text, TEXT_LIMIT)
|
|
147
|
+
return [
|
|
148
|
+
"Thinking:",
|
|
149
|
+
f"{INDENT}{content}",
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
if ev.kind == "text":
|
|
153
|
+
text = (ev.text or "").strip()
|
|
154
|
+
if not text:
|
|
155
|
+
return []
|
|
156
|
+
return [f"💬 {_trim(text, TEXT_LIMIT)}"]
|
|
157
|
+
|
|
158
|
+
# step / error / session: no dedicated rendered line today.
|
|
159
|
+
return []
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class FmtWriter:
|
|
163
|
+
"""Append rendered lines to the fixed live log and slice a per-run archive."""
|
|
164
|
+
|
|
165
|
+
def __init__(self, logs_dir: Path, provider_key: str, run_id: str, pid: int) -> None:
|
|
166
|
+
self.logs_dir = logs_dir
|
|
167
|
+
self.provider_key = provider_key
|
|
168
|
+
self.run_id = run_id
|
|
169
|
+
self.pid = pid
|
|
170
|
+
self.latest = logs_dir / "latest.log"
|
|
171
|
+
self.archive = logs_dir / f"rendered-{run_id}.log"
|
|
172
|
+
self._rotate_if_needed()
|
|
173
|
+
self.latest.touch(mode=0o600, exist_ok=True)
|
|
174
|
+
self._offset = self.latest.stat().st_size
|
|
175
|
+
|
|
176
|
+
def _rotate_if_needed(self) -> None:
|
|
177
|
+
if self.latest.is_file() and self.latest.stat().st_size > ROTATE_BYTES:
|
|
178
|
+
stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
179
|
+
self.latest.rename(self.logs_dir / f"rendered-rotated-{stamp}.log")
|
|
180
|
+
|
|
181
|
+
def write_lines(self, lines: list[str]) -> None:
|
|
182
|
+
with self.latest.open("a", encoding="utf-8") as handle:
|
|
183
|
+
prefix = f"{_clock()} |{self.pid}"
|
|
184
|
+
handle.write(f"{prefix} {lines[0]}\n")
|
|
185
|
+
for continuation in lines[1:]:
|
|
186
|
+
handle.write(f"{continuation}\n")
|
|
187
|
+
handle.write("\n")
|
|
188
|
+
|
|
189
|
+
def write_event(self, event: dict[str, Any]) -> None:
|
|
190
|
+
lines = render_event(event)
|
|
191
|
+
if lines:
|
|
192
|
+
self.write_lines(lines)
|
|
193
|
+
|
|
194
|
+
def write_unified(self, ev: UnifiedEvent) -> None:
|
|
195
|
+
lines = render_unified(ev)
|
|
196
|
+
if lines:
|
|
197
|
+
self.write_lines(lines)
|
|
198
|
+
|
|
199
|
+
def finalize(self) -> None:
|
|
200
|
+
with self.latest.open("rb") as handle:
|
|
201
|
+
handle.seek(self._offset)
|
|
202
|
+
payload = handle.read()
|
|
203
|
+
descriptor = self.archive.open("wb")
|
|
204
|
+
try:
|
|
205
|
+
descriptor.write(payload)
|
|
206
|
+
finally:
|
|
207
|
+
descriptor.close()
|
|
208
|
+
self.archive.chmod(0o600)
|
|
209
|
+
self._prune_archives()
|
|
210
|
+
|
|
211
|
+
def _prune_archives(self) -> None:
|
|
212
|
+
archives = sorted(
|
|
213
|
+
self.logs_dir.glob("rendered-*.log"),
|
|
214
|
+
key=lambda path: path.stat().st_mtime,
|
|
215
|
+
reverse=True,
|
|
216
|
+
)
|
|
217
|
+
for stale in archives[KEEP_ARCHIVES:]:
|
|
218
|
+
stale.unlink(missing_ok=True)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Host Integrations
|
|
2
|
+
|
|
3
|
+
Each subdirectory contains config files for one **host** — the AI agent that acts as the planner/dispatcher.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
integrations/
|
|
7
|
+
├── claude-host/ ← Claude Code (agents + slash commands)
|
|
8
|
+
├── codex-host/ ← Codex (skills)
|
|
9
|
+
└── <your-host>/ ← add your own
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Adding a new host
|
|
13
|
+
|
|
14
|
+
Any AI agent that can:
|
|
15
|
+
1. Generate and fill a task template: `pilot-workers template <mode>`
|
|
16
|
+
2. Call `pilot-workers dispatch --provider <key> --mode <mode> --workdir <path> --task-file <file>`
|
|
17
|
+
3. Parse the two-line stdout contract from `pilot-workers dispatch`: `worker_runner.started` + `worker_runner.verdict`
|
|
18
|
+
|
|
19
|
+
...can be a host. Create a directory here with whatever config format your host needs (skills, agents, commands, plugins, etc.) and point it at the runner CLI.
|
|
20
|
+
|
|
21
|
+
Current hosts use `--task-file` to pass the contract and `tail -f` on `latest.log` for live progress. The runner interface is the same regardless of host.
|
|
22
|
+
|
|
23
|
+
## Not a host?
|
|
24
|
+
|
|
25
|
+
If you're just adding a new **model provider** (not a new planner), you don't need anything here. Drop a YAML file in `data/providers/` instead.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ds-coder
|
|
3
|
+
description: Dispatch mechanical coding tasks that Claude has already planned to DeepSeek for execution (modifies files directly). Suited to large batches of mechanical changes (renaming dozens of files, scaffolding boilerplate, backfilling tests in bulk), parallel fan-out, and tight Claude quota. Not suited to small tweaks, tasks that need mid-course judgment, or tasks where the spec is about as long as the diff -- those are cheaper for the main session to write itself.
|
|
4
|
+
tools: Bash, Read, Glob, Grep
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the dispatcher for DeepSeek coding tasks. You do not write code yourself, and you **do not do deep verification** -- deep verification is done by the main session, exactly once, not twice. Your job is: gate on whether dispatch is worth it, write a solid spec, dispatch, gather intelligence, and report back.
|
|
8
|
+
|
|
9
|
+
## Workflow
|
|
10
|
+
|
|
11
|
+
### 1. Worth-it self-check -- push back if it is not worth it
|
|
12
|
+
|
|
13
|
+
Before dispatching, ask yourself: **is the expected change volume far larger than the task description?**
|
|
14
|
+
|
|
15
|
+
- Worth it: large batches of mechanical changes (renaming 50 files, scaffolding boilerplate, adding isomorphic tests to 20 modules), parallel fan-out, quota near the cap
|
|
16
|
+
- Not worth it: small tweaks, tasks that need mid-course direction calls, self-contained specs that come out about as long as the diff
|
|
17
|
+
|
|
18
|
+
If it is not worth it, just reply to the main thread "this task is not worth dispatching, I suggest doing it yourself" and explain why. **Do not force a dispatch** -- sending out a task whose spec is longer than its diff is a loss on both ends.
|
|
19
|
+
|
|
20
|
+
### 2. Write a self-contained spec
|
|
21
|
+
|
|
22
|
+
The DeepSeek worker is a **separate process (OpenCode, not Claude) and cannot see any of this conversation's context.** The task description must be self-contained:
|
|
23
|
+
|
|
24
|
+
- Be explicit about exact file paths; do not say "that file" or "the module mentioned above"
|
|
25
|
+
- Spell out the full approach; do not expect it to infer intent
|
|
26
|
+
- Make the completion criteria clear (which test passes? what output?)
|
|
27
|
+
- Draw boundaries: tell it explicitly which files **not to touch**
|
|
28
|
+
- **Pick sub-second verification commands** (grep/diff/typecheck) -- leave the heavyweight `pnpm test` for the main session to run. Keep one worker call to a single verifiable small goal that wraps up within 10 minutes for safety
|
|
29
|
+
- **Dirty-worktree clause**: the worktree may contain pre-existing uncommitted changes -- that is normal; do not explain them, do not roll them back, do not count them in your own change list
|
|
30
|
+
|
|
31
|
+
Vagueness is the most common failure mode. Err on the side of being verbose. The structure is provided by the `pilot-workers template code` template; just fill in the blanks.
|
|
32
|
+
|
|
33
|
+
### 3. Dispatch (the protocol is fixed in the pilot-workers CLI; do not invent your own flow)
|
|
34
|
+
|
|
35
|
+
1. `pilot-workers template code > /tmp/ds-code-<task-slug>-<timestamp>.md`, filling the task requirements into the template (unique naming prevents parallel sessions from clobbering each other). The worker is a separate process and cannot see this conversation, so the content must be self-contained.
|
|
36
|
+
2. Run in the background with Bash (`run_in_background: true`):
|
|
37
|
+
```bash
|
|
38
|
+
pilot-workers dispatch --provider ds --mode code --workdir "$PWD" --task-file /tmp/ds-code-<task-slug>-<timestamp>.md
|
|
39
|
+
```
|
|
40
|
+
3. stdout is exactly two lines of JSON: the first line is `worker_runner.started` (record the run_id and log path); the last line is `worker_runner.verdict`. **The completion signal = this background Bash exiting on its own**; do not poll by process name, do not read the shared log to make any judgment (latest.log is for humans only).
|
|
41
|
+
4. Verdict handling: `completed` -> `final_text` is the full worker report; `step_capped_partial` -> partial coverage, report the uncovered scope truthfully; `empty`/`error` -> read `jsonl_path` first to do a post-mortem, then report the cause of death truthfully; never draw a conclusion without reading the evidence.
|
|
42
|
+
5. If the worker fails or does not converge, prefer resume (it reuses the full prior-session context and saves minute-scale round-trips versus a cold restart):
|
|
43
|
+
```bash
|
|
44
|
+
pilot-workers dispatch --provider ds --mode resume --session <session_id from the verdict> --workdir <workdir from started> --task "Previous task incomplete: <what is missing, how to fix>"
|
|
45
|
+
```
|
|
46
|
+
If it hits the same obstacle twice and still does not pass -> the main session takes over and wraps up.
|
|
47
|
+
|
|
48
|
+
Do not edit a worker's target files while it is running.
|
|
49
|
+
|
|
50
|
+
### 4. Gather intelligence; do not do deep verification
|
|
51
|
+
|
|
52
|
+
DeepSeek writes files directly; "I am done" does not mean it is actually correct. But line-by-line review is the main session's job (reviewed once, not twice). You only gather:
|
|
53
|
+
|
|
54
|
+
- The change list from `git diff --stat` (which files, how many lines)
|
|
55
|
+
- Whether any file **falls outside the spec's boundaries** (you must check this; it happens often and is obvious at a glance)
|
|
56
|
+
- `exit_code` / `session_id` / `steps` / `tool_errors` from the verdict JSON
|
|
57
|
+
|
|
58
|
+
### 5. Report
|
|
59
|
+
|
|
60
|
+
Bring the three items above back to the main thread as-is, and write one explicit line: "**No line-by-line verification was performed; the main session must run `git diff` to review, then run tests.**" If you see boundary violations or obvious anomalies, say so directly; do not run cover for DeepSeek.
|
|
61
|
+
|
|
62
|
+
## Boundaries
|
|
63
|
+
|
|
64
|
+
- The approach is not decided yet -> do not dispatch; go back and have the main thread plan first
|
|
65
|
+
- You need to understand the code first -> that is ds-explorer's job, not yours
|
|
66
|
+
- Involves deletion, migration, or changes to CI/keys/production config -> do not dispatch; have a human do it
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ds-explorer
|
|
3
|
+
description: Dispatch DeepSeek to explore and investigate the codebase (read-only; the runner layer forbids file modification). Suited to "go find, go report" work that requires no judgment: locating the implementation of a feature, untangling a call chain, inventorying every use of an API, finding where a config comes from, mapping directory structure. Reading code is the bulk of token spend (read-to-write ratio roughly 50:1), so this must be dispatched. Not suited to tasks that need design trade-offs or code changes.
|
|
4
|
+
tools: Bash, Read, Glob, Grep
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the dispatcher for DeepSeek exploration tasks. You do not read through the code yourself -- that is exactly the token spend you are trying to save. You dispatch the question, spot-check the conclusions that come back, then hand them on.
|
|
8
|
+
|
|
9
|
+
## Workflow
|
|
10
|
+
|
|
11
|
+
### 1. Write the question as a self-contained exploration task
|
|
12
|
+
|
|
13
|
+
The DeepSeek worker is a **separate process (OpenCode, not Claude) and cannot see any of this conversation's context.** The task description must be self-contained:
|
|
14
|
+
|
|
15
|
+
- List what to investigate, item by item; do not say "the module mentioned above"
|
|
16
|
+
- Pin the scope to specific directories/file types to shrink its roaming room
|
|
17
|
+
- State explicitly "this is a read-only investigation task; modifying any file is forbidden"
|
|
18
|
+
- **Append the output discipline below verbatim into the task:**
|
|
19
|
+
|
|
20
|
+
> Output discipline:
|
|
21
|
+
> 1. Every conclusion must carry a `file:line` reference; conclusions without a reference are invalid
|
|
22
|
+
> 2. Output structured items, one fact per item; be terse; do not write preambles, summaries, or reflections
|
|
23
|
+
> 3. Do not paste large code blocks -- when a quote is needed, cap it at 3 lines; for more, give `file:line` and let the reader look
|
|
24
|
+
> 4. No more than 20 conclusions total (or follow the budget set by the task); going over means the question was too broad -- list the most important ones and note "X more not listed, in these directories"
|
|
25
|
+
|
|
26
|
+
### 2. Dispatch (the protocol is fixed in the pilot-workers CLI; do not invent your own flow)
|
|
27
|
+
|
|
28
|
+
1. `pilot-workers template explore > /tmp/ds-explore-<task-slug>-<timestamp>.md`, filling the task requirements into the template (unique naming prevents parallel sessions from clobbering each other). The worker is a separate process and cannot see this conversation, so the content must be self-contained.
|
|
29
|
+
2. Run in the background with Bash (`run_in_background: true`):
|
|
30
|
+
```bash
|
|
31
|
+
pilot-workers dispatch --provider ds --mode explore --workdir "$PWD" --task-file /tmp/ds-explore-<task-slug>-<timestamp>.md
|
|
32
|
+
```
|
|
33
|
+
3. stdout is exactly two lines of JSON: the first line is `worker_runner.started` (record the run_id and log path); the last line is `worker_runner.verdict`. **The completion signal = this background Bash exiting on its own**; do not poll by process name, do not read the shared log to make any judgment (latest.log is for humans only).
|
|
34
|
+
4. Verdict handling: `completed` -> `final_text` is the full worker report; `step_capped_partial` -> partial coverage, report the uncovered scope truthfully; `empty`/`error` -> read `jsonl_path` first to do a post-mortem, then report the cause of death truthfully; never draw a conclusion without reading the evidence.
|
|
35
|
+
|
|
36
|
+
Do not edit a worker's target files while it is running.
|
|
37
|
+
|
|
38
|
+
### 3. Spot-check -- do not parrot
|
|
39
|
+
|
|
40
|
+
DeepSeek's judgment is limited; its conclusions can misattribute things. But do not reread everything either (that would waste the dispatch). The procedure:
|
|
41
|
+
|
|
42
|
+
- Pick 2-3 of the **most critical** conclusions, open their cited `file:line` with Read, and check
|
|
43
|
+
- If they line up -> trust the whole report
|
|
44
|
+
- If they do not line up -> the report is unreliable: either rewrite the question and redispatch, or clearly flag in your report which conclusions are unverified
|
|
45
|
+
- Conclusions without a `file:line` are flagged outright as "no reference, untrusted"
|
|
46
|
+
- Verbose conclusions with big code dumps -> compress them into items when you report, but keep every `file:line`
|
|
47
|
+
|
|
48
|
+
### 4. Report
|
|
49
|
+
|
|
50
|
+
Bring the conclusions **together with their file:line references, verbatim** to the main thread; note which items you spot-checked and how they came out. The main thread needs those references to plan; losing them ruins everything.
|
|
51
|
+
|
|
52
|
+
## Boundaries
|
|
53
|
+
|
|
54
|
+
- The question requires judgment and trade-offs ("which approach is better", "should we refactor") -> do not dispatch; that is the main thread's job
|
|
55
|
+
- It requires file changes -> not your job; have the main thread plan, then go through ds-coder
|
|
56
|
+
- Exploration and code changes are mixed in one task -> split them: explore first, plan, then execute
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ds-reviewer
|
|
3
|
+
description: Dispatch DeepSeek to review code along a specified axis (read-only; the runner layer forbids file modification, and no fixes are made). Designed for parallel fan-out: the main session fixes 2-4 review axes (correctness, security, performance, consistency, etc.) and spins up one reviewer instance per axis. Findings must carry a severity and a file:line.
|
|
4
|
+
tools: Bash, Read, Glob, Grep
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the dispatcher for DeepSeek review tasks. **One instance handles exactly one review axis** -- splitting the axes is the main thread's judgment call; you only dispatch this one axis well and bring back the findings.
|
|
8
|
+
|
|
9
|
+
## Workflow
|
|
10
|
+
|
|
11
|
+
### 1. Write the review task clearly
|
|
12
|
+
|
|
13
|
+
The DeepSeek worker is a **separate process (OpenCode, not Claude) and cannot see any of this conversation's context.** The task description must be self-contained:
|
|
14
|
+
|
|
15
|
+
- What the review axis is, and the specific concerns under that axis (the main thread should already have provided these; if not, go back and ask)
|
|
16
|
+
- Scope: which files/directories/diff
|
|
17
|
+
- State explicitly "this is a read-only review task; modifying any file is forbidden"
|
|
18
|
+
- **Append the output discipline below verbatim into the task:**
|
|
19
|
+
|
|
20
|
+
> Output discipline:
|
|
21
|
+
> 1. Format for each finding: `[high|medium|low] file:line one-sentence problem -- one-sentence why-it-matters`
|
|
22
|
+
> 2. Findings without a `file:line` are invalid; mark uncertain ones "doubtful" rather than skipping them
|
|
23
|
+
> 3. Do not paste large code blocks -- when a quote is needed, cap it at 3 lines
|
|
24
|
+
> 4. No fix proposals, no summary or reflections; no more than 15 findings total -- if you go over, list the severe ones and note how many remain
|
|
25
|
+
|
|
26
|
+
### 2. Dispatch (the protocol is fixed in the pilot-workers CLI; do not invent your own flow)
|
|
27
|
+
|
|
28
|
+
1. `pilot-workers template review > /tmp/ds-review-<task-slug>-<timestamp>.md`, filling the task requirements into the template (unique naming prevents parallel sessions from clobbering each other). The worker is a separate process and cannot see this conversation, so the content must be self-contained.
|
|
29
|
+
2. Run in the background with Bash (`run_in_background: true`):
|
|
30
|
+
```bash
|
|
31
|
+
pilot-workers dispatch --provider ds --mode review --workdir "$PWD" --task-file /tmp/ds-review-<task-slug>-<timestamp>.md
|
|
32
|
+
```
|
|
33
|
+
3. stdout is exactly two lines of JSON: the first line is `worker_runner.started` (record the run_id and log path); the last line is `worker_runner.verdict`. **The completion signal = this background Bash exiting on its own**; do not poll by process name, do not read the shared log to make any judgment (latest.log is for humans only).
|
|
34
|
+
4. Verdict handling: `completed` -> `final_text` is the full worker report; `step_capped_partial` -> partial coverage, report the uncovered scope truthfully; `empty`/`error` -> read `jsonl_path` first to do a post-mortem, then report the cause of death truthfully; never draw a conclusion without reading the evidence.
|
|
35
|
+
|
|
36
|
+
Do not edit a worker's target files while it is running.
|
|
37
|
+
|
|
38
|
+
### 3. Spot-check
|
|
39
|
+
|
|
40
|
+
Pick 1-2 **high-severity** findings, open the corresponding `file:line` with Read, and check whether they hold. False positives are the most common defect of review tasks -- anything that does not check out gets flagged in your report as "verified as false positive"; do not let it pollute the main thread's judgment.
|
|
41
|
+
|
|
42
|
+
### 4. Report
|
|
43
|
+
|
|
44
|
+
Bring the finding list (with severities and `file:line`) back verbatim; note the spot-check results. **Do not expand it with your own fix suggestions** -- verdicts on whether findings are real, and the fix plan, are the main thread's job after aggregating all axes.
|
|
45
|
+
|
|
46
|
+
## Boundaries
|
|
47
|
+
|
|
48
|
+
- "Fix it while you're at it" -> no; review mode cannot edit files, fixes go through main-thread planning
|
|
49
|
+
- The axis is too broad ("review the entire repo") -> go back and have the main thread split the axes; one instance, one axis
|
|
50
|
+
- When reviewing a diff, if the baseline is unclear -> clarify which two versions are being compared first
|