ftown-bridge 0.19.39 → 0.19.40
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/create-ftown-session.js +3 -8
- package/dist/create-ftown-session.js.map +1 -1
- package/dist/ftown-cli.d.ts +1 -0
- package/dist/ftown-cli.js +15 -7
- package/dist/ftown-cli.js.map +1 -1
- package/dist/ftown-env-cli.d.ts +1 -0
- package/dist/ftown-env-cli.js +15 -4
- package/dist/ftown-env-cli.js.map +1 -1
- package/dist/ftown-home.js +1 -1
- package/dist/ftown-sessions-cli.js +3 -3
- package/dist/harness-cli.js +3 -3
- package/dist/harness-installer.js +8 -75
- package/dist/harness-installer.js.map +1 -1
- package/dist/harness-registry.d.ts +2 -23
- package/dist/harness-registry.js +2 -16
- package/dist/harness-registry.js.map +1 -1
- package/dist/index.js +2 -8
- package/dist/index.js.map +1 -1
- package/dist/install-ftown-skill.d.ts +6 -0
- package/dist/install-ftown-skill.js +78 -3
- package/dist/install-ftown-skill.js.map +1 -1
- package/dist/mail-delivery.js +1 -1
- package/dist/mail-delivery.js.map +1 -1
- package/dist/provider-env-store.js +1 -1
- package/dist/provider-env-store.js.map +1 -1
- package/dist/retired-skill-assets.d.ts +3 -0
- package/dist/retired-skill-assets.js +11 -0
- package/dist/retired-skill-assets.js.map +1 -1
- package/package.json +2 -3
- package/skills/ftown/SKILL.md +54 -19
- package/skills/ftown/references/orchestrator.md +22 -99
- package/skills/ftown/references/sessions.md +32 -35
- package/dist/claude-trust.d.ts +0 -11
- package/dist/claude-trust.js +0 -47
- package/dist/claude-trust.js.map +0 -1
- package/dist/install-ftown-workflows-cli.d.ts +0 -3
- package/dist/install-ftown-workflows-cli.js +0 -33
- package/dist/install-ftown-workflows-cli.js.map +0 -1
- package/dist/workflow-runner-cli.d.ts +0 -2
- package/dist/workflow-runner-cli.js +0 -376
- package/dist/workflow-runner-cli.js.map +0 -1
- package/dist/workflow-runner.d.ts +0 -163
- package/dist/workflow-runner.js +0 -307
- package/dist/workflow-runner.js.map +0 -1
- package/skills/fticket/SKILL.md +0 -166
- package/skills/ftown/references/workflows.md +0 -470
- package/skills/ftown/scripts/example.flow.mjs +0 -170
package/dist/workflow-runner.js
DELETED
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ftown-workflows engine.
|
|
3
|
-
*
|
|
4
|
-
* Brings Workflow-tool-style deterministic orchestration to REAL ftown sessions:
|
|
5
|
-
* each `agent()` call spawns a real ftown session via the bridge loopback API, the
|
|
6
|
-
* child writes its result to a file, the runner polls the filesystem for that file
|
|
7
|
-
* (race-free — no inbox / Stop-hook contention), removes the session and returns it.
|
|
8
|
-
*
|
|
9
|
-
* The engine is fully dependency-injected (BridgeClient / ResultStore / Clock /
|
|
10
|
-
* Logger) so it is unit-testable without a live bridge, real fs or real timers.
|
|
11
|
-
*/
|
|
12
|
-
const DEFAULT_TIMEOUT_MS = 1_800_000;
|
|
13
|
-
const DEFAULT_MAX_CONCURRENT = 4;
|
|
14
|
-
const DEFAULT_POLL_INTERVAL_MS = 2000;
|
|
15
|
-
// A new session reports running:false for a short startup window; only after this grace
|
|
16
|
-
// (with the child never observed running) do we treat it as failed-to-start.
|
|
17
|
-
const DEFAULT_STARTUP_GRACE_MS = 30_000;
|
|
18
|
-
// ---- Pure helpers (exported + individually unit-tested) ----
|
|
19
|
-
/**
|
|
20
|
-
* Build the full child prompt: the user's `task`, then a clearly delimited protocol
|
|
21
|
-
* block instructing the child to write its final result as JSON to `resultFilePath`
|
|
22
|
-
* and then stop. When `schema` is provided, the block embeds the schema and says
|
|
23
|
-
* `result` must conform. The block always literally contains `resultFilePath`.
|
|
24
|
-
*/
|
|
25
|
-
export function buildAgentPrompt(task, resultFilePath, schema) {
|
|
26
|
-
const lines = [
|
|
27
|
-
task.trim(),
|
|
28
|
-
'',
|
|
29
|
-
'--- ftown-workflows RESULT PROTOCOL (read carefully) ---',
|
|
30
|
-
'You are a worker session in a deterministic workflow. When you have finished the',
|
|
31
|
-
'task above, you MUST write your FINAL result as a single JSON object to this file:',
|
|
32
|
-
'',
|
|
33
|
-
` RESULT FILE: ${resultFilePath}`,
|
|
34
|
-
'',
|
|
35
|
-
'The JSON object must have this exact shape:',
|
|
36
|
-
' { "ok": true, "result": <your result> } on success',
|
|
37
|
-
' { "ok": false, "error": "<why it failed>" } on failure',
|
|
38
|
-
'',
|
|
39
|
-
];
|
|
40
|
-
if (schema) {
|
|
41
|
-
lines.push('On success, `result` MUST be valid JSON conforming to this JSON schema:', '', JSON.stringify(schema, null, 2), '');
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
lines.push('On success, `result` may be a plain string or any JSON value. (Without a schema, a', 'non-string `result` is returned to the calling script as a JSON string.)', '');
|
|
45
|
-
}
|
|
46
|
-
lines.push('Write the file atomically (write fully, then save). Do NOT print the result to the', 'terminal instead of the file.', 'The result FILE is the ONLY accepted output channel. Do NOT report via ftown-harness mail, the terminal, or any other channel.', 'After the file is written, STOP — do no further work.', `Reminder: the result file path is ${resultFilePath}`, '--- end protocol ---');
|
|
47
|
-
return lines.join('\n');
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Parse raw file text into RawResult. Returns null if text is empty or not valid
|
|
51
|
-
* JSON (partial write). Throws nothing. If JSON parses but lacks a boolean `ok`,
|
|
52
|
-
* treat as null (not yet complete).
|
|
53
|
-
*/
|
|
54
|
-
export function parseResultFile(text) {
|
|
55
|
-
if (!text || text.trim().length === 0)
|
|
56
|
-
return null;
|
|
57
|
-
let parsed;
|
|
58
|
-
try {
|
|
59
|
-
parsed = JSON.parse(text);
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
65
|
-
return null;
|
|
66
|
-
const obj = parsed;
|
|
67
|
-
if (typeof obj.ok !== 'boolean')
|
|
68
|
-
return null;
|
|
69
|
-
return obj;
|
|
70
|
-
}
|
|
71
|
-
// ---- Internal helpers ----
|
|
72
|
-
/** Async counting semaphore: caps concurrently-running spawn+poll cycles. */
|
|
73
|
-
class Semaphore {
|
|
74
|
-
permits;
|
|
75
|
-
waiters = [];
|
|
76
|
-
constructor(permits) {
|
|
77
|
-
this.permits = Number.isFinite(permits) ? Math.max(1, Math.floor(permits)) : 1;
|
|
78
|
-
}
|
|
79
|
-
async acquire() {
|
|
80
|
-
if (this.permits > 0) {
|
|
81
|
-
this.permits -= 1;
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
await new Promise((resolve) => this.waiters.push(resolve));
|
|
85
|
-
}
|
|
86
|
-
release() {
|
|
87
|
-
const next = this.waiters.shift();
|
|
88
|
-
if (next) {
|
|
89
|
-
// Hand the permit directly to the next waiter (keeps the cap exact).
|
|
90
|
-
next();
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
this.permits += 1;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
/** Make a label filesystem-safe for use as a result-file stem. */
|
|
98
|
-
function sanitizeStepKey(label) {
|
|
99
|
-
const cleaned = label
|
|
100
|
-
.replace(/[^A-Za-z0-9._-]+/g, '-')
|
|
101
|
-
.replace(/\.{2,}/g, '-')
|
|
102
|
-
.replace(/^-+|-+$/g, '');
|
|
103
|
-
return cleaned.length > 0 ? cleaned : 'step';
|
|
104
|
-
}
|
|
105
|
-
/** Map a successful RawResult to the value agent() returns. */
|
|
106
|
-
function mapOk(raw, hasSchema) {
|
|
107
|
-
const value = raw.result;
|
|
108
|
-
if (hasSchema)
|
|
109
|
-
return value ?? null;
|
|
110
|
-
if (value === undefined || value === null)
|
|
111
|
-
return null;
|
|
112
|
-
// No schema: a string is returned as-is; a non-string structured value is
|
|
113
|
-
// JSON-stringified (NOT lossily String()-coerced to "[object Object]").
|
|
114
|
-
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
115
|
-
}
|
|
116
|
-
/** Build + run the context, execute the module, return its return value. */
|
|
117
|
-
export async function runWorkflow(deps, mod, opts) {
|
|
118
|
-
const body = mod.default ?? mod.run;
|
|
119
|
-
if (typeof body !== 'function') {
|
|
120
|
-
throw new Error('workflow module has no default or `run` export');
|
|
121
|
-
}
|
|
122
|
-
const { bridge, store, clock, logger } = deps;
|
|
123
|
-
const defaultShell = opts.defaultShell;
|
|
124
|
-
// Resolve numerics defensively: `??` does NOT catch NaN, and a NaN cap would
|
|
125
|
-
// deadlock the semaphore / make every timeout comparison false. Coerce non-finite
|
|
126
|
-
// values to the documented defaults.
|
|
127
|
-
const maxConcurrent = Number.isFinite(opts.maxConcurrent)
|
|
128
|
-
? Math.max(1, Math.floor(opts.maxConcurrent))
|
|
129
|
-
: DEFAULT_MAX_CONCURRENT;
|
|
130
|
-
const defaultTimeoutMs = Number.isFinite(opts.defaultTimeoutMs)
|
|
131
|
-
? Math.max(0, opts.defaultTimeoutMs)
|
|
132
|
-
: DEFAULT_TIMEOUT_MS;
|
|
133
|
-
// Number.isFinite(null) === false, so both NaN and null collapse to null (unbounded).
|
|
134
|
-
const maxAgents = Number.isFinite(opts.maxAgents)
|
|
135
|
-
? Math.max(0, Math.floor(opts.maxAgents))
|
|
136
|
-
: null;
|
|
137
|
-
const sem = new Semaphore(maxConcurrent);
|
|
138
|
-
// Per-run step-key state (drives resumability + dedup).
|
|
139
|
-
const usedKeys = new Set();
|
|
140
|
-
let agentSeq = 0;
|
|
141
|
-
// Budget accounting — only NON-cached spawns count.
|
|
142
|
-
let spentCount = 0;
|
|
143
|
-
function deriveStepKey(label) {
|
|
144
|
-
agentSeq += 1;
|
|
145
|
-
const base = sanitizeStepKey(label ?? `step-${agentSeq}`);
|
|
146
|
-
let key = base;
|
|
147
|
-
let n = 2;
|
|
148
|
-
while (usedKeys.has(key)) {
|
|
149
|
-
key = `${base}-${n}`;
|
|
150
|
-
n += 1;
|
|
151
|
-
}
|
|
152
|
-
usedKeys.add(key);
|
|
153
|
-
return key;
|
|
154
|
-
}
|
|
155
|
-
const budget = {
|
|
156
|
-
maxAgents,
|
|
157
|
-
spent: () => spentCount,
|
|
158
|
-
remaining: () => (maxAgents === null ? Infinity : maxAgents - spentCount),
|
|
159
|
-
};
|
|
160
|
-
/** Poll the result file until it appears, the session dies, or we hit the cap. */
|
|
161
|
-
async function pollForResult(sessionId, path, pollIntervalMs, timeoutMs, startupGraceMs) {
|
|
162
|
-
const start = clock.now();
|
|
163
|
-
// A freshly-created session reports `running: false` for a brief startup window
|
|
164
|
-
// (the PTY exists but the child process is not registered as running yet). If we
|
|
165
|
-
// treated that first `!running` as terminal we would kill the child before it ever
|
|
166
|
-
// booted. So `!running` is only terminal once the child has been observed running
|
|
167
|
-
// at least once (it ran, then exited), or once the startup grace has elapsed
|
|
168
|
-
// without it ever coming up (it failed to start).
|
|
169
|
-
let everRunning = false;
|
|
170
|
-
for (;;) {
|
|
171
|
-
const found = await store.readResult(path);
|
|
172
|
-
if (found)
|
|
173
|
-
return found;
|
|
174
|
-
const elapsed = clock.now() - start;
|
|
175
|
-
if (elapsed >= timeoutMs)
|
|
176
|
-
return null;
|
|
177
|
-
const running = await bridge.isRunning(sessionId);
|
|
178
|
-
if (running) {
|
|
179
|
-
everRunning = true;
|
|
180
|
-
}
|
|
181
|
-
else if (everRunning || elapsed >= startupGraceMs) {
|
|
182
|
-
// Ran-then-exited (do one final read for the wrote-then-exited race) or
|
|
183
|
-
// never started within the grace window.
|
|
184
|
-
return await store.readResult(path);
|
|
185
|
-
}
|
|
186
|
-
await clock.sleep(pollIntervalMs);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
async function agent(prompt, agentOpts = {}) {
|
|
190
|
-
if (typeof prompt !== 'string' || prompt.length === 0) {
|
|
191
|
-
throw new Error('agent(): prompt must be a non-empty string');
|
|
192
|
-
}
|
|
193
|
-
const stepKey = deriveStepKey(agentOpts.label);
|
|
194
|
-
const label = agentOpts.label ?? stepKey;
|
|
195
|
-
const phase = agentOpts.phase;
|
|
196
|
-
const hasSchema = agentOpts.schema !== undefined;
|
|
197
|
-
const path = store.resultPath(opts.runId, stepKey);
|
|
198
|
-
// 1. Resume: a valid result already on disk → return without spawning.
|
|
199
|
-
// A failing cache read is treated as a MISS (proceed to spawn) so agent()
|
|
200
|
-
// honors its never-rejects contract; the poll read inside the try block
|
|
201
|
-
// surfaces real store failures as null.
|
|
202
|
-
let cached = null;
|
|
203
|
-
try {
|
|
204
|
-
cached = await store.readResult(path);
|
|
205
|
-
}
|
|
206
|
-
catch {
|
|
207
|
-
cached = null;
|
|
208
|
-
}
|
|
209
|
-
if (cached) {
|
|
210
|
-
logger.event({ kind: 'agent-done', label, phase, ok: cached.ok, cached: true });
|
|
211
|
-
return cached.ok ? mapOk(cached, hasSchema) : null;
|
|
212
|
-
}
|
|
213
|
-
// 7. Budget: a set cap blocks further spawns once exhausted.
|
|
214
|
-
if (maxAgents !== null && spentCount >= maxAgents) {
|
|
215
|
-
logger.event({ kind: 'agent-error', label, phase, error: 'budget exhausted' });
|
|
216
|
-
return null;
|
|
217
|
-
}
|
|
218
|
-
spentCount += 1;
|
|
219
|
-
const spec = {
|
|
220
|
-
prompt: buildAgentPrompt(prompt, path, agentOpts.schema),
|
|
221
|
-
shellType: agentOpts.shell ?? defaultShell,
|
|
222
|
-
parentSessionId: opts.selfSessionId,
|
|
223
|
-
};
|
|
224
|
-
if (agentOpts.workdir ?? opts.workdir)
|
|
225
|
-
spec.workingDir = agentOpts.workdir ?? opts.workdir;
|
|
226
|
-
spec.name = agentOpts.name ?? label;
|
|
227
|
-
if (agentOpts.model)
|
|
228
|
-
spec.model = agentOpts.model;
|
|
229
|
-
// 6. Concurrency: only `maxConcurrent` spawn+poll cycles run at once.
|
|
230
|
-
await sem.acquire();
|
|
231
|
-
let sessionId;
|
|
232
|
-
try {
|
|
233
|
-
const session = await bridge.createSession(spec);
|
|
234
|
-
sessionId = session.id;
|
|
235
|
-
logger.event({ kind: 'agent-start', label, phase, sessionId });
|
|
236
|
-
const pollInterval = Number.isFinite(agentOpts.pollIntervalMs)
|
|
237
|
-
? Math.max(50, agentOpts.pollIntervalMs)
|
|
238
|
-
: DEFAULT_POLL_INTERVAL_MS;
|
|
239
|
-
const startupGrace = Number.isFinite(agentOpts.startupGraceMs)
|
|
240
|
-
? Math.max(0, agentOpts.startupGraceMs)
|
|
241
|
-
: DEFAULT_STARTUP_GRACE_MS;
|
|
242
|
-
const raw = await pollForResult(sessionId, path, pollInterval, agentOpts.timeoutMs ?? defaultTimeoutMs, startupGrace);
|
|
243
|
-
if (raw && raw.ok) {
|
|
244
|
-
logger.event({ kind: 'agent-done', label, phase, ok: true, cached: false });
|
|
245
|
-
return mapOk(raw, hasSchema);
|
|
246
|
-
}
|
|
247
|
-
const error = raw
|
|
248
|
-
? (raw.error ?? 'agent reported failure')
|
|
249
|
-
: 'no result (session exited or timed out)';
|
|
250
|
-
logger.event({ kind: 'agent-error', label, phase, error });
|
|
251
|
-
return null;
|
|
252
|
-
}
|
|
253
|
-
catch (err) {
|
|
254
|
-
// Real bridge/store errors must not reject agent() — map to a failure.
|
|
255
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
256
|
-
logger.event({ kind: 'agent-error', label, phase, error: message });
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
finally {
|
|
260
|
-
// 5. Cleanup: ALWAYS remove the session, swallowing 404s.
|
|
261
|
-
if (sessionId !== undefined) {
|
|
262
|
-
try {
|
|
263
|
-
await bridge.removeSession(sessionId);
|
|
264
|
-
}
|
|
265
|
-
catch {
|
|
266
|
-
/* removeSession must not throw on 404 — ignore. */
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
sem.release();
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
async function parallel(thunks) {
|
|
273
|
-
return Promise.all(thunks.map(async (thunk) => {
|
|
274
|
-
try {
|
|
275
|
-
return await thunk();
|
|
276
|
-
}
|
|
277
|
-
catch {
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
}));
|
|
281
|
-
}
|
|
282
|
-
async function pipeline(items, ...stages) {
|
|
283
|
-
return Promise.all(items.map(async (item, index) => {
|
|
284
|
-
let prev = item;
|
|
285
|
-
try {
|
|
286
|
-
for (const stage of stages) {
|
|
287
|
-
prev = await stage(prev, item, index);
|
|
288
|
-
}
|
|
289
|
-
return prev;
|
|
290
|
-
}
|
|
291
|
-
catch {
|
|
292
|
-
return null;
|
|
293
|
-
}
|
|
294
|
-
}));
|
|
295
|
-
}
|
|
296
|
-
const ctx = {
|
|
297
|
-
agent,
|
|
298
|
-
parallel,
|
|
299
|
-
pipeline,
|
|
300
|
-
phase: (title) => logger.event({ kind: 'phase', title }),
|
|
301
|
-
log: (message) => logger.event({ kind: 'log', message }),
|
|
302
|
-
args: opts.args,
|
|
303
|
-
budget,
|
|
304
|
-
};
|
|
305
|
-
return await body(ctx);
|
|
306
|
-
}
|
|
307
|
-
//# sourceMappingURL=workflow-runner.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-runner.js","sourceRoot":"","sources":["../src/workflow-runner.ts"],"names":[],"mappings":"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"}
|
package/skills/fticket/SKILL.md
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: fticket
|
|
3
|
-
description: >-
|
|
4
|
-
Operate fticket (FTS, the Factory Ticket System) through its `fts` CLI: tickets,
|
|
5
|
-
atomic claims, dependencies, resource leases with FIFO waitlists, scheduler sweeps,
|
|
6
|
-
status/history/events, and stuck-work diagnosis. Use whenever work mentions FTS,
|
|
7
|
-
factory.db, factory tickets, worker claims, resource leases, or factory pipelines.
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# fticket — transactional coordination for agent teams
|
|
11
|
-
|
|
12
|
-
FTS is the durable control plane for concurrent agents. It owns task status, atomic
|
|
13
|
-
claims, dependencies, resource leases, and an immutable history/event stream in one
|
|
14
|
-
SQLite database (WAL mode with race-free write transactions). Ticket folders hold
|
|
15
|
-
durable artifacts; FTS holds who owns work, what is blocked, and what happens next.
|
|
16
|
-
|
|
17
|
-
Prefer FTS over direct agent messages whenever an existing database/ticket is available:
|
|
18
|
-
|
|
19
|
-
- use the ticket and artifact folder for shared context;
|
|
20
|
-
- use claims, lifecycle transitions, notes, board/history/events for status;
|
|
21
|
-
- use dependencies for ordering;
|
|
22
|
-
- use resource leases for shared mutable surfaces;
|
|
23
|
-
- use direct messages only when FTS is unavailable/fenced or for urgent escalation.
|
|
24
|
-
|
|
25
|
-
## Runtime
|
|
26
|
-
|
|
27
|
-
- CLI: `fts` (also `python -m fticket`)
|
|
28
|
-
- Python: 3.13+
|
|
29
|
-
- Install: `uv tool install fticket` or `uv tool upgrade fticket`
|
|
30
|
-
- Without installing: `uvx --python 3.13 fticket ...`
|
|
31
|
-
- Database: `--db PATH`, default `factory.db`; the flag may precede or follow a subcommand.
|
|
32
|
-
- Configuration lives in the database; there are no fticket environment variables.
|
|
33
|
-
|
|
34
|
-
Never open or edit the SQLite file directly. Use the CLI/API so fencing, leases, events,
|
|
35
|
-
and invariants remain transactional.
|
|
36
|
-
|
|
37
|
-
## Ticket lifecycle
|
|
38
|
-
|
|
39
|
-
Statuses are `queued`, `claimed`, `in_progress`, `done`, `rejected`, `blocked`, and
|
|
40
|
-
`dead_letter`.
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
fts init --db factory.db --stages code,review,qa,deploy
|
|
44
|
-
fts create --db factory.db --title "build parser" --stage code --folder tasks/0001
|
|
45
|
-
fts claim --db factory.db --stage code --worker coder-1
|
|
46
|
-
fts start --db factory.db --ticket 1 --worker coder-1 --epoch 2
|
|
47
|
-
fts renew --db factory.db --ticket 1 --worker coder-1 --epoch 2
|
|
48
|
-
fts complete --db factory.db --ticket 1 --worker coder-1 --epoch 2 \
|
|
49
|
-
--note "implementation-notes.md written; checks green"
|
|
50
|
-
fts advance --db factory.db --ticket 1 --worker coder-1 --to-stage review \
|
|
51
|
-
--note "ready for review"
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Claims expire unless renewed (default five minutes). Always pass the epoch returned by
|
|
55
|
-
`claim` when the command offers `--epoch`; it fences stale workers after reassignment.
|
|
56
|
-
Run `fts scheduler` continuously, or `fts tick` periodically, so expired claims/leases
|
|
57
|
-
are swept and waitlists/dependencies are promoted.
|
|
58
|
-
|
|
59
|
-
Legal flow:
|
|
60
|
-
|
|
61
|
-
- `queued` → `claimed` only via `claim`
|
|
62
|
-
- `claimed` → `in_progress` via `start`
|
|
63
|
-
- `in_progress` → `done` via `complete`
|
|
64
|
-
- `done` → `queued` at a different stage via `advance`
|
|
65
|
-
- `reject` bounces work according to the pipeline; repeated bounces dead-letter
|
|
66
|
-
- operator escape hatches: `transition`, `dead-letter`, and `revive`
|
|
67
|
-
|
|
68
|
-
Dependencies are durable ordering, not messages:
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
fts add-dep --db factory.db --ticket 8 --depends-on 7 --until review
|
|
72
|
-
fts dag --db factory.db --format ascii
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## Resources (leases with FIFO waitlists)
|
|
76
|
-
|
|
77
|
-
Register stable names once, then acquire before touching the shared surface:
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
fts register-resource --db factory.db --name staging --policy exclusive_only
|
|
81
|
-
fts register-resource --db factory.db --name test-db --policy both
|
|
82
|
-
|
|
83
|
-
fts acquire --db factory.db --resource staging --ticket 8 --worker coder-1 \
|
|
84
|
-
--mode exclusive
|
|
85
|
-
fts resources --db factory.db --json
|
|
86
|
-
fts release --db factory.db --ticket 8 --resource staging
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Policies: `both`, `exclusive_only`, `shared_only`. Modes: `exclusive`, `shared`.
|
|
90
|
-
If acquisition is waitlisted, the ticket becomes blocked on that resource. Do not touch
|
|
91
|
-
the resource until the scheduler grants the lease. Waitlists are FIFO, and leases expire
|
|
92
|
-
after their TTL (default ten minutes), so long operations must renew their ticket claim
|
|
93
|
-
and finish/release the resource promptly.
|
|
94
|
-
|
|
95
|
-
Acquire multiple resources in alphabetical order to avoid team-level deadlock patterns.
|
|
96
|
-
Plan a release for every acquired lease and run it even when later work fails.
|
|
97
|
-
|
|
98
|
-
`fts release` is overloaded and accepts exactly one release target:
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
# Release a ticket claim back to the queue
|
|
102
|
-
fts release --db factory.db --ticket 8 --worker coder-1 --epoch 2 --note "reason"
|
|
103
|
-
|
|
104
|
-
# Release a resource lease
|
|
105
|
-
fts release --db factory.db --ticket 8 --resource staging
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Never combine `--worker` and `--resource` in one release command.
|
|
109
|
-
|
|
110
|
-
## Shared context and status
|
|
111
|
-
|
|
112
|
-
Use `fts show --json` first, then read the ticket's artifact folder. Artifacts accrete:
|
|
113
|
-
do not delete or rewrite another stage's handoff. Name the artifact in concise completion,
|
|
114
|
-
advance, or rejection notes so later workers can locate it from immutable history.
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
fts show --db factory.db 8 --json
|
|
118
|
-
fts board --db factory.db
|
|
119
|
-
fts queues --db factory.db
|
|
120
|
-
fts events --db factory.db --after 120
|
|
121
|
-
fts why --db factory.db 8
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
FTS does not provide free-form chat. That is deliberate: substantive context belongs in
|
|
125
|
-
artifacts and status belongs in lifecycle/history. Use ftown mail only for urgent
|
|
126
|
-
questions or escalation that cannot be represented durably, and summarize the answer in
|
|
127
|
-
the ticket artifact before continuing.
|
|
128
|
-
|
|
129
|
-
## Scheduler
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
fts tick --db factory.db --verbose
|
|
133
|
-
fts scheduler --db factory.db --interval-ms 1000
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Nothing else expires claims/leases or promotes resource waitlists. A factory dispatcher
|
|
137
|
-
may call `tick` itself; otherwise run one scheduler process per database.
|
|
138
|
-
|
|
139
|
-
## Diagnosis and observability
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
fts board --db factory.db --json
|
|
143
|
-
fts show --db factory.db 8 --json
|
|
144
|
-
fts why --db factory.db 8
|
|
145
|
-
fts resources --db factory.db --json
|
|
146
|
-
fts dag --db factory.db --format ascii
|
|
147
|
-
fts triage --db factory.db --json
|
|
148
|
-
fts doctor --db factory.db
|
|
149
|
-
fts events --db factory.db --after 0 --follow
|
|
150
|
-
fts serve --db factory.db --port 8787
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
For a stuck ticket: run `why`, then `show`, then `resources`; use `triage` for
|
|
154
|
-
dead-letters/orphans and `doctor` for systemic invariant failures. The events cursor is
|
|
155
|
-
the integration hook for orchestrators—persist the last cursor and poll after it.
|
|
156
|
-
|
|
157
|
-
## Operational gotchas
|
|
158
|
-
|
|
159
|
-
- `claimed` is reachable only via `claim`; `transition --to claimed` is invalid.
|
|
160
|
-
- `advance` must change stage.
|
|
161
|
-
- `blocked` is driven by resource/dependency mechanics, not an arbitrary worker status.
|
|
162
|
-
- Ticket folders are immutable identities and never move.
|
|
163
|
-
- History, events, and reached stages are append-only.
|
|
164
|
-
- Read commands are safe for observers; write commands require the correct worker/epoch.
|
|
165
|
-
- One FTS object/CLI process uses one connection; SQLite serializes concurrent writers.
|
|
166
|
-
- If `uvx fticket` is shadowed by a 0.1.0 installed tool, run `uv tool upgrade fticket`.
|