Compare commits
5 Commits
ca2b47eb5f
...
5ea931f24d
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ea931f24d | |||
| a505c138e0 | |||
| bcc5fd44e7 | |||
| 1ae44eb705 | |||
| b17b1f1587 |
@@ -116,6 +116,9 @@ def cmd_smoke(cfg, a):
|
||||
body = {"name": "agentd smoke %s %s" % (a.terminal, time.strftime("%Y-%m-%d %H:%M")),
|
||||
"description_html": SMOKE_BRIEF, "state": pl.states(p["id"])[p["states"]["todo"]],
|
||||
"assignees": [pl.members()[user]]}
|
||||
if a.labels:
|
||||
labels = pl.labels(p["id"])
|
||||
body["labels"] = [labels[x] for x in a.labels.split(",")]
|
||||
issue = pl.create_issue(p["id"], body)
|
||||
key = "%s-%s" % (p.get("key_prefix", a.project), issue["sequence_id"])
|
||||
print("created %s; ticking every %ss (timeout %s min)" % (key, a.interval, a.timeout))
|
||||
@@ -195,6 +198,7 @@ def parser():
|
||||
s.add_argument("terminal")
|
||||
s.add_argument("--interval", type=int, default=30)
|
||||
s.add_argument("--timeout", type=int, default=30, help="minutes")
|
||||
s.add_argument("--labels", default="", help="comma-separated Plane labels, e.g. model:sol")
|
||||
s.add_argument("--as", dest="as_")
|
||||
s.set_defaults(fn=cmd_smoke)
|
||||
sub.add_parser("check-config", help="validate the config and list token env vars").set_defaults(fn=cmd_check_config)
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
# plane-agentd live configuration on oregon (phase 1). No secrets: token_key values are Vault key names; the tokens
|
||||
# are injected by `agent-access.py exec --secret-env KEY=KEY` (see cron/agentd-tick.sh and docs/runbook.md).
|
||||
plane: {base_url: https://plane.kappiee.com, workspace: kappiee}
|
||||
vault: {client: ~/.local/share/server-access-client/agent-access.py, config: https://nas.tail9531b.ts.net:8443/identity/agent.json}
|
||||
|
||||
agentd:
|
||||
state_dir: ~/.gwent-board/agentd # JSON state, agentd.log, heartbeat.json, local run dirs (runs/)
|
||||
helpers_dir: /home/ubuntu/Documents/wjj/project/gwent-design/tools # win10-ps.sh / win10-put.sh
|
||||
interval: 120
|
||||
max_rounds_per_thread: 8
|
||||
comment_max_chars: 6000
|
||||
cli_terminal: oregon
|
||||
|
||||
terminals:
|
||||
win10:
|
||||
transport: warpgate-ssh
|
||||
target: win10
|
||||
os: windows
|
||||
launch: schtasks
|
||||
repo: 'C:\Users\Administrator\Documents\GwentAssistantProject\gwent'
|
||||
run_root: 'C:\Users\Administrator\Documents\GwentAssistantProject\gwent\runtime\agentd'
|
||||
bins: {codex: 'C:\Users\Administrator\AppData\Local\Programs\OpenAI\Codex\bin\codex.exe'}
|
||||
plane_user: window10
|
||||
token_key: PLANE_WINDOW10_API_TOKEN
|
||||
concurrency: 1
|
||||
busy_processes: [codex] # do not start while a codex.exe agentd did not start is running
|
||||
capabilities: [android, game]
|
||||
executors: [codex]
|
||||
oregon:
|
||||
transport: local
|
||||
os: linux
|
||||
launch: subprocess
|
||||
repo: /home/ubuntu/Documents/wjj/project/gwent
|
||||
plane_user: oregon
|
||||
token_key: PLANE_OREGON_API_TOKEN
|
||||
concurrency: 1 # one shared worktree: no parallel edits
|
||||
bins: {claude: /home/ubuntu/.local/bin/claude} # cron has a minimal PATH; codex is not installed on oregon
|
||||
executors: [claude-code]
|
||||
|
||||
executors:
|
||||
codex:
|
||||
adapter: codex
|
||||
run: "codex exec --json -C '{repo}' -s workspace-write -m {model} -c 'model_reasoning_effort=\"{effort}\"' {tier} -o '{out}' -"
|
||||
# `exec resume` takes no -C/-s: the runner already cd's into the repo, the sandbox goes in as a -c override
|
||||
resume: "codex exec resume {thread} --json -m {model} -c 'model_reasoning_effort=\"{effort}\"' -c 'sandbox_mode=\"workspace-write\"' {tier} -o '{out}' -"
|
||||
tier_arg: "-c 'service_tier=\"{tier}\"'"
|
||||
thread_from: events.jsonl:thread.started.thread_id
|
||||
last_message: '{out}'
|
||||
models: [gpt-6.1-sol, gpt-6-astra]
|
||||
default_effort: medium
|
||||
claude-code:
|
||||
adapter: claude-code
|
||||
run: "claude -p --model {model} --output-format json --permission-mode {perm}"
|
||||
resume: "claude -p --resume {thread} --model {model} --output-format json --permission-mode {perm}"
|
||||
permission_mode: acceptEdits
|
||||
thread_from: json:session_id
|
||||
last_message: json:result
|
||||
models: [sonnet, opus, haiku]
|
||||
|
||||
projects:
|
||||
GWENT:
|
||||
id: 79cb5a60-aa4f-4138-9864-1dd70426e665
|
||||
key_prefix: GW
|
||||
states: {backlog: Backlog, todo: Todo, running: In Progress, waiting: Waiting, review: Review, done: Done}
|
||||
reviewer: oregon
|
||||
answerer: oregon
|
||||
protocol_file: /home/ubuntu/Documents/wjj/project/gwent/design/board-protocol.md
|
||||
routing:
|
||||
by_assignee: {window10: win10, oregon: oregon}
|
||||
by_label:
|
||||
'model:astra': {model: gpt-6-astra, effort: high}
|
||||
'model:sol': {model: gpt-6.1-sol, tier: fast}
|
||||
'effort:high': {effort: high}
|
||||
'needs-game': {serialize_group: game}
|
||||
default_executor: {win10: codex, oregon: claude-code}
|
||||
retry:
|
||||
transient_max: 3
|
||||
backoff_minutes: [2, 5, 10]
|
||||
timeout_hours: 4
|
||||
max_rounds_per_thread: 8
|
||||
max_progress_rounds: 12
|
||||
# notify (ntfy) is phase 3: left out on purpose
|
||||
+1
-1
@@ -53,7 +53,7 @@
|
||||
"repo": {"type": "string"},
|
||||
"plane_user": {"type": "string"},
|
||||
"token_key": {"type": "string"},
|
||||
"concurrency": {"type": "integer", "minimum": 1},
|
||||
"concurrency": {"type": "integer", "minimum": 0},
|
||||
"capabilities": {"$ref": "#/definitions/strings"},
|
||||
"executors": {"type": "array", "items": {"type": "string"}, "minItems": 1},
|
||||
"bins": {"type": "object", "additionalProperties": {"type": "string"}},
|
||||
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# cron entry (*/2): one agentd tick under server-access exec (Plane tokens in the process env only), with a lock.
|
||||
# The outer flock skips a tick while the previous one still runs; agentd also holds its own state lock.
|
||||
exec flock -n /home/ubuntu/.gwent-board/agentd.lock \
|
||||
timeout 900 python3 -I /home/ubuntu/.local/share/server-access-client/agent-access.py \
|
||||
--config https://nas.tail9531b.ts.net:8443/identity/agent.json exec \
|
||||
--secret-env PLANE_OREGON_API_TOKEN=PLANE_OREGON_API_TOKEN \
|
||||
--secret-env PLANE_WINDOW10_API_TOKEN=PLANE_WINDOW10_API_TOKEN --timeout 850 -- \
|
||||
python3 /home/ubuntu/Documents/wjj/project/plane-agentd/cli/agentd tick --once >> /home/ubuntu/.gwent-board/agentd-cron.log 2>&1
|
||||
+2
-1
@@ -94,4 +94,5 @@ docs/ design.md executor-contract.md runbook.md
|
||||
## 9. 部署位置
|
||||
- Plane VM(plane-01)只跑 Plane:它是设备化节点(固定版本、每日备份停写路径),不放 agentd。
|
||||
- 阶段 1–2:agentd 跑在 oregon(已有 Vault 客户端、Warpgate 中继脚本、Claude Code、现有 worker 状态),cron 拉起;配置页面同进程,只监听 tailnet 地址。
|
||||
- 阶段 3 起:迁到 PVE 上的专用小节点 `agentd-01`(1–2 vCPU / 2 GB,tailnet + 内网 10.77.10.0/24,与 Plane/Temporal 同网段,webhook 走内网),装 Vault 客户端、中继脚本、Claude Code 与 Codex CLI,使其同时也是一个执行终端(跑核对/验收类任务)。对外访问经 Warpgate HTTP target,复用既有登录。
|
||||
- 阶段 3 起:迁到 PVE 上的精简 LXC `agentd-01`(1 vCPU / 1–2 GB,tailnet + 内网 10.77.10.0/24,与 Plane/Temporal 同网段,webhook 走内网)。**LXC 上不装任何 agent CLI**,只有 Python 3.10+、PyYAML、Agent Vault 客户端、ssh 客户端;所有终端(win10、oregon、mac…)一律远程启动(`transport: warpgate-ssh`),LXC 只负责调度、回帖、通知。对外访问经 Warpgate HTTP target,复用既有登录。
|
||||
- 远程启动的通用实现(P1-6):Windows 用计划任务(已实现);Linux/macOS 经 Warpgate SSH 上传 `run.sh`,`setsid nohup` 脱离会话,轮询 `exit` 文件取回 `last.md`/线程 ID;中继目标名由终端配置给出,不写死。
|
||||
|
||||
@@ -40,6 +40,7 @@ cli/agentd comment GWENT 12 reply.md # 或 - 读 stdin
|
||||
cli/agentd state GWENT 12 Todo # Plane 状态名或配置键(todo/review…)
|
||||
cli/agentd assign GWENT 12 window10
|
||||
cli/agentd smoke GWENT oregon # 建一个要求回复 "[review] ok" 的工作项,tick 到结束,PASS 则移到 Done
|
||||
cli/agentd smoke GWENT win10 --labels model:sol # --labels 给冒烟项加标签(路由到模型/档位)
|
||||
```
|
||||
CLI 以 `--as <terminal>` / `agentd.cli_terminal` / 第一个有 token 的终端的身份操作。
|
||||
|
||||
|
||||
@@ -45,6 +45,12 @@ class ConfigTest(unittest.TestCase):
|
||||
d["terminals"]["oregon"]["concurrency"] = "two"
|
||||
self.check_both(d, "concurrency")
|
||||
|
||||
def test_concurrency_zero_is_valid(self): # 0 = terminal paused (nothing new is dispatched)
|
||||
d = config_data()
|
||||
d["terminals"]["win10"]["concurrency"] = 0
|
||||
self.assertEqual(config.validate(d), [])
|
||||
self.assertEqual(config.mini_validate(d, SCHEMA), [])
|
||||
|
||||
def test_semantic_routing_to_unknown_terminal(self):
|
||||
d = config_data()
|
||||
d["projects"]["GWENT"]["routing"]["by_assignee"]["mac"] = "mac"
|
||||
|
||||
@@ -160,6 +160,14 @@ class CoreTest(unittest.TestCase):
|
||||
self.assertEqual(started, ["GW-1-1", "GW-3-1"]) # 2 slots; GW-2 waits for the game group
|
||||
self.assertEqual([self.p.state_of(x) for x in (a, b, c, d)], ["In Progress", "Todo", "In Progress", "Todo"])
|
||||
|
||||
def test_concurrency_zero_pauses_terminal(self):
|
||||
self.h.cfg.terminals["win10"]["concurrency"] = 0
|
||||
w = self.p.add_issue(1)
|
||||
o = self.p.add_issue(2, assignee="oregon")
|
||||
self.h.tick()
|
||||
self.assertNotIn(("win10", "codex"), self.h.execs) # not even probed (busy_processes)
|
||||
self.assertEqual((self.p.state_of(w), self.p.state_of(o)), ("Todo", "In Progress"))
|
||||
|
||||
def test_label_routing(self):
|
||||
self.p.add_issue(1, labels=["model:astra"])
|
||||
self.p.add_issue(2, assignee="oregon", labels=["cap:gpu"]) # oregon lacks the capability
|
||||
|
||||
Reference in New Issue
Block a user