relay-flow 0.0.1 → 0.2.0-alpha
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/README.md +148 -143
- package/cmd/relay-flow/commands_test.go +464 -0
- package/cmd/relay-flow/main.go +670 -180
- package/cmd/relay-flow/scenario_test.go +1135 -0
- package/cmd/relay-flow/serve.go +609 -0
- package/go.mod +69 -2
- package/go.sum +185 -0
- package/internal/config/config.go +88 -0
- package/internal/config/machine.go +99 -48
- package/internal/config/machine_test.go +248 -0
- package/internal/config/merge_test.go +118 -0
- package/internal/config/writeatomic.go +36 -0
- package/internal/config/writeatomic_test.go +98 -0
- package/internal/execution/goworkflows/activities.go +490 -0
- package/internal/execution/goworkflows/engine.go +487 -0
- package/internal/execution/goworkflows/engine_test.go +600 -0
- package/internal/execution/goworkflows/fakes_test.go +517 -0
- package/internal/execution/goworkflows/interpreter.go +605 -0
- package/internal/execution/goworkflows/logging_test.go +154 -0
- package/internal/execution/goworkflows/mailbox_test.go +423 -0
- package/internal/execution/goworkflows/node_runtime_integration_test.go +127 -0
- package/internal/execution/goworkflows/node_runtime_test.go +486 -0
- package/internal/execution/goworkflows/projection.go +504 -0
- package/internal/execution/goworkflows/recovery_test.go +1092 -0
- package/internal/execution/goworkflows/retry_log_test.go +59 -0
- package/internal/execution/goworkflows/retry_projection_test.go +98 -0
- package/internal/harness/contract_test.go +169 -0
- package/internal/harness/factory.go +63 -0
- package/internal/harness/harness.go +41 -0
- package/internal/harness/opencode/opencode.go +166 -0
- package/internal/harness/opencode/opencode_test.go +50 -0
- package/internal/harness/plugin_selection_test.go +126 -0
- package/internal/identity/identity.go +37 -0
- package/internal/logging/logging.go +56 -0
- package/internal/logging/logging_test.go +116 -0
- package/internal/paths/paths.go +67 -0
- package/internal/recover/recover.go +115 -0
- package/internal/repo/poller.go +186 -0
- package/internal/repo/poller_test.go +327 -0
- package/internal/repo/repo.go +119 -0
- package/internal/repo/service.go +216 -0
- package/internal/repo/service_test.go +298 -0
- package/internal/retry/retry.go +118 -0
- package/internal/router/router.go +83 -0
- package/internal/router/router_test.go +144 -0
- package/internal/run/manager.go +108 -0
- package/internal/run/run.go +140 -0
- package/internal/run/run_identity_test.go +52 -0
- package/internal/run/run_manager_test.go +266 -0
- package/internal/runner/contract_test.go +221 -0
- package/internal/runner/factory.go +65 -0
- package/internal/runner/orca/orca.go +363 -170
- package/internal/runner/orca/orca_test.go +134 -160
- package/internal/runner/orca/orcacli/orcacli.go +215 -0
- package/internal/runner/orca/orcacli/orcacli_test.go +154 -0
- package/internal/runner/orca/orcacli/testdata/repo-list.json +18 -0
- package/internal/runner/orca/orcacli/testdata/strict-orca.sh +30 -0
- package/internal/runner/orca/orcacli/testdata/terminal-close.json +12 -0
- package/internal/runner/orca/orcacli/testdata/terminal-create.json +18 -0
- package/internal/runner/orca/orcacli/testdata/terminal-list.json +51 -0
- package/internal/runner/orca/orcacli/testdata/terminal-send.json +1 -0
- package/internal/runner/orca/orcacli/testdata/terminal-show.json +1 -0
- package/internal/runner/orca/orcacli/testdata/worktree-create.json +22 -0
- package/internal/runner/orca/orcacli/testdata/worktree-list.json +31 -0
- package/internal/runner/orca/orcacli/testdata/worktree-remove.json +6 -0
- package/internal/runner/runner.go +47 -64
- package/internal/server/api_test.go +300 -0
- package/internal/server/client.go +192 -74
- package/internal/server/fixture_test.go +248 -0
- package/internal/server/server.go +425 -248
- package/internal/server/shutdown_test.go +116 -0
- package/internal/task/contract_test.go +223 -0
- package/internal/task/factory.go +103 -0
- package/internal/task/jira/acli/acli.go +306 -0
- package/internal/task/jira/acli/acli_test.go +208 -0
- package/internal/task/jira/acli/testdata/acli_comments.json +55 -0
- package/internal/task/jira/acli/testdata/search_invalid_assignee.txt +1 -0
- package/internal/task/jira/acli/testdata/search_invalid_status.txt +1 -0
- package/internal/task/jira/acli/testdata/search_success.json +1 -0
- package/internal/task/jira/filters_test.go +234 -0
- package/internal/task/jira/helpers_test.go +60 -0
- package/internal/task/jira/jira.go +507 -0
- package/internal/task/jira/normalize.go +101 -0
- package/internal/task/jira/testdata/acli_search.json +120 -0
- package/internal/task/jira/transition_defaults_test.go +156 -0
- package/internal/task/jira/validation_test.go +94 -0
- package/internal/task/task.go +84 -0
- package/internal/workflow/report.go +85 -0
- package/internal/workflow/report_test.go +259 -0
- package/internal/workflow/service.go +142 -0
- package/internal/workflow/store.go +136 -0
- package/internal/workflow/store_test.go +282 -0
- package/internal/workflow/workflow.go +342 -0
- package/internal/workflow/workflow_test.go +410 -0
- package/package.json +1 -1
- package/internal/acli/acli.go +0 -229
- package/internal/config/demo_test.go +0 -17
- package/internal/config/schema.go +0 -193
- package/internal/config/schema_test.go +0 -162
- package/internal/daemon/daemon.go +0 -218
- package/internal/daemon/daemon_test.go +0 -204
- package/internal/discovery/discovery.go +0 -122
- package/internal/discovery/discovery_test.go +0 -62
- package/internal/opencode/opencode.go +0 -26
- package/internal/orcacli/orcacli.go +0 -264
- package/internal/runner/orca/README.md +0 -64
- package/internal/runner/runner_test.go +0 -64
- package/internal/server/server_test.go +0 -195
- package/internal/tasks/jira/README.md +0 -69
- package/internal/tasks/jira/component_test.go +0 -16
- package/internal/tasks/jira/decode.go +0 -24
- package/internal/tasks/jira/jira.go +0 -231
- package/internal/tasks/jira/jira_test.go +0 -259
- package/internal/tasks/jira/jql_test.go +0 -16
- package/internal/tasks/tasks.go +0 -90
- package/internal/tasks/tasks_test.go +0 -91
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# relay-flow
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Durable, graph-based agent workflow runner. A ticket is the unit of work; a workflow YAML declares nodes and routes; a durable engine (go-workflows + SQLite) drives progression, waits, retries, and recovery. The task system (Jira built-in) supplies parent tickets and mailbox subtasks; the runner (Orca built-in) owns worktrees and terminals; the harness (OpenCode built-in) owns agent sessions and report semantics. All three are pluggable.
|
|
4
|
+
|
|
5
|
+
This is a ground-up rewrite. The previous per-workflow, in-memory daemon is gone. There is no migration path and no compatibility layer.
|
|
4
6
|
|
|
5
7
|
---
|
|
6
8
|
|
|
@@ -10,190 +12,193 @@ Graph-based agent workflow engine. Tickets are tokens moving across **nodes**; e
|
|
|
10
12
|
|
|
11
13
|
| Tool | Why |
|
|
12
14
|
|---|---|
|
|
13
|
-
| [opencode](https://opencode.ai) | Agents run in opencode sessions |
|
|
14
|
-
| [Orca](https://github.com/Necmttn/orca) CLI + app | Worktrees + terminals (
|
|
15
|
-
| [acli](https://developer.atlassian.com/cloud/acli) | Jira access (
|
|
16
|
-
| Go 1.
|
|
15
|
+
| [opencode](https://opencode.ai) | Agents run in opencode sessions (harness) |
|
|
16
|
+
| [Orca](https://github.com/Necmttn/orca) CLI + app | Worktrees + terminals (runner) |
|
|
17
|
+
| [acli](https://developer.atlassian.com/cloud/acli) | Jira access (task system) |
|
|
18
|
+
| Go 1.24+ | Build the CLI |
|
|
17
19
|
|
|
18
20
|
### Install
|
|
19
21
|
|
|
20
22
|
```sh
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
go install github.com/rajpopat27/relay-flow/cmd/relay-flow@latest
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
OpenCode plugin: add `"relay-flow-plugin"` to the `plugin` array in your repo's `opencode.json`:
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"$schema": "https://opencode.ai/config.json",
|
|
31
|
+
"plugin": ["relay-flow-plugin"]
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The plugin is the report-path half of the harness contract: it parses the agent's structured report, applies the agent/HITL nudge policy, and delivers `{runId, node, reportId, report}` via `relay-flow report` with retry.
|
|
26
36
|
|
|
27
|
-
|
|
28
|
-
go install github.com/rajpopat27/relay-flow/cmd/relay-flow@v0.1.2
|
|
37
|
+
### One-time machine setup
|
|
29
38
|
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
```sh
|
|
40
|
+
relay-flow init
|
|
32
41
|
```
|
|
33
42
|
|
|
34
|
-
|
|
43
|
+
Prompts for the three plugin names (`task`, `runner`, `harness` — e.g. `jira`, `orca`, `opencode`), atomically writes `~/.relay-flow/config.yaml` (0600), and initializes `~/.relay-flow/state.db`. Refuses to overwrite existing configuration or history.
|
|
35
44
|
|
|
36
|
-
|
|
45
|
+
The full machine layout is fixed under `~/.relay-flow` (0700):
|
|
37
46
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
```
|
|
48
|
+
config.yaml 0600 machine config
|
|
49
|
+
state.db 0600 durable execution (SQLite)
|
|
50
|
+
server.sock 0600 CLI ↔ server
|
|
51
|
+
server.lock 0600 single-process flock
|
|
52
|
+
server.log 0600
|
|
53
|
+
plugin.log 0600
|
|
54
|
+
workflows/<name>.yaml 0644 submitted workflow definitions
|
|
55
|
+
```
|
|
43
56
|
|
|
44
|
-
|
|
57
|
+
### Register a repo
|
|
45
58
|
|
|
46
|
-
|
|
59
|
+
```sh
|
|
60
|
+
relay-flow repo register
|
|
61
|
+
```
|
|
47
62
|
|
|
48
|
-
|
|
63
|
+
Discovers/selects a runner repo, collects the task plugin's required keys, validates runner + task connectivity, and atomically saves. Registration is rejected while another registered repo already holds the same canonical task scope (e.g. same Jira site+project+component).
|
|
49
64
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
65
|
+
### Submit a workflow
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
relay-flow workflow submit --file <path>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Workflows live at `~/.relay-flow/workflows/<name>.yaml` after submit. Replacement and removal are rejected while any run of that workflow is active.
|
|
72
|
+
|
|
73
|
+
### Run
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
relay-flow serve # normal start; requires an initialized database
|
|
77
|
+
relay-flow serve --recover # explicit destructive rebuild from the task system
|
|
78
|
+
relay-flow stop
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`serve --recover` treats ALL SQLite execution state as gone, closes surviving run-owned terminals (preserving worktrees and code), resets Jira parent+mailbox state, and starts every labeled parent in a fresh deterministic run from `start` with fresh `nodeVisitID`s. Recovery never runs automatically; database loss is never inferred.
|
|
82
|
+
|
|
83
|
+
---
|
|
53
84
|
|
|
54
|
-
|
|
55
|
-
type: jira
|
|
56
|
-
config: # opaque to core; strictly validated by the adapter
|
|
57
|
-
query: project = ABCD # JQL fragment (no issuetype/assignee/ORDER BY)
|
|
58
|
-
issueTypes: [Task]
|
|
59
|
-
assigneeIsAgent: true # or omit → assignee comes from `relay-flow init`
|
|
85
|
+
## Workflow YAML
|
|
60
86
|
|
|
61
|
-
|
|
62
|
-
|
|
87
|
+
```yaml
|
|
88
|
+
name: basicFlow # lowerCamel; determines claim label wf:basicFlow
|
|
89
|
+
repos: [payments] # one or more registered repos, unique
|
|
90
|
+
cleanupRunnerOnEnd: false # optional; when true the runner tears down at end
|
|
63
91
|
|
|
64
|
-
|
|
92
|
+
taskConfig: # optional; adapter-owned; merged root → repo → workflow → node
|
|
93
|
+
transitions:
|
|
94
|
+
start: { parent: "In Progress" }
|
|
95
|
+
work: { mailbox: "In Progress" }
|
|
96
|
+
end: { parent: "Done" }
|
|
65
97
|
|
|
66
98
|
nodes:
|
|
99
|
+
start:
|
|
100
|
+
onSuccess: [{ target: coding }]
|
|
101
|
+
|
|
67
102
|
coding:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
103
|
+
type: agent # or hitl
|
|
104
|
+
agent: build # opencode agent
|
|
105
|
+
description: | # becomes the mailbox description and launch prompt
|
|
106
|
+
Implement the ticket.
|
|
107
|
+
onSuccess: [{ target: reviewing, when: "work complete" }]
|
|
108
|
+
onFailure: [{ target: coding, when: "retry" }]
|
|
109
|
+
nudgePrompt: "Check edge cases for {{ticket}} before reporting." # optional custom instructions
|
|
110
|
+
|
|
73
111
|
reviewing:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
112
|
+
type: hitl
|
|
113
|
+
agent: build
|
|
114
|
+
description: Human review.
|
|
115
|
+
onSuccess: [{ target: end }]
|
|
116
|
+
onFailure: [{ target: coding }]
|
|
117
|
+
|
|
118
|
+
end: {}
|
|
80
119
|
```
|
|
81
120
|
|
|
82
|
-
|
|
121
|
+
Rules enforced at submit:
|
|
83
122
|
|
|
84
|
-
|
|
123
|
+
- `start` and `end` are reserved lifecycle nodes. `start` has exactly one success target and no type/agent/description/routes on failure. `end` has no type/agent/description/routes.
|
|
124
|
+
- Every other node is `agent` or `hitl`, has an agent and a description, and declares at least one valid route for every permitted outcome.
|
|
125
|
+
- Routes are single-target; no route may target `start`.
|
|
126
|
+
- The graph must be fully reachable from `start`; unknown fields are rejected; `runnerPlugin`/`harnessPlugin`/`closeOn`/legacy `tasks`/`runner` keys are rejected.
|
|
127
|
+
- Only `agent` and `hitl` nodes receive mailbox subtasks; `start` and `end` never do.
|
|
128
|
+
- `cleanupRunnerOnEnd` is the only workflow cleanup knob and takes priority over terminal retention after `end`; the word `terminal` refers to runner terminals only.
|
|
85
129
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
130
|
+
### Task-config merge
|
|
131
|
+
|
|
132
|
+
`taskConfig` may appear at root, repo, workflow, and node scopes. The adapter merges in that order: maps merge recursively, later scalar/list replaces, omitted keys inherit, explicit YAML `null` is rejected. The merged values decode against one adapter-owned typed config at use time.
|
|
133
|
+
|
|
134
|
+
### Jira transition defaults
|
|
135
|
+
|
|
136
|
+
Omitted transitions default to:
|
|
137
|
+
|
|
138
|
+
- `start`: parent → `In Progress`
|
|
139
|
+
- work node: mailbox → `In Progress` (parent unchanged)
|
|
140
|
+
- `end`: parent → `Done`
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Structured node report
|
|
145
|
+
|
|
146
|
+
Every visit (agent or HITL) ends with the same contract:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
STATUS: success | failure
|
|
150
|
+
NEXT STEP: <one configured route for that status>
|
|
151
|
+
|
|
152
|
+
SUMMARY
|
|
153
|
+
- Completed: ...
|
|
154
|
+
- Not completed: ... | None
|
|
155
|
+
- Issues discovered: ... | None
|
|
156
|
+
- Verification: ...
|
|
157
|
+
- Notes: ... | None
|
|
158
|
+
|
|
159
|
+
FEEDBACK
|
|
160
|
+
- Reason for next step: ...
|
|
161
|
+
- Required actions: ...
|
|
162
|
+
- Relevant context: ...
|
|
163
|
+
- Expected result: ...
|
|
90
164
|
```
|
|
91
165
|
|
|
92
|
-
`
|
|
166
|
+
`None` is the literal marker for an intentionally empty section. When `NEXT STEP` is `end`, every FEEDBACK field must be `None` and no feedback comment is written.
|
|
167
|
+
|
|
168
|
+
The plugin delivers the report as one JSON object via `relay-flow report` stdin with the shared backoff (initial 2s, factor 2, jitter 0.2, max 5m) until acknowledged. Duplicate/stale reports are acked safely with no repeated graph effects. Invalid agent output is nudged; invalid HITL output stays silent.
|
|
93
169
|
|
|
94
170
|
---
|
|
95
171
|
|
|
96
172
|
## Architecture
|
|
97
173
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
- **
|
|
101
|
-
- **
|
|
102
|
-
- **
|
|
103
|
-
- **
|
|
104
|
-
- The **tracker is the single source of truth**. The server holds no database; restart = resubmit, and claim labels (`wf:<name>`) survive to drive recovery.
|
|
105
|
-
|
|
106
|
-
### End-to-end sequence
|
|
107
|
-
|
|
108
|
-
```mermaid
|
|
109
|
-
sequenceDiagram
|
|
110
|
-
participant J as Tracker (Jira)
|
|
111
|
-
participant S as relay-flow serve
|
|
112
|
-
participant R as Runner (Orca)
|
|
113
|
-
participant O as OpenCode + plugin
|
|
114
|
-
|
|
115
|
-
loop every pollIntervalSeconds
|
|
116
|
-
S->>J: List() — one query (query + issuetype + component + assignee)
|
|
117
|
-
J-->>S: tickets (Node via when-map, ClaimedBy via wf:* labels)
|
|
118
|
-
end
|
|
119
|
-
S->>J: Claim(ticket) — add label wf:xyzTaskFlow
|
|
120
|
-
S->>R: Spawn(ticket, node, agent, env RELAY_*)
|
|
121
|
-
R->>R: ensure worktree → terminal key:agent:node → opencode --prompt
|
|
122
|
-
R->>O: agent session starts
|
|
123
|
-
O->>O: works… ends reply with STATUS/SUMMARY
|
|
124
|
-
O->>S: plugin: relay-flow report --workflow --ticket --node --outcome --summary
|
|
125
|
-
S->>J: Report → transition to target node's state + comment
|
|
126
|
-
S-->>O: {action: transitioned | commented | error}
|
|
127
|
-
Note over O: action=error → plugin retries 3× → nudges the session
|
|
128
|
-
```
|
|
174
|
+
- **Task system** owns parent tickets, mailbox subtasks, task state, labels, comments, and adapter config. The parent ticket is the unit of work.
|
|
175
|
+
- **Durable workflow engine** (go-workflows + SQLite) owns graph progression, waits, reports, retries, and recovery. No custom state machine.
|
|
176
|
+
- **Mailbox subtask** is one agent/HITL node's scratch space; its description defines the node's work and its comments hold the node's summary plus selected incoming feedback.
|
|
177
|
+
- **Harness** owns agent launch, session/report behavior, parsing, nudging, and resume semantics.
|
|
178
|
+
- **Runner** owns ticket worktrees/environments, terminals, liveness, and execution of harness commands.
|
|
179
|
+
- **Compensation/rollback never exists.** Recovery always rolls forward through idempotent activities.
|
|
129
180
|
|
|
130
|
-
###
|
|
131
|
-
|
|
132
|
-
```mermaid
|
|
133
|
-
flowchart TD
|
|
134
|
-
L[tasks.List] --> T{per ticket}
|
|
135
|
-
T -->|ClaimedBy = other workflow| SKIP1[skip — mutex]
|
|
136
|
-
T -->|Node unmapped| SKIP2[log + skip]
|
|
137
|
-
T -->|node in closeOn| CLOSE[runner.Close — tear down terminals]
|
|
138
|
-
T -->|node agentless| GATE[claim if unclaimed, then leave for the human]
|
|
139
|
-
T -->|ClaimedBy = me, unknown in memory| BOUNCE[go bounce]
|
|
140
|
-
T -->|unclaimed| DISP[go dispatch]
|
|
141
|
-
|
|
142
|
-
DISP --> C1[tasks.Claim] --> S1[runner.Spawn fresh session]
|
|
143
|
-
BOUNCE --> F{runner.Find by title}
|
|
144
|
-
F -->|session alive| N1[Nudge once per node visit]
|
|
145
|
-
F -->|gone| S2[Spawn fresh — claim already held]
|
|
146
|
-
```
|
|
181
|
+
### Identity
|
|
147
182
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
```mermaid
|
|
153
|
-
flowchart LR
|
|
154
|
-
subgraph CLI[relay-flow CLI]
|
|
155
|
-
M[cmd/relay-flow<br/>serve · submit · report · init]
|
|
156
|
-
end
|
|
157
|
-
subgraph SRV[server — one process, N workflows]
|
|
158
|
-
H[/submit · /report · /shutdown/]
|
|
159
|
-
D1[daemon: poll loop] --> T1[tasks iface]
|
|
160
|
-
D1 --> R1[runner iface]
|
|
161
|
-
end
|
|
162
|
-
subgraph ADAPTERS[adapters — registry pattern]
|
|
163
|
-
J[tasks/jira<br/>acli] -.-> T1
|
|
164
|
-
O[runner/orca<br/>worktrees + terminals] -.-> R1
|
|
165
|
-
end
|
|
166
|
-
M -->|unix socket ~/.relay-flow/server.sock| H
|
|
167
|
-
H --> D1
|
|
168
|
-
```
|
|
183
|
+
- `runID` is deterministic from `repo/workflow/ticket`.
|
|
184
|
+
- `nodeVisitID` is generated once per node entry as a durable replay-safe side effect; it changes on revisit and on fresh runs after `--recover`.
|
|
185
|
+
- Terminal titles are stable `<ticket>:<node>` — they never carry `nodeVisitID`, workflow, or agent.
|
|
186
|
+
- Report wire keys are `runId` / `node` / `reportId`; `nodeVisitID` stays internal.
|
|
169
187
|
|
|
170
|
-
|
|
171
|
-
|---|---|---|
|
|
172
|
-
| opencode plugin | `plugin/report-status.ts` | Parses STATUS/SUMMARY deterministically, calls `relay-flow report` (thin socket client), retries/nudges |
|
|
173
|
-
| CLI | `cmd/relay-flow/` | `serve` hosts workflows; `submit` registers one; `report` is a one-shot client |
|
|
174
|
-
| daemon | `internal/daemon/` | Poll loop, 3-way switch, dispatch/bounce goroutines |
|
|
175
|
-
| server | `internal/server/` | Socket lifecycle, submit validation, report routing |
|
|
176
|
-
| config | `internal/config/` | Workflow YAML schema + graph validation |
|
|
177
|
-
| Jira adapter | `internal/tasks/jira/` | [readme](internal/tasks/jira/README.md) — query/claim/report over acli |
|
|
178
|
-
| Orca adapter | `internal/runner/orca/` | [readme](internal/runner/orca/README.md) — worktrees, terminals, prompts |
|
|
188
|
+
### Poll cycle
|
|
179
189
|
|
|
180
|
-
|
|
190
|
+
One Repo Poller per registered repo (not per workflow) fetches active parent tickets on the configured `pollIntervalSeconds` (default 15). Per ticket, the Ticket Router resolves at most one workflow: multiple `wf:*` claims → `InvalidClaimError`; exactly one claim resolves directly; zero filter matches → `ErrNoMatch`; one match → that workflow; multiple matches → `AmbiguousError` with no mutation. Successful resolution goes to the Run Manager; the run is claimed with `wf:<name>` before `EnsureRun` fires.
|
|
181
191
|
|
|
182
|
-
|
|
183
|
-
- **No fallback paths**: report goes through the server or not at all; server down = system down (the plugin's 3× retry + nudge covers transient gaps).
|
|
184
|
-
- **Labels are never removed**: `wf:<name>` is the crash-recovery anchor.
|
|
185
|
-
- **Terminals outlive their node visit** — a bounce reuses the session; only `closeOn` nodes close them.
|
|
186
|
-
- **Parallelism**: one long-lived poll goroutine per workflow; short-lived dispatch/bounce goroutines per ticket; tickets at the same node run in parallel terminals with isolated worktrees.
|
|
192
|
+
### Shutdown and recovery
|
|
187
193
|
|
|
188
|
-
|
|
194
|
+
- Graceful shutdown stops accepting requests and new polls immediately, cancels worker polling, waits up to 30s for running calls, then closes the socket and database. Durable unfinished work resumes on the next normal start.
|
|
195
|
+
- Completed/canceled runs are removed after `completedRunRetentionDays` (default 30). The retention sweep runs once at startup, never on a ticker.
|
|
189
196
|
|
|
190
|
-
|
|
191
|
-
New execution backend (tmux, …): implement `runner.Runner` + register — see [runner/orca README](internal/runner/orca/README.md#writing-a-new-runner-tmux-).
|
|
197
|
+
---
|
|
192
198
|
|
|
193
199
|
## Development
|
|
194
200
|
|
|
195
201
|
```sh
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
go install ./...
|
|
202
|
+
go test ./...
|
|
203
|
+
cd plugin && bun test
|
|
199
204
|
```
|